【问题标题】:How to do stateful list operations in haskell如何在haskell中进行有状态列表操作
【发布时间】:2014-01-02 07:59:41
【问题描述】:

我需要一个遍历列表并生成新列表的操作,其中新列表元素依赖于之前看到的所有元素。为此,我想在迭代之间传递一个累加器/状态。

以元组列表为例,其中元组的组件可以是“未定义的”。未定义的值应假定列表中较早的相同组件的最新值,如果有的话。所以在任何阶段我都会有一个已定义组件的状态,我需要将其传递给下一次迭代。

所以有了[l] 类型的列表和a 类型的累加器/状态,就会有一个类型的函数

f :: a -> l -> (a,l)

即它吐出一个新的列表元素和一个新的累加器。

是否有一个函数可以简单地将 f 应用于列表?我查看了折叠、扫描和展开,但它们似乎都不起作用。

编辑:虽然 state monad 看起来很有希望,但我只能看到如何获得最终状态,但看不到如何获得新的列表元素。

【问题讨论】:

  • 请注意,如果您想区分有值没有值,您应该使用Maybe 而不是比undefined.
  • 当然——“未定义”是随便写的。真正的数据类型是三个可能值的变化指示,其中之一是“无变化”。
  • 这只是一个完全正常的fold,或者我不明白。
  • 折叠将列表折叠成一个“值”,但我也想要新的列表元素。嗯 - 我可能会让状态累积新的列表元素(而不仅仅是最新的“值”)。这就是你的建议吗?

标签: list haskell iteration


【解决方案1】:

您可以使用一些标准功能来满足您的要求。

听起来很像你想要mapAccum,所以你只需要导入Data.List 并决定你要累积的方式。 (我怀疑你想要mapAccumL :: (acc -> x -> (acc, y)) -> acc -> [x] -> (acc, [y])。)

mapAccumL

import Data.List

data Instruction = NoChange | Reset | MoveBy Int

tell :: Int -> Instruction -> (Int,String) -- toy accumulating function
tell n NoChange = (n,"")
tell n Reset = (0,"Reset to zero")
tell n (MoveBy i) = (n+i,"Add "++show i++" to get "++ show (n+i))

这会给

ghci> mapAccumL tell 10 [MoveBy 5, MoveBy 3, NoChange, Reset, MoveBy 7]
(7,["Add 5 to get 15","Add 3 to get 18","","Reset to zero","Add 7 to get 7"])

扫描L

但也许您不需要使用mapAccum 的全部功能,因为有时累加器就是您想要的新列表,所以scanl :: (a -> b -> a) -> a -> [b] -> [a] 可以解决问题

act :: Int -> Instruction -> Int
act n NoChange = n
act n Reset = 0
act n (MoveBy i) = n+i

像这样:

ghci> scanl act 10 [MoveBy 5, MoveBy 3, NoChange, Reset, MoveBy 7]
[10,15,18,18,0,7]

mapAccum 的定义

不管怎样,mapAccumLmapAccumRData.List 中是这样描述的:

mapAccumL :: (acc -> x -> (acc, y)) -> acc -> [x] -> (acc, [y])
mapAccumL _ state []        =  (state, [])
mapAccumL f state (x:xs)    =  (finalstate,y:ys)
                           where (nextstate, y ) = f state x
                                 (finalstate,ys) = mapAccumL f nextstate xs

mapAccumL 函数的行为类似于mapfoldl 的组合;它将一个函数应用于列表的每个元素,从左到右传递一个累加参数,并将这个累加器的最终值与新列表一起返回。

mapAccumR :: (acc -> x -> (acc, y)) -> acc -> [x] -> (acc, [y])
mapAccumR _ state []        =  (state, [])
mapAccumR f state (x:xs)    =  (finalstate, y:ys)
                           where (finalstate,y ) = f nextstate x
                                 (nextstate, ys) = mapAccumR f state xs

mapAccumR 函数的行为类似于mapfoldr 的组合;它将一个函数应用于列表的每个元素,从右到左传递一个累加参数,并将该累加器的最终值与新列表一起返回。

【讨论】:

    【解决方案2】:

    您希望 mapMState monad 一起使用,其中您的累加器 a 将是 State。首先,要了解为什么需要 State,只需获取类型签名并翻转参数和结果的顺序即可:

    import Data.Tuple
    
    f :: a -> l -> (a, l)
    
    uncurry f :: (a, l) -> (a, l)
    
    swap . uncurry f . swap :: (l, a) -> (l, a)
    
    curry (swap . uncurry f . swap) :: l -> a -> (l, a)
    

    或者您可以定义f 以使参数和结果以正确的顺序排列,无论您喜欢哪个。我将调用这个交换函数f'

    f' :: l -> a -> (l, a)
    

    现在让我们在 f' 类型签名的右半边添加一组额外的括号:

    f' :: l -> (a -> (l, a))
    

    括号中的部分是State 计算,其中状态为a,结果为l。所以我将继续使用来自Control.Monad.Trans.Statestate 函数将其转换为State 类型:

    state :: (a -> (l, a)) -> State a l
    

    所以转换后的f' 看起来像这样:

    f'' :: l -> State a l
    f'' = state . f'
    

    但是,最终你真正想要的功能是某种类型的东西:

    final :: [l] -> a -> ([l], a)
    
    -- which is really just:
    state . final :: [l] -> State a [l]
    

    这意味着我需要一些函数来接收l -> State a l 并将其转换为[l] -> State a [l]。这正是mapM 所做的,除了mapM 适用于任何Monad,而不仅仅是State

    mapM :: (Monad m) => (a -> m b) -> ([a] -> m [b])
    

    请注意,如果我们将m 替换为State a,并将ab 设置为l,那么它的类型完全正确:

    mapM :: (l -> State a l) -> ([l] -> State a [l])
    
    f''' :: [l] -> State a [l]
    f''' = mapM f''
    

    现在我们可以使用 runState 解开 State 以获取适当类型的列表线程函数:

    final :: [l] -> a -> ([l], a)
    final = runState . f'''
    

    因此,如果我们将所有这些步骤合二为一,我们会得到:

    final = runState . mapM (state . f')
    

    ... 其中f' 是您为交换参数和结果的顺序而编写的函数。如果您选择不修改原始函数,则解决方案会稍微冗长:

    final = runState . mapM (state . uncurry (swap . curry f . swap))
    

    【讨论】:

      【解决方案3】:

      如果没有您实际想要达到的具体目标,获得答案有点困难。但似乎如果您的f 具有以下类型:

      f :: (a, [l]) -> l -> (a,l)
      

      然后你可以定义一个函数,f':

      f' :: (a, [l]) -> l -> (a,l)
      f' acc@(y, xs) x = (z, x':xs)
          where
              (z, x') = f acc
      

      然后可以在折叠中使用。

      foldr f' (e, []) xs
      

      f 的新签名允许它访问列表中所有前面的元素,f' 将来自对 f 的调用中的新元素添加到列表中。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-07-20
        • 1970-01-01
        • 2019-11-22
        相关资源
        最近更新 更多