Есть ли смысл сделать IO экземпляром MonadCont?

Очевидно MonadConts более ограничен и дает больше мощности, чем обычный MonadS, благодаря его callCC, Это означает меньше случаев этого, и больше вы можете сделать с ним.

Когда посмотрите на определенные случаи MonadContПохоже, что все перечисленное там требует либо Cont или же ContT или уже существующий MonadCont пример. Это означает, что мы должны начать с некоторых Cont или же ContT и в частности не может повернуться IO в MonadCont,

Тем не менее, я считаю, что имеет смысл использовать callCC в IO контекст, поэтому мы можем упростить следующее (отрегулировано на официальной странице Hackage callCC пример):

whatsYourName :: IO ()
whatsYourName = do
    name <- getLine
    let response = flip runCont id $ do
        callCC $ \exit -> do
            when (null name) (exit "You forgot to tell me your name!")
            return $ "Welcome, " ++ name ++ "!"            
    print response

в

whatsYourName' :: IO ()
whatsYourName' = do
    name <- getLine
    response <- callCC $ \exit -> do
            when (null name) (exit "You forgot to tell me your name!")
            return $ "Welcome, " ++ name ++ "!"            
    print response

использовать callCC в блоке до более чистым способом.

Конечно, чтобы сделать IO экземпляр MonadCont мы должны иметь немного магии, так как callCC за IO означает "вызов данной функции с будущими вычислениями, определяет, что будет дальше в реальном мире", так что только интерпретатор или компилятор могут фактически знать, что это означает. С другой стороны, я не видел ни одной теоретической причины, по которой это можно было бы импортировать, поскольку в Scheme это уже давно, и создание такого экземпляра вообще не требует языковых изменений.

Возможная проблема

Один из факторов, о котором я могу думать, это то, что семантика callCC конфликт с надлежащей гарантией очистки. Многие языки обеспечивают контроль "try...finally" для правильной очистки, и деструктор C++ также гарантирует это. Я не уверен, что это на Хаскеле, но если callCC доступно для IO затем можно использовать его, чтобы убежать от любого IO включенный контекст, который требует очистки, поэтому предоставление sush гарантии станет невозможным, как вы можете видеть, что происходит в Ruby.

Обсуждение мнений

Ответ от @jozefg очень хороший. Я просто хочу записать свое мнение здесь.

  1. Правда, что MonadCont приходят из MTL. Но это не означает, что GHC или другой компилятор не может определить unsafeCallCC и определить экземпляр, если MonadCont с правильным определением находится в области действия модуля компиляции и -XIOMonadCont быть установленным

  2. Я уже говорил о безопасности исключений, и в этом трудно убедиться. Тем не менее, Haskell уже есть unsafePerformIOчто в основном еще более небезопасно, чем unsafeCallCC, по-моему.

  3. Конечно callCC в большинстве случаев слишком мощный и его следует избегать, когда это возможно. Тем не менее, по моему мнению, стиль передачи продолжения может использоваться для явной ленивой оценки, что может помочь лучше понять программу и, таким образом, облегчить поиск возможных оптимизаций. Конечно, CPS не MonadCont, но это естественный шаг - использовать его и преобразовывать глубоко вложенные внутренние функции в нотации do.

4 ответа

Решение

Я бы сказал, что это плохая идея. Прежде всего, MonadCont находится в MTL. GHC ничего не знает об этом, это будет означать, что компилятор будет зависеть от сторонней библиотеки, ick.

Во-вторых, callCC непопулярен даже среди некоторых высокопрофессиональных шемеров, в основном потому, что он делает рассуждения о коде болезненными Во многом так же, как goto трудно рассуждать о. Особенно, когда в Хаскеле, где бы нам пришлось беспокоиться

  1. Это исключение безопасно? (Что уже довольно сложно)
  2. Это callCC безопасный?

Наконец, нам это даже не нужно. Если вы хотите использовать продолжения и IO, используйте ContT IOЭто так же мощно. Тем не менее, я почти гарантирую, что он может быть заменен чем-то менее мощным, как monad-prompt, Продолжение - кувалда, 9 из 10 раз, callCC слишком мощный и может быть более приятно сформулирован, используя комбинацию функций более высокого порядка и лени.

Как пример, одно из прототипов использования callCC это реализовать что-то вроде исключений, но в Haskell мы можем просто использовать монады:) (которые полагаются на функции более высокого порядка и лень).

По сути, то, что вы предлагаете, увеличивает сложность, означает объединение MTL с базой и целый ряд других неприятностей, которых нужно просто избегать. liftIO,

RE Edits

  1. Конечно, вы можете сделать это, но тогда это не пример MonadCont конечно:)
  2. Это немного по-другому, unsafePerformIO предназначен для использования таким образом, чтобы побочные эффекты не были видны никому другому, поскольку у вас нет никаких гарантий, как и когда все будет выполнено.

    Если бы это было в случае с callCCIOтогда вы могли бы просто использовать Cont!

  3. Стиль прохождения продолжения полезен, и у нас есть, с ConT r IO! Для меня это самый большой гвоздь в гробу, я не вижу никакой пользы для этой идеи по сравнению с простым использованием существующей библиотеки по сравнению с трудным и потенциально небезопасным взломом компилятора.

Используя ContT монада для простой функциональности отмены, вы эффективно ударяете муху с базуки.:-) Если ваша цель - просто прекратить возвращать сообщение об ошибке, когда что-то идет не так, то вы можете рассмотреть возможность использования ErrorT монада (предоставляется трансформаторами) вместо. В качестве альтернативы, если вы не будете использовать отдельный тип для результата при возникновении ошибки, вы можете рассмотреть возможность использования AbortT монада (предоставляется AbortT-трансформерами).

Ответ НЕТ, потому что call / cc- плохая функция для любого языка. Хотя call / cc был в Scheme очень долгое время, это не значит, что call / cc- хорошая языковая функция. Опыт использования call / cc в Scheme показал, что это плохая языковая особенность: использование call / cc часто приводит к утечке памяти, call / cc без dynamic-wind не подходит для любой серьезной программы или любой библиотеки; с другой стороны, при динамическом ветре стандартные идиомы call / cc становятся медленными. Все эти недостатки перечислены со вспомогательным кодом и другими доказательствами в http://okmij.org/ftp/continuations/against-callcc.html

Я согласен, что стиль прохождения продолжения имеет много преимуществ. Фактически, когда мы пишем код на Haskell в монадическом стиле, мы используем именно CPS. Действительно, рассмотрим простой вызов вложенной функции putChar (getChar ()), Мы пишем это в CPS следующим образом

 getCharK :: () -> (Char -> w) -> w
 putCharK :: Char -> (() -> w) -> w

 gp :: (() -> w) -> w
 gp = \k -> getCharK () (\c -> putCharK c k)

А вот как мы пишем один и тот же вложенный вызов для монадических getM и putM (для некоторой монады M):

getM :: () -> M Char
putM :: Char -> M ()

gpM :: M ()
gpM =  getM () `bind` (\c -> putM c)

Теперь, если связывание определяется как

bind m f = \k -> m (\x -> (f x) k)

тогда приложение с монадической вложенной функцией идентично версии кода CPS. Монадический пример работает в Haskell, если вы замените M на ContT w IO и и обертки нового типа ContT/runContT в определении bind (который становится (>>=) в монаде ContT w IO). Итак, Haskell уже позволяет нам писать код на CPS; do-nonation делает это довольно удобным.

В документации сказано:

Многие алгоритмы, которые требуют продолжения в других языках, не требуют их в Haskell, из-за ленивой семантики Haskell. Злоупотребление монадой Continuation может привести к коду, который невозможно понять и поддерживать

Я думаю, что это, вероятно, главная причина.

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