使用两个没有变压器的单子

Byt*_*ter 6 haskell state-monad monad-transformers

为了理解如何使用monad变换器,我编写了以下代码而没有一个.它逐行读取标准输入并显示每条反转线,直到遇到空行.它还使用行计算行数,State最后显示总数.

import Control.Monad.State

main = print =<< fmap (`evalState` 0) go where
    go :: IO (State Int Int)
    go = do
        l <- getLine
        if null l
        then return get
        else do
            putStrLn (reverse l)
            -- another possibility: fmap (modify (+1) >>) go
            rest <- go
            return $ do
                modify (+1)
                rest
Run Code Online (Sandbox Code Playgroud)

我想在每行之前添加当前行号.我能够做到StateT:

import Control.Monad.State

main = print =<< evalStateT go 0 where
    go :: StateT Int IO Int
    go = do
        l <- lift getLine
        if null l
        then get
        else do
            n <- get
            lift (putStrLn (show n ++ ' ' : reverse l))
            modify (+1)
            go
Run Code Online (Sandbox Code Playgroud)

我的问题是:如何在没有monad变换器的版本中做同样的事情?

Dan*_*ner 10

你遇到的问题是,手动展开StateT s IO as -> IO (s, a),不是IO (s -> (s, a))!一旦掌握了这些洞察力,就可以很容易地看到如何做到这一点:

go :: Int -> IO (Int, Int)
go s = do
    l <- getLine
    if null l
    then return (s, s)
    else do
        putStrLn (show s ++ ' ' : reverse l)
        go (s+1)
Run Code Online (Sandbox Code Playgroud)


dfe*_*uer 2

您只需要在每一行上运行累积状态计算。这是 O(n\xc2\xb2) 时间,但由于您的第一个程序已经使用 O(n) 空间,所以这并不算太糟糕。当然,这种StateT方法几乎在各个方面都是优越的!如果你真的想“手动”完成而不付出效率代价,那么只需手动管理状态,而不是根本构建状态转换器。State使用而不是确实没有得到任何好处Int在第一个程序中

\n

  • 我意识到这一点。我并不是为了提高效率而寻找没有 monad 变压器的版本,我只是想看看它会是什么样子,并通过比较两者来学习一些东西,希望能够更好地理解 monad 变压器的需求。 (2认同)