Монада IO предотвращает короткое замыкание встроенной карты M?
Несколько озадачен следующим кодом. В не-игрушечной версии задачи я пытаюсь выполнить монадическое вычисление в монаде Result, значения которого могут быть построены только из IO. Похоже, магия за IO делает такие вычисления строгими, но я не могу понять, как именно это происходит.
Код:
data Result a = Result a | Failure deriving (Show)
instance Functor Result where
fmap f (Result a) = Result (f a)
fmap f Failure = Failure
instance Applicative Result where
pure = return
(<*>) = ap
instance Monad Result where
return = Result
Result a >>= f = f a
Failure >>= _ = Failure
compute :: Int -> Result Int
compute 3 = Failure
compute x = traceShow x $ Result x
compute2 :: Monad m => Int -> m (Result Int)
compute2 3 = return Failure
compute2 x = traceShow x $ return $ Result x
compute3 :: Monad m => Int -> m (Result Int)
compute3 = return . compute
main :: IO ()
main = do
let results = mapM compute [1..5]
print $ results
results2 <- mapM compute2 [1..5]
print $ sequence results2
results3 <- mapM compute3 [1..5]
print $ sequence results3
let results2' = runIdentity $ mapM compute2 [1..5]
print $ sequence results2'
Выход:
1
2
Failure
1
2
4
5
Failure
1
2
Failure
1
2
Failure
2 ответа
Хорошие тесты. Вот что происходит:
в
mapM compute
мы видим лень на работе, как обычно. Здесь нет ничего удивительного.в
mapM compute2
мы работаем внутри монады IO, чьяmapM
определение потребует весь список: в отличиеResult
который пропускает хвост списка, как толькоFailure
найден,IO
всегда будет сканировать весь список. Обратите внимание на код:compute2 x = traceShow x $ return $ Result x
Таким образом, вышеперечисленное напечатает сообщение отладки, как только будет получен доступ к каждому элементу списка действий ввода-вывода. Все есть, поэтому мы печатаем все.
в
mapM compute3
мы сейчас используем, примерно:compute3 x = return $ traceShow x $ Result x
Теперь, так как
return
в IO ленив, это не вызоветtraceShow
при возврате действия IO. Так когдаmapM compute3
запущено, сообщения не видно. Вместо этого мы видим сообщения только тогда, когдаsequence results3
это бег, который заставляетResult
- не все из них, но только столько, сколько нужно.финал
Identity
Пример тоже довольно хитрый. Обратите внимание:> newtype Id1 a = Id1 a > data Id2 a = Id2 a > Id1 (trace "hey!" True) `seq` 42 hey! 42 > Id2 (trace "hey!" True) `seq` 42 42
при использовании
newtype
, во время выполнения не включается бокс / распаковка (подъем АКА), поэтому принудительноеId1 x
причины причинx
быть вынужденным. Сdata
Типы этого не происходит: значение обернуто в поле (например,Id2 undefined
не эквивалентноundefined
).В вашем примере вы добавляете
Identity
конструктор, но это изnewtype Identity
!! Итак, при звонкеreturn $ traceShow x $ Result x
return
здесь ничего не заворачивать, аtraceShow
сразу же срабатывает, как толькоmapM
это запустить.
Ваш Result
тип, по-видимому, практически идентичен Maybe
, с
Result <-> Just
Failure <-> Nothing
Ради моего бедного мозга я буду придерживаться Maybe
Терминология в остальной части этого ответа.
чи объяснил почему IO (Maybe a)
не закорачивает, как вы ожидали. Но есть тип, который вы можете использовать для такого рода вещей! По сути, это тот же тип, но с другим Monad
пример. Вы можете найти это в Control.Monad.Trans.Maybe
, Это выглядит примерно так:
newtype MaybeT m a = MaybeT
{ runMaybeT :: m (Maybe a) }
Как видите, это просто newtype
обертка вокруг m (Maybe a)
, Но это Monad
экземпляр сильно отличается
instance Monad m => Monad (MaybeT m) where
return a = MaybeT $ return (Just a)
m >>= f = MaybeT $ do
mres <- runMaybeT m
case mres of
Nothing -> return Nothing
Just a -> runMaybeT (f a)
То есть, m >>= f
управляет m
вычисления в базовой монаде, получая Maybe
что-то или другое. Если это получится Nothing
просто останавливается, возвращаясь Nothing
, Если он что-то получает, он передает это f
и запускает результат. Вы также можете включить любой m
действие в "успешной" MaybeT m
действие с использованием lift
от Control.Monad.Trans.Class
:
class MonadTrans t where
lift :: Monad m => m a -> t m a
instance MonadTrans MaybeT where
lift m = MaybeT $ Just <$> m
Вы также можете использовать этот класс, определенный где-то как Control.Monad.IO.Class
, что часто бывает понятнее и может быть гораздо удобнее:
class MonadIO m where
liftIO :: IO a -> m a
instance MonadIO IO where
liftIO m = m
instance MonadIO m => MonadIO (MaybeT m) where
liftIO m = lift (liftIO m)