Aad*_*hah 2 monads haskell non-deterministic state-monad monad-transformers
考虑以下Haskell代码:
import Control.Monad.State
test :: Int -> [(Int, Int)]
test = runStateT $ do
a <- lift [1..10]
modify (+a)
return a
main = print . test $ 10
Run Code Online (Sandbox Code Playgroud)
这会产生以下输出:
[(1,11),(2,12),(3,13),(4,14),(5,15),(6,16),(7,17),(8,18),(9,19),(10,20)]
Run Code Online (Sandbox Code Playgroud)
但是我想生成以下输出:
[(1,11),(2,13),(3,16),(4,20),(5,25),(6,31),(7,38),(8,46),(9,55),(10,65)]
Run Code Online (Sandbox Code Playgroud)
使用像JavaScript这样的不纯语言很容易做到这一点:
function test(state) {
var result = [];
for (var a = 1; a <= 10; a++) {
result.push([a, state += a]);
}
return result;
}
Run Code Online (Sandbox Code Playgroud)
你如何在Haskell做同样的事情?
Haskell类型和JavaScript代码的逻辑不匹配:JS代码在状态中有两个值(Int和返回的列表).相比之下,StateT Int [] a该州并没有真正的名单; 相反,它多次运行有状态操作(每次运行初始状态不变)并在列表中收集所有结果.
换句话说,JS代码具有类型State (Int, [(Int, Int)]) [(Int, Int)].但这太过于字面翻译,我们可以编写更优雅的Haskell代码.
坚持Statemonad,我们可以返回一个列表mapM或forM:
test2 :: Int -> [(Int, Int)]
test2 = evalState $
forM [1..10] $ \a -> do
s <- get <* modify (+a)
return (a, s)
Run Code Online (Sandbox Code Playgroud)
一些lens魔术可以使它更类似于JS代码:
{-# LANGUAGE TupleSections #-}
import Control.Lens
test3 :: Int -> [(Int, Int)]
test3 = evalState $
forM [1..10] $ \a -> (a,) <$> (id <+= a)
Run Code Online (Sandbox Code Playgroud)
但是,我们可以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])
Run Code Online (Sandbox Code Playgroud)