Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to write my version of unfoldr in a better / more idiomatic manner in Haskell?

Tags:

haskell

fold

I'm trying to write my version of unfoldr in order to solve a chapter exercise from Haskell Book (Chapter 12, Signaling Adversity). The book says:

Write the function myUnfoldr using direct recursion. Compare with the built-in unfoldr to check your implementation. Again, don’t look at implementations of unfoldr so that you figure it out yourself.

And gives the following:

myUnfoldr :: (b -> Maybe (a, b)) -> b -> [a] 
myUnfoldr = undefined

My initial attempt for myUnfoldr has been to write it using a single helper function:

maybeToList :: Maybe a -> [a]
maybeToList (Just x) = [x]
maybeToList Nothing = []

myUnfoldr :: (b -> Maybe (a, b)) -> b -> [a]
myUnfoldr f x = (fst $ head $ maybeToList (f x)) :
  myUnfoldr f (snd $ head $ maybeToList (f x))

This type checks, and for a few example runs it seems like I've achieved what unfoldr does, because:

λ> take 10 $ unfoldr (\x -> Just (x, x + 1)) 0
[0,1,2,3,4,5,6,7,8,9]

and the same result with myUnfoldr:

λ> take 10 $ myUnfoldr (\x -> Just (x, x + 1)) 0
[0,1,2,3,4,5,6,7,8,9]

But of course, myUnfoldr is problematic because if I try the following:

λ> take 10 $ myUnfoldr (\x -> Nothing) 0
[*** Exception: Prelude.head: empty list

whereas unfoldr works as expected, returning [].

I can see why my unFoldr is problematic, but I couldn't find out how to handle the situation where f can return Nothing.

So my second take at the problem is to use another helper function, and this time use guards:

isJust :: Maybe a -> Bool
isJust (Just _) = True
isJust _      = False

myUnfoldr :: (b -> Maybe (a, b)) -> b -> [a]
myUnfoldr f x | isJust $ (f x) = (fst $ head $ maybeToList (f x)) : myUnfoldr f (snd $ head $ maybeToList (f x))
              | otherwise = []

This second version seems to work as expected both for take 10 $ myUnfoldr (\x -> Just (x, x+1)) 0 and take 10 $ myUnfoldr (\x -> Nothing) 0

But I'm still not comfortable with my solution.

Any ideas whether it can be written in a more concise form, without using a helper function? Or in a more idiomatic way?

like image 806
Emre Sevinç Avatar asked Dec 09 '25 04:12

Emre Sevinç


1 Answers

Two words: pattern matching. You're using $ head $ maybeToList (f x) twice to get both the next state as well as the list element. However, we can pattern match on f x to get whether we're having Just another element (and a state to continue our little unfolding) or whether we're done:

myUnfoldr :: (s -> Maybe (a, s)) -> s -> [a]
myUnfoldr f x = case f x of
    Just (next, state) -> next : myUnfoldr f state
    Nothing            -> []

The problem with your variant is that you don't use isJust (f x) to your advantage. Instead of checking whether something is Just and then getting the values out of it, just check whether the pattern matches.

Note that this is also possible with pattern guards:

myUnfoldr :: (s -> Maybe (a, s)) -> s -> [a]
myUnfoldr f x
  | Just (next, state) <- f x = next : myUnfoldr f state
  | otherwise                 = []

I haven't read the book yet, but I guess that both techniques have been told by chapter 12.

like image 51
Zeta Avatar answered Dec 10 '25 19:12

Zeta



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!