haskell - 结合状态和列表单子(monad)

标签 haskell monads monad-transformers state-monad non-deterministic

考虑以下 Haskell 代码:

import Control.Monad.State

test :: Int -> [(Int, Int)]
test = runStateT $ do
    a <- lift [1..10]
    modify (+a)
    return a

main = print . test $ 10

这会产生以下输出:

[(1,11),(2,12),(3,13),(4,14),(5,15),(6,16),(7,17),(8,18),(9,19),(10,20)]

但是我想生成以下输出:

[(1,11),(2,13),(3,16),(4,20),(5,25),(6,31),(7,38),(8,46),(9,55),(10,65)]

这在像 JavaScript 这样的不纯语言中很容易做到:

function test(state) {
    var result = [];

    for (var a = 1; a <= 10; a++) {
        result.push([a, state += a]);
    }

    return result;
}

如何在 Haskell 中做同样的事情?

最佳答案

Haskell 类型和您的 JavaScript 代码的逻辑不匹配:JS 代码在状态中有两个值(Int 和返回列表)。相比之下,StateT Int [] a 在状态中并没有真正的列表;相反,它会多次运行有状态操作(每次运行的初始状态都不变)并将所有结果收集到一个列表中。

换句话说,JS代码的类型是State (Int, [(Int, Int)]) [(Int, Int)]。但这太直译了,我们可以编写更优雅的 Haskell 代码。

坚持使用 State monad,我们可以返回一个带有 mapMforM 的列表:

test2 :: Int -> [(Int, Int)]
test2 = evalState $ 
    forM [1..10] $ \a -> do
        s <- get <* modify (+a)
        return (a, s) 

一些lens魔法可以让它更像JS代码:

{-# LANGUAGE TupleSections #-}

import Control.Lens

test3 :: Int -> [(Int, Int)]
test3 = evalState $ 
    forM [1..10] $ \a -> (a,) <$> (id <+= a)

但是,我们可以完全取消 State,我认为这是最好的方法:

import Control.Monad (ap)

test4 :: Int -> [(Int, Int)]
test4 n = ap zip (tail . scanl (+) n) [1..10]
-- or without ap : zip [1..10] (drop 1 $ scanl (+) n [1..10])

关于haskell - 结合状态和列表单子(monad),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22892095/

相关文章:

haskell - 在 Haskell 上,有一个标准函数可以在树上执行 "scan"吗?

haskell - 箭头的创造性使用

haskell - 在filterM中,为什么 `return (if b then x:ys else ys)`在创建完所有列表后被评估一次?

mongodb - Scala 中的 Monad 转换器用于理解处理选项并收集错误消息

haskell - 如何为具有幻像类型变量的新类型定义 MonadUnliftIO 实例?

haskell - 结合 StateT 和 State monad

haskell - 参数化 Maybe monad

list - Haskell:有没有一种惯用的方式将列表中的每个项目插入到它自己的列表中?

Scalaz 迭代器 : "Lifting" `EnumeratorT` to match `IterateeT` for a "bigger" monad

haskell - ArrowLoop 是如何工作的?另外,mfix?