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:
permute xs = [y:ps | (y,ys) <- selections xs, ps <- permute ys]
...we can substitute in []
for xs
to see what happens:
permute [] = [y:ps | (y,ys) <- selections [], ps <- permute ys]
Given the definition selections [] = []
, we can simplify to:
permute [] = [y:ps | (y,ys) <- [], ps <- permute ys]
...from which it is clear that no results are generated, so the whole list comprehension is empty, simplifying down to just:
permute [] = []
Now, consider the last recursive step before the base, substituting [x]
as the argument:
permute [x] = [y:ps | (y,ys) <- selections [x], ps <- permute ys]
The definition of selections
is selections (x:xs) = (x, xs) : [ (y, x:ys) | (y,ys) <- selections xs ]
, substituting in [x]
gives selections [x] = (x, []) : [ (y, x:ys) | (y,ys) <- selections [] ]
. selections []
evaluates to []
, so the entire list comprehension reduces to []
as well, giving selections [x] = (x, []) : []
or just selections [x] = [(x, [])]
.
Substitute that into permute
as above:
permute [x] = [y:ps | (y,ys) <- [(x, [])], ps <- permute ys]
There's only one element in the list, so we can ignore the <-
comprehension binding and substitute directly:
permute [x] = [y:ps | (y,ys) = (x, []), ps <- permute ys]
permute [x] = [ x:ps | ps <- permute []]
Having established that permute []
evaluates to []
, we can substitute that in as well and find that the list comprehension again reduces to []
:
permute [x] = []
...which easily generalizes to returning []
for any input. The working version, however, uses the following definition:
permute [] = [[]]
In the final reduction of the final recursive step, this changes the substitutions to the following:
permute [x] = [ x:ps | ps <- permute []]
permute [x] = [ x:ps | ps <- [[]] ]
Since ps
is being bound to something with a single element, we again can substitute directly:
permute [x] = (x:[])
Which is just saying that permute [x] = [x]
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With