Haskell中的unfold
函数非常便于创建列表。它的定义是:
unfold :: (b -> Maybe (a, b)) -> b -> [a]
但是我想得到累加器的最后一个值。可能的实现是:
unfoldRest :: (b -> Maybe (a, b)) -> b -> ([a], b)
unfoldRest fct ini = go fct ini []
where
go f s acc =
case f s of
Nothing -> (acc, s)
Just (a, b) -> go f b (acc ++ [a])
但我想知道是否有办法用现有的功能来做。最后这个:
countDown 0 = Nothing
countDown n = Just (n, n-1)
unfoldRest countDown 10
将返回:
([10,9,8,7,6,5,4,3,2,1],0)
因为累加器值达到0
时迭代停止。
答案 0 :(得分:8)
import Data.List
unfoldr' :: (b -> Maybe (a, b)) -> b -> [(a, b)]
unfoldr' f = unfoldr (fmap (\(a, b) -> ((a, b), b)) . f)
将为您提供所有累加器的状态。然后你可以选择查看你想要的任何一个,包括最后一个。
答案 1 :(得分:2)
这不是一个很好的答案(Tom Ellis更好地介绍了“使用现有功能的方法”部分),但值得再看一下你原来的解决方案。由于您使用(++)
重复追加单个元素,因此它相对于生成列表的长度需要二次时间。您可以通过删除帮助函数并使用(:)
直接构建列表来避免这种情况:
unfoldRest' :: (b -> Maybe (a, b)) -> b -> ([a], b)
unfoldRest' f s = case f s of
Nothing -> ([], s)
Just (a, b) -> (\ ~(xs, y) -> (a : xs, y)) $ unfoldRest' f b
懒惰模式匹配(lambda中的~(xs, y)
)很重要;它允许你查看列表的第一个元素而不必计算最终状态,因此用无限列表做一些有用的事情(无论如何,Tom Ellis的解决方案对于无限列表更好,因为你可以看到不是只有生成的值,但也可以在任意段之后的状态)。正如Will Ness指出的那样,您可能会发现Just
案例的右侧使用let
绑定更自然,如let (xs, y) = unfoldRest' f b in (a : xs, y)
中那样。
当您使用“pointfree”标记问题时,值得一提的是,使用maybe
和Control.Arrow
组合器可以减少相当多的点数:
import Control.Arrow ((***), first, app)
unfoldRest'' f s =
maybe ([], s) (app . (first . (:) *** unfoldRest'' f)) $ f s
你是否想走那么远是一个品味问题。懒惰问题得到了正确处理,因为函数(***)
的实现使用了惰性模式匹配。
答案 2 :(得分:1)
我以前曾解决过这个问题 - 解决它的方法之一是使用 State monad。
简单来说,它们处理形式为 s -> (d, s)
的函数。直观地说,s
是在计算过程中可能发生变化的状态类型。
首先要注意的是,s -> Maybe (d, s)
没有 s -> (d, s)
形式:前者是事物的元组,而后者是 Maybe
,我们需要一个函数在类型 s -> (Maybe d, s)
上,如果函数返回 None
,则修改后的函数将返回之前的状态。此适配器的一种可能实现是:
keepFailure :: (s -> Maybe (d, s)) -> (s -> (Maybe d, s))
keepFailure f s = maybe (Nothing, s) (first Just) (f s)
记住 import Data.Bifunctor
因为 first
函数
有一个函数可以将 s -> (d, s)
转换为 State s d
,称为 state
,并且
runState
将其转换回来。现在我们实现一个函数,它会尝试耗尽所有可能值的状态:
stateUnfoldr :: State s (Maybe d) -> State s [d]
stateUnfoldr f = do
mx <- f
case mx of
Just x -> do
xs <- stateUnfoldr f
return $ x:xs
Nothing -> return []
简单来说,mx <- f
的工作原理类似于“将 f
应用于输入,更新状态,将返回值分配给 mx
”
然后,我们可以将所有内容拼凑起来:
fStateUnfoldr :: (s -> Maybe (d, s)) -> (s -> ([d], s))
fStateUnfoldr f = runState $ stateUnfoldr $ state . keepFailure $ f
记得import Control.Monad.State
state . keepFailure
将 f
改编为 State s (Maybe d)
Monad,然后 stateUnfoldr
展开为 State s [d]
,然后 runState
将其转回函数.
如果您只需要状态或列表,我们也可以使用 execState
或 evalState
代替 runState
。