I’m learning Haskell, and one of my practice functions was a simple recursive permute. I adapted the solution described here and originally got this:
selections [] = []
selections (x:xs) = (x, xs) : [ (y, x:ys) | (y,ys) <- selections xs ]
permute xs = [y:ps | (y,ys) <- selections xs, ps <- permute ys]
(Yes, this could be shorter, but I was going for explicitness and clarity.)
However, this version of permute always returned an empty list! After flailing a bit, I got it to work by changing permute to:
permute [] = [[]]
permute xs = [y:ps | (y,ys) <- selections xs, ps <- permute ys]
However, I’m still perplexed as to why the original version always returns an empty list.
Well, the two are obviously very similar, so why not look in detail at where they disagree? The recursive portion is exactly the same in both, so first we can say that both versions do the same thing on non-empty lists. This sounds wrong because they give different results, but it’s actually true in that they perform the same operation on the result of the recursive call.
The base case from the correct version is
permute [] = [[]], which is self-explanatory. The base case from the first version, however, is implicit in the list comprehension. Given the definition:…we can substitute in
[]forxsto see what happens:Given the definition
selections [] = [], we can simplify to:…from which it is clear that no results are generated, so the whole list comprehension is empty, simplifying down to just:
Now, consider the last recursive step before the base, substituting
[x]as the argument:The definition of
selectionsisselections (x:xs) = (x, xs) : [ (y, x:ys) | (y,ys) <- selections xs ], substituting in[x]givesselections [x] = (x, []) : [ (y, x:ys) | (y,ys) <- selections [] ].selections []evaluates to[], so the entire list comprehension reduces to[]as well, givingselections [x] = (x, []) : []or justselections [x] = [(x, [])].Substitute that into
permuteas above:There’s only one element in the list, so we can ignore the
<-comprehension binding and substitute directly:Having established that
permute []evaluates to[], we can substitute that in as well and find that the list comprehension again reduces to[]:…which easily generalizes to returning
[]for any input. The working version, however, uses the following definition:In the final reduction of the final recursive step, this changes the substitutions to the following:
Since
psis being bound to something with a single element, we again can substitute directly:Which is just saying that
permute [x] = [x].