It turns out that you can solve your compress
problem using a foldr
with a three-argument function.
compress :: Eq a => [a] -> [a]
compress [] = []
compress (z:zs) = z : foldr f (const []) zs z
where f x k w | x==w = k x
| otherwise = x : k x
Let's dissect that. First, we can improve readability by changing the last two lines to
where f x k = \w -> if x==w then k x else x : k x
This makes it evident that a ternary function is nothing but a binary function returning a unary function. The advantage of looking at it in this way is that foldr
is best understood when passed a binary function. Indeed, we are passing a binary function, which just happens to return a function.
Let's focus on types now:
f :: a -> (a -> [a]) -> (a -> [a])
f x k
So, x::a
is the element of the list we are folding on. Function k
is the result of the fold on the list tail. The result of f x k
is something having the same type as k
.
\w -> if .... :: (a -> [a])
The overall idea behind this anonymous function is as follows. The parameter k
plays the same role as acc
in the OP code, except it is a function expecting the previous element w
in the list before producing the accumulated compressed list.
Concretely, we use now k x
when we used acc
, passing on the current element to the next step, since by that time x
will become the previous element w
. At the top-level, we pass z
to the function which is returned by foldr f (const [])
.
This compress
variant is lazy, unlike the posted solution. In fact, the posted solution needs to scan the whole list before starting producing something: this is due to (\x acc -> ...)
being strict in acc
, and to the use of last xs
. Instead, the above compress outputs list elements in a "streaming" fashion. Indeed, it works with infinite lists as well:
> take 10 $ compress [1..]
[1,2,3,4,5,6,7,8,9,10]
That being said, I think using a foldr
here feels a bit weird: the code above is arguably less readable than the explicit recursion.