2015-07-25 3 views
6

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

Мой вопрос: как сделать то же самое в версии без монада трансформаторов?

ответ

2

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

+2

Я это понимаю. Я не ищу версию без трансформатора монады для эффективности, я просто хочу посмотреть, как это будет выглядеть, и научиться чему-то, сравнив эти два, надеюсь, лучше понять необходимость в монадных трансформаторах. – ByteEater

+0

Кроме того, выполнение вычислений накопленных состояний в каждой строке является тем, что я рассматривал и отклонял именно по этой причине: он не кажется правильным способом использования монадов, простой «Int» был бы лучшим выбором. Более того, с инкрементацией это не имеет никакого значения, но это было бы неправильно концептуально, так как вычисление 'State' строится путем добавления действий' modify (+1) ', поэтому, если бы у меня было, например, 'modify (+ length l)', это не сработало бы так, как должно. – ByteEater

+0

@ByteEater, способ сделать это без трансформатора монады - просто передать «Int» вручную (раздражает) или использовать «IORef» (ограниченный «IO» -подобными вещами и потенциально неэффективным, но хорошо, если коробка неизбежна или обновления встречаются редко). Я не знаю, что еще вы ищете. – dfeuer

1

Возможно, это то, что вы ищете?

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 хвост рекурсивной, создания вашего состояния вычислений, которые затем можно оценить на каждом шагу.

EDIT

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

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) 
+0

Действительно, это позволяет использовать текущее значение, вычисляемое в 'State Int', за счет перерасчета его на линейное число раз (хотя с ссылочно прозрачным языком и интеллектуальным компилятором эту стоимость можно было бы избежать). Я считал это, но не убеждался, что это канонический эквивалент с монадами «State» и «IO» моей второй программы, в которой используется трансформатор. – ByteEater

+0

Я сомневаюсь, что это уже не «каноническое», чем моя первая попытка, но мое второе редактирование может привязать вычисление состояния к постоянному размеру, отбросив предыдущее вычисление состояния и немедленно установить текущее состояние в результат последнего плюс 1. Лучше? – Matt

+0

Лучше, как вы описали. Но он по-прежнему не очень легко сравнивается с версией с использованием StateT, чтобы четко видеть, какое улучшение кода (рефакторинг в превосходной форме) может быть фактически достигнуто с помощью монадного трансформатора. – ByteEater

10

Проблема у вас есть то, что рука-разворачивания 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) 
Смежные вопросы