Когда именно требуется подъем в монадных трансформаторах?

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

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 реализации монады нет необходимости явного lifts. Это касается всех случаев, когда вы используете 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. Вы можете модулировать свое понимание кода. Система типов отслеживает огромное количество бухгалтерии для вас. Положитесь на него, чтобы правильно вести бухгалтерию, так что вам нужно только держать логику в голове. Компилятор и система типов работают там как интеллектуальные усилители. Чем больше они заботятся, тем меньше нужно держать в голове при написании программного обеспечения.

Другие вопросы по тегам