The other day, I was thinking about some code in a domain that I used to do a lot of work in: signal filtering. Imagine that you have an array of doubles and you want to remove some of its spikiness. One way to handle this is to use a low pass filter. You take your old array and transform it into a new one in which each element array[i] has the value (old_array[i-1] + 2 * old_array[i] + old_array[i + 1]) / 4.0. The code for that filter is straightforward except for the fact that you have to deal with the edge cases: the calculations for i = 0 and i = n - 1 are a little different. All in all, it's about five to seven lines of code, depending upon how you format it.
For kicks, I decided to write it in Haskell. If I'm clever enough, I thought, I should be able to write the code without any edge conditions. I can create a lazy list that goes beyond the edges and then pick out the subset that I need from the center.. the values in the range 0..n-1.
I ended up with a solution but it wasn't pleasant, so I posted it to the Haskell Cafe mailing list. A guy who knows much more Haskell than I do pulled an edge case back in and came up with this shorter solution:
smooth n = (!! n) . iterate f where
f ds = [(x + 2 * y + g y z) / 4.0 | x:y:z <- tails (head ds : ds)]
g x [] = x
g _ (x:_) = x
The function smooth allows you to smooth a list n times. It does this by creating an infinite list of iterative applications of a low pass filter and selecting the nth element of that list. The function that is iterated, f, constructs a list of elements which are applications of the filter (x[i-1] + * 2.0 x[i] + x[i+1] / 4.0). The smooth function also uses a list comprehension that matches triples based upon the staggered tails of a the original list. The special case at the far end of the list is handled by a function, g, which takes an element and a list and returns the appropriate values at the edge for the filter.
Simple, right?
It depends on your perspective, I guess. There is some simplicity there if you think in those terms.
To me, this problem seems emblematic of the difference between imperative and functional programming. In functional programming, some solutions are very natural when seen from the imperative mindset, but others aren't - you have you go around the block to get next door. Functional programming has a grain to it and the grain makes particular classes of solutions much more natural even though they appear alien when seen from other contexts.
I guess this is true of all styles of programming. There certainly are some problems that are difficult in an imperative style. The thing that I find interesting about the pure functional style, though, is that it pushes you toward its own set of abstractions. The function tails in the code above is a good example. If you give tails this list: [1,2,3] it gives you this list back: [[1,2,3],[2,3],[3],[]]. I doubt many people using a non-functional language could imagine many uses for a function like that, but when the grain runs toward laziness and away from mutation, you reach for these sorts of abstractions for leverage - they become your bread and butter.
Haskell truly is a different language. It makes most other programming languages look like dialects of each other.

Comments (17)
[Haskell] makes most other programming languages look like dialects of each other.
Thats because they all live in the IO monad (except Prolog, which lives in the List monad part time).
Posted by Paul Johnson | March 25, 2008 3:49 PM
The above code is pretty hard to read. Certainly there are better names than f and g.
Posted by Michael Maloney | March 25, 2008 4:29 PM
Also, I didn't realize it at first, but this code example abuses fail in the list monad! Heathen!
(the pattern matching in the list comprehension will fail for tails of length less than 2)
Posted by Michael Maloney | March 25, 2008 4:37 PM
Michael, yes, giving f a good name is easy. I'm not sure about g, though.
One of the things that I notice about Haskell is that people seem to treat it like math. Meaning is conveyed as much by pattern as by name. It did bug me when I first enountered it: Haskell and the Great Verb Drought of 1998 ( http://michaelfeathers.typepad.com/michael_feathers_blog/2007/01/haskell_and_the.html )
Posted by Michael Feathers | March 25, 2008 4:59 PM
I keep reading about how readable Haskell can be. Someday I hope to see an actual example of this. Not knowing the language, the first of the four lines was the only one that made any real sense to me (even that is a bit weird).
Then again I didn't bother to read the problem it was trying to solve... but if it's genuinely beautiful code, should I have needed to? Shouldn't it be *self* documenting?
Posted by Pete | March 25, 2008 5:04 PM
For me, the tricky part here was to realise that all sub-lists of the tails list with less than 3 elements are discarded(x:y:z pattern does that). Everything else is straightforward.
Posted by foldr | March 25, 2008 5:48 PM
That isn't abuse of fail, that's correct use of fail.
The problem with fail is that (in Haskell 98) it's a part of the Monad typeclass, but not all monads have a proper notion of failure. For instance, in the state monad, fail is implemented with a call to error, so a failed pattern match results in bottom, which isn't particularly nice behavior.
In Haskell 1.4, monadic pattern matching resulted in a MonadZero constraint, I believe (which is part of a split up version of the current MonadPlus). MonadZeros have a proper notion of failure (mzero), so fail makes sense there (it's an mzero that takes a String describing what caused the mzero). And, of course, lists are valid MonadZeros (with mzero = []), whereas State is not, so everything was dandy.
The reason fail is regarded as evil is that in H98 (for reasons I don't know), it was moved to the Monad class, even though it doesn't make sense to put it there. However, there's nothing wrong with using possibly-failing pattern matches in list comprehensions or, in general, MonadPlus computations. That's the correct place to use it.
Posted by Dan Doel | March 25, 2008 5:53 PM
Here is a different, verbose version that may be a more readable starting point for beginners.
Posted by Tom | March 25, 2008 6:26 PM
If I understand your Haskell correctly, this can be done in one line using SuperCollider's hybrid approach, which is something I'd like to see in more general-purpose languages (SuperCollider is for real-time audio/music programming):
b = a.collect({ |x, i| (a[i-1] ? x) + (x * 2) + (a[i+1] ? x) / 4 });
"a ? b" returns a if a is not nil, b if it is. Accessing arrays outside their bounds returns nil.
Posted by Tim Walters | March 25, 2008 9:46 PM
g y z can be rewritten as head (z++y), thus eliminating 2 lines and the uninformative name "g".
Posted by Anonymous | March 25, 2008 11:23 PM
If I understand the edge conditions correctly, the smoothing expression x * 2y + z uses
xo = head ds and
zn-1 = last ds
smooth could them be expressed as:
smooth n = (!! n) . iterate f where
f ds = [(x + 2 * y + z) / 4.0 | shift-left-1, ds, shift-right-1]
shift-left-1 = take (length ds) [head ds : ds]
shift-rigt-1 = take (length ds) [ds : last ds]
Posted by bhrgunatha | March 26, 2008 12:30 AM
bhrgunatha: Your shift-rigt-1 is wrong, you forgot to skip the first element.
Posted by llogiq | March 26, 2008 8:22 AM
def smooth(ds): return [sum(a)/len(a) for a in [ds[i-1:i]+[ds[i]]+ds[i:i+2] for i in range(0, len(ds))]]
Python doesn't have "iterate", which makes duplicating the solution precisely in one line kind of gross:
def smooth(ds, n): return reduce(lambda m, junk: [sum(a)/len(a) for a in [m[i-1:i] + m[i:i+1] + m[i:i+2] for i in range(0, len(m))]], range(0, n), ds)
Of course, I'm not sure what the exact edge condition is supposed to be (I don't speak Haskell and the code doesn't just work (TM) when I paste it into hugs).
Cheers.
Posted by chops | March 26, 2008 9:38 AM
Done in a function-level[1] programming language J[2]:
3&((4 %~ [: +/ 1 2 1&*)\)@({. , ] , {:)
[1] http://en.wikipedia.org/wiki/Function-level
[2] http://jsoftware.com
Posted by June Kim | March 26, 2008 10:12 AM
smooth :: [Double] -> [Double]
smooth [] = []
smooth xs = [(l + 2*x + r)/4 | (l,x,r) where
xs_buff = [0] ++ xs ++ [0]
triples (x:y:z:rest) = (x,y,z) : triples (y:z:rest)
triples _ = []
Posted by David | March 27, 2008 10:57 PM
Whoops:
smooth[]=[]
smooth xs=[ (l + 2*x + r) / 4 | (l, x, r) <- triples xs_buf]
where
xs_buff = [0] ++ xs ++ [0]
triples (x:y:z:rest) = (x,y,z):triples(y:z:rest)
triples _ = []
Posted by Anonymous | March 28, 2008 8:24 PM
@llogic: yes thanks, should've been
smooth n = (!! n) . iterate f where
f ds = [(x + 2 * y + z) / 4.0 | shift-left-1, ds, shift-right-1]
shift-left-1 = take (length ds) [head ds : ds]
shift-rigt-1 = take (length ds) [tail ds : last ds]
Posted by bhrgunatha | March 29, 2008 1:47 AM