Типы в вычислениях MaybeT

Работая над вычислением ввода-вывода, я получил лестницу из case mbValue of …s и понял, что для упрощения кода мне следует использовать монаду Maybe. Так как это внутри вычисления IO и мне нужно получить IO значений, я использовал преобразователь монады MaybeT, чтобы я мог lift IO вычислить в Maybe.

Я всегда думал о том, что значения «лишаются» их Maybeness после values <- mbValue внутри Maybe вычисления, но здесь это оказывается слишком простой эвристикой.

Как показано ниже, при использовании значения Maybe a в качестве a (здесь путем передачи его в read) проверка типа не выполняется:

import Control.Monad.Trans (lift)
import Control.Monad.Trans.Maybe (runMaybeT)

lol :: IO (Maybe Int)
lol = return (Just 3)

lal :: IO (Maybe String)
lal = return (Just "8")

foo :: IO (Maybe Bool)
foo = do
  b <- runMaybeT $ do
    x <- lift lol
    y <- lift lal
    return (x < (read y)) 
  return b       ^-- Couldn't match type ‘Maybe String’ with ‘String’

main = foo >>= print

Если я помещу типизированное отверстие для return (x < (read y)), я увижу, что он ожидает Bool, что имеет смысл, но также и то, что текущие привязки включают

||   y :: Data.Maybe.Maybe GHC.Base.String
||     (bound at /private/tmp/test.hs:14:5)
||   x :: Data.Maybe.Maybe GHC.Types.Int
||     (bound at /private/tmp/test.hs:13:5)

То есть y это Maybe String. Это, конечно, объясняет ошибку, но я в замешательстве. Где я неправильно понимаю, и как я могу исправить эту ошибку?


person beta    schedule 13.02.2015    source источник


Ответы (2)


Вкратце: замените lift конструктором MaybeT.

Обратите внимание, что

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

и

lift :: (MonadTrans t, Monad m) => m a -> t m a

Ваше использование lift в

x <- lift lol

находится в типе

lift :: IO (Maybe Int) -> MaybeT IO (Maybe Int)

Вот почему x снова станет Maybe Int. lift добавляет новый слой MaybeT, который не зависит от уже имеющегося экземпляра Maybe.

Но

MaybeT :: m (Maybe a) -> MaybeT m a

вместо этого как в

x <- MaybeT lol

будет использоваться в типе

MaybeT :: IO (Maybe a) -> MaybeT IO a

и поступай правильно.

person kosmikus    schedule 13.02.2015

При специализации на MaybeT, lift :: Monad m => m a -> MaybeT m a. Поскольку lol :: IO (Maybe Int), m равно IO, а a равно Maybe Int, следовательно, lift lol :: MaybeT IO (Maybe Int).

IO (Maybe a) — это просто значение, содержащееся в обертке MaybeT IO a newtype, поэтому нет необходимости поднимать его; вместо этого используйте конструктор MaybeT, например, как в MaybeT lol.

Но это не то, как люди склонны использовать преобразователи монад. Вместо этого просто используйте значения MaybeT и поднимайте по мере необходимости:

import Control.Monad
import Control.Monad.Trans (lift)
import Control.Monad.Trans.Maybe (runMaybeT, MaybeT)

lol :: MaybeT IO Int
lol = return 3

lal :: MaybeT IO String
lal = return "8"

foo :: IO (Maybe Bool)
foo = 
  runMaybeT $ do
    x <- lol
    y <- lal

    _ <- lift getLine -- lift (IO String) to MaybeT IO String
    _ <- return 100   -- lift any pure value    
    _ <- mzero        -- use the MonadPlus instance to get a lifted Nothing.

    return (x < (read y)) 

main = foo >>= print
person András Kovács    schedule 13.02.2015