展开返回累加器的最后一个状态

时间:2014-05-08 17:20:59

标签: haskell pointfree unfold

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时迭代停止。

3 个答案:

答案 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”标记问题时,值得一提的是,使用maybeControl.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 . keepFailuref 改编为 State s (Maybe d) Monad,然后 stateUnfoldr 展开为 State s [d],然后 runState 将其转回函数.

如果您只需要状态或列表,我们也可以使用 execStateevalState 代替 runState