使用 monad 计算对列表上的累积和(haskell)

use*_*917 0 monads haskell list

我有一个对结构列表[("oct",1),("nov",1),("dec",1)]

我想计算对内的总和:[("oct",1),("nov",2),("dec",3)]。我认为这是单子实现的一个很好的例子,但无法弄清楚如何保存容器。

我尝试在列表上创建函数(我已经知道了scanl1,只是在这里展示努力:)

csm (x:[]) = [x]
csm (x:y:xs) = x : csm ((x + y) : xs)
Run Code Online (Sandbox Code Playgroud)

然后尝试类似的事情:

sumOnPairs l = do
  pair <- l
  return (csm (snd pair))
Run Code Online (Sandbox Code Playgroud)

我的解决方案不起作用,请为我指出正确的方向

che*_*ner 5

列表 monad 建模了非确定性:对列表中的每个元素执行相同的操作,然后将结果收集到新列表中。

对于您想要的顺序遍历类型(对元素执行某些操作,然后使用结果对下一个元素执行某些操作等),您可以使用 monadState执行类似的操作

import Control.Monad.Trans.State
import Data.Bifunctor

type Pair = (String, Int)

foo :: Pair -> State Pair Pair
foo (month, y) = do
   -- bimap f g (x,y) == (f x, g y)
   -- The new month replaces the old month,
   -- and y is added to the sum.
   modify (bimap (const month) (+y))
   -- Return a snapshot of the state
   get
   

sumOnPairs :: [Pair] -> [Pair]
sumOnPairs = flip evalState ("", 0) . traverse foo
Run Code Online (Sandbox Code Playgroud)

在每一步中,新状态是当前月份以及旧状态数字和当前数字的总和。traverse在遍历原始列表时将这些状态累积在列表中。

> sumOnPairs [("oct",1),("nov",1),("dec",1)]
[("oct",1),("nov",2),("dec",3)]
Run Code Online (Sandbox Code Playgroud)

您还可以仅保留状态中的总和,而不是刚刚更换的月份总和。

foo' :: Pair -> State Int Pair
foo' x@(_, count) = do
   modify (+ count)
   fmap (<$ x) get

sumOnPairs' :: [Pair] -> [Pair]
sumOnPairs' = flip evalState 0 . traverse bar
Run Code Online (Sandbox Code Playgroud)

在这种情况下,状态中仅保留当前总和;新的对是通过使用<$运算符生成的,该Functor运算符的实例(,) String用状态中的总和替换当前对中的数字

> 6 <$ ("foo", 3)
("foo", 6)
Run Code Online (Sandbox Code Playgroud)

我认为如果您选择这条路线,使用Data.Functor.($>)( 的翻转版本<$)可能更具可读性。

foo' x@(_, count) = do
   modify (+ count)
   fmap (x $>) get
Run Code Online (Sandbox Code Playgroud)

从视觉上看,它更类似于您不需要映射get:时可以编写的内容x $> y == (fst x, y)