如何在haskell中执行有状态列表操作

时间:2013-12-14 16:03:13

标签: list haskell iteration

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

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

因此,使用类型[l]的列表和类型为a的累加器/状态,将会有类型

的函数
f :: a -> l -> (a,l)

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

是否有允许简单地将f应用于列表的功能?我看着折叠,扫描和展开,但他们似乎都没有做到这一点。

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

3 个答案:

答案 0 :(得分:9)

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

这听起来非常像你想要的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"])

scanL

但也许你不需要使用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

的定义

无论如何,Data.List中描述了mapAccumLmapAccumR的方式:

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的组合;它将一个函数应用于列表的每个元素,从右向左传递累加参数,并将该累加器的最终值与新列表一起返回。

答案 1 :(得分:6)

您希望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。我将继续使用State中的state函数将其转换为Control.Monad.Trans.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''

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

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

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

final = runState . mapM (state . f')

...其中f'是您编写的函数,用于交换参数和结果的顺序。如果您选择不修改原始功能,那么解决方案稍微冗长一点:

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

答案 2 :(得分:1)

如果没有您实际想要实现的细节,找到答案有点困难。但似乎如果你的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的调用中的新元素添加到列表中。