Когда именно требуется подъем в монадных трансформаторах?
Я изучаю монадные трансформаторы, и я запутался, когда необходимо использовать лифт. Предположим, у меня есть следующий код (он не делает ничего интересного, просто самое простое, что я мог бы использовать для демонстрации).
foo :: Int -> State Int Int
foo x = do
(`runContT` pure) $ do
callCC $ \exit -> do
when (odd x) $ do
-- lift unnecessary
a <- get
put $ 2*a
when (x >= 5) $ do
-- lift unnecessary, but there is exit
a <- get
exit a
when (x < 0) $ do
-- lift necessary
a <- lift $ foo (x + 10)
lift $ put a
lift get
Таким образом, есть стек монады, где основной блок do имеет тип ContT Int (StateT Int Identity) Int
,
Теперь в третьем when
Для выполнения блока с рекурсией необходим лифт для компиляции программы. Во втором блоке подъем не нужен, но я почему-то предполагаю, что это из-за наличия exit
что так или иначе заставляет линию выше линии быть поднятой к ContT
, Но в первом блоке подъем не требуется. (Но если он явно добавлен, проблем тоже нет.) Это действительно меня смущает. Я чувствую все when
do блоки эквивалентны, и лифт должен требоваться везде или нигде. Но это, очевидно, не правда. Где ключевое отличие, которое делает лифт необходимым / не обязательным?
2 ответа
Здесь возникает путаница, потому что библиотека монадного преобразователя, которую вы используете, немного умна. В частности, тип get
а также put
явно не упоминает State
или же StateT
, Скорее, они вдоль линии
get :: MonadState s m => m s
put :: MonadState s m => s -> m ()
Поэтому, пока мы используем это в контексте с MonadState
реализации монады нет необходимости явного lift
s. Это касается всех случаев, когда вы используете get
/put
поскольку
instance MonadState s (StateT s m)
instance MonadState s m => ContT k m
оба держатся. Другими словами, разрешение класса типов будет автоматически обрабатывать все, что вам нужно. Это, в свою очередь, означает, что вы можете отказаться от lift
на get
/put
в конце вашей программы.
Это не может произойти с вашими рекурсивными вызовами, потому что его тип явно State Int Int
, Если вы обобщили это до MonadState Int m => m Int
Вы могли бы даже исключить этот последний лифт.
Я хотел бы дать альтернативный ответ, который является одновременно поверхностным и в то же время всем важным.
Вам нужно использовать lift
когда lift
заставляет вещи проверять, что иначе нет.
Да, это звучит поверхностно и, кажется, не имеет никакого глубокого смысла. Но это не совсем так. MonadTrans
класс для вещей, которые могут нейтральным образом поднять монадические действия в более широкий контекст. Законы классов предоставляют более четкие правила о том, что означает "нейтральный", если вам нужно техническое описание. Но результат в том, что lift
не делает ничего, кроме того, что необходимо для обеспечения совместимости данного действия с другим типом.
Итак - что же lift
делать? Это обеспечивает логику, необходимую для подъема монадического действия в больший тип. Когда вам нужно его использовать? Когда у вас есть монадическое действие, которое вам нужно поднять на больший тип. Когда у вас есть монадическое действие, которое вам нужно поднять на больший шрифт? Когда это то, что говорят вам типы.
Это ключевая часть использования Haskell. Вы можете модулировать свое понимание кода. Система типов отслеживает огромное количество бухгалтерии для вас. Положитесь на него, чтобы правильно вести бухгалтерию, так что вам нужно только держать логику в голове. Компилятор и система типов работают там как интеллектуальные усилители. Чем больше они заботятся, тем меньше нужно держать в голове при написании программного обеспечения.