结合State和List Monads

时间:2014-04-06 09:19:19

标签: 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中做同样的事情?

2 个答案:

答案 0 :(得分:6)

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])

答案 1 :(得分:0)

你的系列是二次的,可以更简单地生成:

series = fmap (\x -> (x, quot (x*x + x) 2 + 10)) [1..]

如果你想要一个递归关系,你可以写成:

series :: [(Int,Int)]
series = let
  series' x y = (x, x + y) : series' (x + 1) (x + y)
  in series' 1 10

没有令人信服的理由让我看到使用monad。