I was recently visiting the Ferrari museum in Maranello, Italy, where I saw this display of telemetry data from racing cars.

Telemetry data from a racing car. The racing track is displayed in the upper left corner and various data channels are displayed below.
The processing and the display of telemetry data is an interesting programming challenge. It has application in space exploration (as in, when you land a probe on a surface of a comet), medicine, and the military. The same techniques are used in financial systems where streams carry information about stock prices, commodity prices, and currency exchange rates.
It’s also a problem that lends itself particularly well to functional programming. If you are one of these shops working with telemetry, and you have to maintain legacy code written in imperative style, you might be interested in an alternative approach, especially if you are facing constant pressure to provide more sophisticated analysis tools and introduce concurrency to make the system faster and more responsive.
What all these applications have in common is that they deal with multiple channels generating streams of data. The data has to be either displayed in real time or stored for later analysis and processing. It’s pretty obvious to a functional programmer that channels are functors, and that they should be composed using combinators. In fact this observation can drive the whole architecture. The clincher is the issue of concurrency: retrofitting non-functional code to run in parallel is a lost battle — it’s almost easier to start from scratch. But treating channels as immutable entities makes concurrency almost an after-thought.
Everything is a Number
The most basic (and totally wrong) approach is to look at telemetry as streams of numbers. This is the assembly language of data processing. When everything is a number and you can apply your math any way you wish. The problem is that you are throwing away a lot of useful information. You want to use types as soon as possible to encode additional information and to prevent nonsensical operations like adding temperature to velocity.
In an engineering application, the least you can do is to keep track of units of measurement. You also want to distinguish between channels that produce floating-point numbers and ones that produce integers, or Booleans, or strings. This immediately tells you that a channel should be a polymorphic data structure. You should be able to stream any type of data, be it bytes, complex numbers, or vectors.
Everything is an Object
To an object-oriented mind it looks very much like a channel should be an object that is parameterized by the type of data it carries. And as an object it should have some methods. We need the get
method to access the current value, and the next
method to increment the position in the stream. As an imperative programmer you might also be tempted to provide a mutator, set
. If you ever want your program to be concurrent, don’t even think about it!
If you’re a C++ programmer, you may overload some operators, and use *
and ++
instead. That would make a channel look more like a forward iterator. But whatever you call it, a functional programmer will recognize it as a list, with the head
and tail
functionality.
Everything is a List
Let’s talk about lists, because there is a lot of misunderstanding around them. When people think of lists in imperative languages they think about storage. A list is probably the worst data type for storing data. Imperative programmers naturally assume that functional programmers, who use lists a lot, must be crazy. They are not! A Haskell list is almost never used for storing bulk data. A list is either an interface to data that is stored elsewhere, or a generator of data. Haskell is a lazy functional language, so what looks like a data structure is really a bunch of functions that provide data on demand.
That’s why I wouldn’t hesitate to implement channels as lists in Haskell. As an added bonus, lists can provide a pull interface to data that is being pushed. Reactive programs that process streams of data may be written as if all the data were already there — the event handler logic can be hidden inside the objects that generate the data. And this is just what’s needed for live telemetry data.
Obviously, functional programming is easier in Haskell than in C++, C#, or Java. But given how much legacy software there is, it could be a lost cause to ask management to (a) throw away existing code and start from scratch, (b) retrain the team to learn a new language, and (c) deal with completely new performance characteristics, e.g., lazy evaluation and garbage collection. So, realistically, the best we can do is to keep introducing functional methods into imperative languages, at least for the time being. It doesn’t mean that Haskell should’t play an important role in it. Over and over again I find myself prototyping solutions in Haskell before translating them into C++. The added effort pays back handsomely through faster prototyping, better code quality, and fewer bugs to chase. So I would highly recommend to every imperative programmer to spend, say, an hour a day learning and playing with Haskell. You’d be amazed how it helps in developing your programming skills.
Everything is a Functor
So, if you’re an object oriented programmer, you’ll probably implement a channel as something like this:
template <class T> Channel { virtual T get(); virtual bool next(); };
and then get stuck. With this kind of interface, the rest of your program is bound to degenerate into a complex system of loops that extract data from streams and process them, possibly stuffing it back into other streams.
Instead, I propose to try the functional way. I will show you some prototype code in Haskell, but mostly explain how things work, so a non-Haskell programmer can gain some insight.
Here’s the definition of a polymorphic channel type, Chan
:
data Chan a = Chan [a]
where a
plays the role of a type variable, analogous to T
in the C++ code above. The right hand side of the equal sign defines the constructor Chan
that takes a list as an argument. Constructors are used both for constructing and for pattern matching. The notation [a]
means a list of a
.
The details don’t really matter, as long as you understand that the channel is implemented as a list. Also, I’m making things a little bit more explicit for didactic purposes. A Haskell programmer would implement the channel as a type alias, type
, rather than a separate type.
Rule number one of dealing with lists is: try not to access their elements in a loop (or, using the functional equivalent of a loop — recursively). Operate on lists holistically. For instance, one of the most common operations on lists is to apply a function to every element. That means we want our Chan
to be a functor.
A functor is a polymorphic data type that supports operating on its contents with a function. In the case of Chan
that’s easy, since a list itself is a functor. I’ll be explicit here, again for didactic reasons. This is how you make Chan
an instance of the Functor
class by defining how to fmap
a function f
over it:
instance Functor Chan where fmap f (Chan xs) = Chan (map f xs)
Here, map
is a library function that applies f
to every element of the list. This is very much like applying C++ std::transform
to a container, except that in Haskell everything is evaluated lazily, so you can apply fmap
to an infinite list, or to a list that is not there yet because, for instance, it’s being generated in real time from incoming telemetry.
Everything is a Combinator
Let’s see how far we can get with this channel idea. The next step is to be able to combine multiple channels to generate streams of derived data. For instance, suppose that you have one channel from a pressure gauge, and another providing volume data, and you want to calculate instantaneous temperature using the ideal gas equation.
Let’s start with defining some types. We want separate types for quantities that are measured using different units. Once more, I’m being didactic here, because there are ready-made Haskell libraries that use so called phantom types to encode measurement units. Here I’ll do it naively:
data Pressure = Pascal Float data Volume = Meter3 Float data Temp = Kelvin Float
I’ll also define the ideal gas constant:
constR = 8.314472 -- J/(mol·K)
Here’s the function that calculates the temperature of ideal gas:
getT :: Float -> Pressure -> Volume -> Temp getT n (Pascal p) (Meter3 v) = Kelvin (p * v / (n * constR))
The question is, how can we apply this function to the pressure and volume channels to get the temperature channel? We know how to apply a function to a single channel using fmap
, but here we have to work with two channels. Fortunately, a channel is not just a functor — it’s an applicative functor. It defines the action of multi-argument functions on multiple channels. I’ll give you a Haskell implementation, but you should be able to do the same in C++ by overloading fmap
or transform
.
instance Applicative Chan where pure x = Chan (repeat x) (Chan fs) <*> (Chan xs) = Chan (zipWith ($) fs xs)
The Applicative
class defines two functions. One is called pure
, and it creates a constant channel from a value by repeatedly returning the same value. The other is a binary operator <*>
that applies a channel of functions (yes, you can treat functions the same way you treat any other data) to a channel of values. The function zipWith
applies, pairwise, functions to arguments using the function application operator ($)
.
Again, the details are not essential. The bottom line is that this allows us to apply our function getT
to two channels (actually, three channels, since we also need to provide the amount of gas in moles — here I’m assuming 0.1 moles).
chT :: Chan Pressure -> Chan Volume -> Chan Temp chT chP chV = getT <$> pure 0.1 <*> chP <*> chV
Such functions that combine channels into new channels are called combinators, and an applicative functor makes the creation of new combinators very easy.
The combinators are not limited to producing physical quantities. They may as well produce channels of alerts, channels of pixels for display, or channels of visual widgets. You can construct the whole architecture around channels. And since we’ve been only considering functional data structures, the resulting architecture can be easily subject to parallelization.
Moving Average
But don’t some computations require mutable state? For instance, don’t you need some kind of accumulators in order to calculate, let’s say, moving averages? Let’s see how this can be done functionally.
The idea is to keep a running sum of list elements within a fixed window of size n
. When advancing through the list, we will add the new incoming element to the running sum and subtract the old outgoing element. The average is just this sum divided by n
.
We can use the old trick of delaying the list by n
positions. We’ll pad the beginning of the delayed list with n
zeros. Here’s the Haskell code:
delay :: Num a => Int -> [a] -> [a] delay n lst = replicate n 0 ++ lst
The first line is the (optional, but very useful) type signature. The second line defines the function delay
that takes the delay counter n
and the list. The function returns a list that is obtained by concatenating (operator ++
) the zero-filled list (replicate n 0
) in front of the original list. For instance, if you start with the list [1, 2, 3, 4]
and delay it by 2, you’ll get [0, 0, 1, 2, 3, 4]
.
The next step is to create a stream of deltas — the differences between elements separated by n
positions. We do it by zipping two lists: the original and the delayed one.
zip lst (delay n lst)
The function zip
pairs elements from the first list with the elements from the second list.
Continuing with our example, the zipping will produce the pairs [(1, 0), (2, 0), (3, 1), (4, 2)]
. Notice that the left number in each pair is the incoming element that is to be added to the running sum, while the right number is the outgoing one, to be subtracted from the running sum.
Now if we subtract the two numbers in each pair we’ll get exactly the delta that has to be added to the running sum at each step. We do the subtraction by mapping the operator (-)
over the list. (To make the subtraction operator (-)
operate on pairs we have to uncurry
it. (If you don’t know what currying is, don’t worry.)
deltas :: Num a => Int -> [a] -> [a] deltas n lst = map (uncurry (-)) (zip lst (delay n lst))
Continuing with the example, we will get [1, 2, 2, 2]
. These are the amounts by which the running sum should change at every step. (Incidentally, for n
equal to one, the deltas are proportional to the derivative of the sampled stream.)
Finally, we have to start accumulating the deltas. There is a library function scanl1
that can be used to produce a list of partial sums when called with the summation operator (+)
.
slidingSums :: Num a => Int -> [a] -> [a] slidingSums n lst = scanl1 (+) (deltas n lst)
At each step, scanl1
will add the delta to the previous running sum. The “1” in its name means that it will start with the first element of the list as the accumulator. The result, in our little example, is [1, 3, 5, 7]
. What remains is to divide each sum by n
and we’re done:
movingAverage :: Fractional a => Int -> [a] -> [a] movingAverage n list = map (/ (fromIntegral n)) (slidingSums n list)
Since n
is an integer, it has to be explicitly converted to a fractional number before being passed to the division operator. This is done using fromIntegral
. The slightly cryptic notation (/ (fromIntegral n))
is called operator section. It just means “divide by n
.”
As expected, the final result for the two-element running average of [1, 2, 3, 4]
is [0.5, 1.5, 2.5, 3.5]
. Notice that we haven’t used any mutable state to achieve this result, which makes this code automatically thread safe. Also, because the calculation is lazy, we can calculate the moving average of an infinite list as long as we only extract a finite number of data points. Here, we are printing the first 10 points of the 5-element moving average of the list of integers from 1 to infinity.
print (take 10 (movingAverage 5 [1..]))
The result is:
[0.2, 0.6, 1.2, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0]
Conclusion
The functional approach is applicable to designing software not only in the small but, more importantly, in the large. It captures the patterns of interaction between components and the ways they compose. The patterns I mentioned in this post, the functor and the applicative functor, are probably the most common, but functional programmers have at their disposal a large variety of patterns borrowed from various branches of mathematics. These patterns can be used by imperative programmers as well, resulting in cleaner and more maintainable software that is, by construction, multithread-ready.
November 12, 2014 at 9:52 pm
functional languages tends to be more concise and easier to maintain. perhaps because it is easier to create complex data structure
November 14, 2014 at 1:06 pm
Your explanation of Applicative is the simplest one I have ever seen. Thank you!
I think that using STL with its containers and algorithms (instead of hand-coded loops and so called “clever code”) is a good step towards Haskell-like way of programming, or functional programming generally.
November 19, 2014 at 8:23 pm
Great article! I’ve a problem that’s a subset of your movingAverages. I’ve a sequence of road segments(which have lengths) and a total distance d from the beginning of the sequence. I need to determine which road segment I’m on in order to access the road segment’s additional properties. So some combination of map, scanl and takeWhile( ? < d ) should get me the object of interest. The closest to scanl in C++ is the partial_sum algorithm, which of course is an algorithm not able to interact with takeWhile. Using Eric's new range lib has facilities which can be composed into a solution, but as far as i can tell requires storing mutable state (the previous partial sum). Any insights?
November 20, 2014 at 5:31 pm
@Jeff: In the tradition of one-liners, this would give you the index of the segment you’re looking for.
The use of
head
makes it partial, so a better solution would usesafeHead
and return aMaybe
result.In C++, I wouldn’t worry about mutation as long as it’s confined to a local variable that cannot possibly be shared between threads.
November 20, 2014 at 5:43 pm
Hey! Great article ! I had a question which may be straightforward for people more familiar with functional programming. It seems like you operate on a chunk of the list at a time to produce the deltas array. However in the case of a purely streaming setting for incoming data how would you modify this approach to work ? I can imagine writing a wrapper around this approach that tries to buffer some n elements but that doesn’t align with the goal of having the moving average available at each point in the stream.
November 20, 2014 at 8:25 pm
@fabrol: I hinted at this in the article but didn’t go into detail. In Haskell, a list is a lazy data structure, so you can think of it as a function that produces data on demand. The list is returned by some other function that controls how data is produced. So evaluating the n-th element of a list might actually call a function that blocks until the value becomes available.
In an imperative language like C++ or Java, you would implement a lazy list as a stream. See my blog about lazy streams.
November 23, 2014 at 10:46 pm
There’s an implicit simplifying assumption that each channel receives one data element per clock tick. But most event logs I’m familiar with contain timestamped records written at varying rates. When nothing has changed, you want the system to remain idle rather than chewing up power or storage sending the same record repeatedly.
So it seems like combining channels gets more complicated if you want to do it efficiently? Elm’s signals aren’t simple lists.
November 24, 2014 at 12:10 pm
@Brian: These are all good questions and the solution depends on the particular system. In the simplest case that I described in the post all channels are synchronized — one item per clock tick. If the clock itself doesn’t run uniformly, time stamps might arrive in a separate channel.
A more complex system has several independently sampled channels, each with its own time stamps. Combining such channels requires a special combinator that fires whenever any of the channels fires — the OR combinator.
Since you mention Elm, you must be familiar with FRP (Functional Reactive Programming). The OR combinator is a problem in functional programming because it’s not deterministic. Conal Elliott in his paper Push-Pull Functional Reactive Programming had to use non-pure
unsafePerformIO
to implement it. But in imperative languages we are not that picky. So it is possible to implement it efficiently, just not in a pure way.In many cases the sampling performance is not even a problem and you can use the “pull” approach. A channel that changes rarely would be encapsulated in a “latch” that stores the last value and returns it every time it’s queried. For real time display of telemetry this might be a sensible approach, since you wouldn’t refresh the screen more often than, say, 25 times a second anyway.
Anyway, this is a large topic, and I wouldn’t dream of exhausting it in one blog post.
November 29, 2014 at 6:36 pm
@Jeff My range-v3 library has all the parts needed to do this out of the box, save one: a partial_sum view. It could easily be implemented either statefully (by keeping the sum in the range object), or statelessly (by keeping it in the iterator). I would probably prefer to keep it in the iterator.
November 29, 2014 at 6:39 pm
EDIT: You can fake it today using the
transform
view with a mutable lambda. But that’s distasteful.November 29, 2014 at 7:28 pm
… and now range-v3 has a
partial_sum
view.December 1, 2014 at 6:15 pm
Thanks Eric, you beat me to it!
December 19, 2014 at 12:33 pm
Just like others..a great post. Indebted to you for ever.
A typo..
this -> data Chan a = Ch [a]
could be -> data Chan a = Chan [a]
A type in Constructor name.
December 19, 2014 at 12:46 pm
Thanks, Chandra. Fixed. It wasn’t actually a typo but an attempt to give the value constructor a different name than the type constructor. But then I kept using
Chan
as the value constructor everywhere anyway.December 25, 2014 at 8:24 pm
Shouldn’t hat read ‘Telemetry data’ instead of ‘Telementry data’
May 3, 2015 at 8:29 am
Thanks for your site – I am using a 7″ tablet also with mouse & keyboard and programming with the Online compiler which offers several languages including C++ and Haskell> This is great as I can now test my programs anywhere without being burdened with having a 14″ laptop ( as long as i can access the web).