Используйте две монады без трансформатора

Чтобы понять, как использовать монадные преобразователи, я написал следующий код без такового. Он читает стандартный ввод строка за строкой и отображает каждую строку в обратном порядке, пока не встретится пустая строка. Он также считает строки, используя 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

Я хотел добавить номер текущей строки перед каждой строкой. Я смог сделать это с 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

У меня вопрос: как сделать то же самое в версии без монадных трансформаторов?

3 ответа

Проблема в том, что вы раскручиваете StateT s IO a является s -> 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)

Вам просто нужно запустить вычисление накопленного состояния в каждой строке. Это время O(n²), но так как ваша первая программа уже использует пространство O(n), это не так уж страшно. Конечно, StateT подход превосходит почти во всех отношениях! Если вы действительно хотите сделать это "вручную" и не платить цену эффективности, просто управляйте государством вручную, а не строите трансформатор состояния вообще. Вы действительно не получаете никакой выгоды, используя State вместо Int в первой программе.

Может быть, это то, что вы ищете?

main = print =<< fmap (`evalState` 0) (go get) where
  go :: State Int Int -> IO (State Int Int)
  go st = do
     l <- getLine 
     if null l
     then return (st >>= \_ -> get)
     else do
          let ln = evalState st 0
          putStrLn(show ln ++ ' ' : reverse l)
          go (st >>= \_ -> modify (+1) >>= \_ ->  get) 

Идея здесь состоит в том, чтобы сделать go хвост рекурсивный, построение вычислений вашего состояния, которые вы затем сможете оценить на каждом шаге.

РЕДАКТИРОВАТЬ

Эта версия будет привязывать размер вычисления состояния к постоянному размеру, хотя при ленивой оценке, когда предыдущее вычисление состояния является принудительным, мы должны иметь возможность использовать его повторно, не переоценивая его, поэтому я предполагаю, что это по существу тот же самый...

main = print =<< fmap (`evalState` 0) (go get) where
  go :: State Int Int -> IO (State Int Int)
  go st = do
     l <- getLine 
     if null l
     then return st
     else do
          let ln = evalState st 0
          putStrLn(show ln ++ ' ' : reverse l)
          go (modify (\s -> s+ln+1) >>= \_ ->  get) 
Другие вопросы по тегам