如何在 Haskell 中以更好/更惯用的方式编写我的 unfoldr 版本?

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

我正在尝试编写我的 unfoldr 版本以解决 Haskell Book 中的章节练习(第 12 章,逆境信号)。书上说:

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.

并给出以下内容:

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

我对 myUnfoldr 的最初尝试是使用单个辅助函数编写它:

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))

此类型检查,并且对于一些示例运行,我似乎已经实现了 unfoldr 所做的,因为:

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

myUnfoldr 相同的结果:

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

当然,myUnfoldr 是有问题的,因为如果我尝试以下操作:

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

unfoldr 按预期工作,returning [].

我明白为什么我的 unFoldr 有问题,但我找不到如何处理 f 可以 return Nothing 的情况。

所以我对这个问题的第二个看法是使用另一个辅助函数,这次使用守卫:

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 = []

第二个版本似乎在 take 10 $ myUnfoldr (\x -> Just (x, x+1)) 0take 10 $ myUnfoldr (\x -> Nothing) 0

上都能正常工作

但我对我的解决方案仍然不满意。

是否可以在不使用辅助函数的情况下以更简洁的形式编写它?或者以更惯用的方式?

两个字:模式匹配。您使用 $ head $ maybeToList (f x) 两次来获取下一个状态和列表元素。但是,我们可以在 f x 上进行模式匹配,以确定我们是否有 Just 另一个元素(以及继续我们的小展开的状态)或者我们是否完成了:

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

您的变体的问题是您没有使用 isJust (f x) 来发挥您的优势。与其检查某些内容是否为 Just 然后从中获取值,不如检查模式是否匹配。

请注意,这也适用于模式守卫:

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

我还没有看过这本书,但我想这两种技术在第 12 章都已经讲过了。