Как использовать Do нотации как с Maybe, так и с IO - PullRequest
2 голосов
/ 11 апреля 2020

Я пытаюсь получить хорошее управление do notation в Haskell.

Я мог бы использовать его с Maybe, а затем распечатать результат. Например:

maybeAdd :: Maybe Integer
maybeAdd = do one <- maybe1
              two <- maybe2
              three <- maybe3
              return (one + two + three)

main :: IO ()
main = putStr (show $ fromMaybe 0 maybeAdd)

Но вместо отдельной функции я пытаюсь использовать нотацию do с функцией Maybe внутри основной функции. Но мне не повезло. Различные попытки, которые я попробовал, включают:

main :: IO ()
main = do one <- maybe1
          two <- maybe2
          three <- maybe3
          putStr (show $ fromMaybe 0 $ return (one + two + three))
main :: IO ()
main = do one <- maybe1
          two <- maybe2
          three <- maybe3
          putStr (show $ fromMaybe 0 $ Just (one + two + three))
main :: IO ()
main = do one <- maybe1
          two <- maybe2
          three <- maybe3
          putStr (show $ (one + two + three))

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

Как мне достичь вышеуказанного? И, может быть, может быть объяснение того, почему подходы, которые я попробовал, тоже были неправильными?

Ответы [ 2 ]

6 голосов
/ 11 апреля 2020

Каждый do блок должен работать в пределах одной монады. Если вы хотите использовать несколько монад, вы можете использовать несколько do блоков. Попытка адаптировать свой код:

main :: IO ()
main = do -- IO block
   let x = do -- Maybe block
          one <- maybe1
          two <- maybe2
          three <- maybe3
          return (one + two + three)
   putStr (show $ fromMaybe 0 x)

Вы можете даже использовать

main = do -- IO block
   putStr $ show $ fromMaybe 0 $ do -- Maybe block
      one <- maybe1
      two <- maybe2
      three <- maybe3
      return (one + two + three)
   -- other IO actions here

, но в некоторых случаях он может быть менее читаемым.

0 голосов
/ 11 апреля 2020

В этом конкретном случае пригодится монадный трансформатор MaybeT. MaybeT монадный трансформатор - это просто тип, определенный как:

newtype MaybeT m a = MaybeT {runMaybeT :: m (Maybe a)}

На самом деле трансформаторы, такие как MaybeT, StateT et c, легко доступны в Control.Monad.Trans.Maybe, Control.Monad.Trans.State .. В целях иллюстрации это может быть что-то вроде экземпляра Monad, как показано ниже;

instance Monad m => Monad (MaybeT m) where
  return  = MaybeT . return . Just
  x >>= f = MaybeT $ runMaybeT x >>= g
            where
            g Nothing  = return Nothing
            g (Just x) = runMaybeT $ f x

, так как вы заметите, что функция monadi c f принимает значение, которое находится в монаде Maybe, которая сама находится в другой монаде (IO в нашем случае). Функция f делает свое дело и упаковывает результат обратно в MaybeT m a.

Также есть класс MonadTrans, где вы можете иметь некоторые общие функции, которые используются типами трансформаторов. Одним из них является lift, который используется для поднятия значения в преобразователь в соответствии с определением этого конкретного экземпляра. Для MaybeT он должен выглядеть следующим образом:

instance MonadTrans MaybeT where
    lift = MaybeT . (liftM Just)

Позволяет выполнять вашу задачу с помощью монадных трансформаторов.

addInts :: MaybeT IO ()
addInts = do
          lift $ putStrLn "Enter two integers.."
          i <- lift getLine
          guard $ test i
          j <- lift getLine
          guard $ test j
          lift . print $ (read i :: Int) + (read j :: Int)
          where
          test = and . (map isDigit)

Поэтому при вызове типа

λ> runMaybeT addInts
Enter two integers..
1453
1571
3024
Just ()

Подвох Так как преобразователь монад также является членом класса типов Monad, их можно вкладывать неопределенно долго и все же делать что-то под единичной нотацией do.

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

Пользуясь возможностью присутствовать на сессии редактирования, я хотел бы добавить лучший код, так как я думаю, что Char на основе test ing может быть не лучшей идеей, так как она не будет принимать во внимание отрицательные Int s. Итак, давайте попробуем использовать readMaybe из пакета Text.Read, пока мы работаем с типом Maybe.

import Control.Monad.Trans.Maybe
import Control.Monad.Trans.Class (lift)
import Text.Read (readMaybe)

addInts :: MaybeT IO ()
addInts = do
          lift $ putStrLn "Enter two integers.."
          i <- lift getLine
          MaybeT $ return (readMaybe i :: Maybe Int)
          j <- lift getLine
          MaybeT $ return (readMaybe j :: Maybe Int)
          lift . print $ (read i :: Int) + (read j :: Int)

Полагаю, теперь он работает лучше ...

λ> runMaybeT addInts
Enter two integers..
-400
500
100
Just ()

λ> runMaybeT addInts
Enter two integers..
Not an Integer
Nothing
...