Есть ли смысл сделать IO экземпляром MonadCont?
Очевидно MonadCont
s более ограничен и дает больше мощности, чем обычный Monad
S, благодаря его 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 очень хороший. Я просто хочу записать свое мнение здесь.
Правда, что
MonadCont
приходят из MTL. Но это не означает, что GHC или другой компилятор не может определитьunsafeCallCC
и определить экземпляр, еслиMonadCont
с правильным определением находится в области действия модуля компиляции и-XIOMonadCont
быть установленнымЯ уже говорил о безопасности исключений, и в этом трудно убедиться. Тем не менее, Haskell уже есть
unsafePerformIO
что в основном еще более небезопасно, чемunsafeCallCC
, по-моему.Конечно
callCC
в большинстве случаев слишком мощный и его следует избегать, когда это возможно. Тем не менее, по моему мнению, стиль передачи продолжения может использоваться для явной ленивой оценки, что может помочь лучше понять программу и, таким образом, облегчить поиск возможных оптимизаций. Конечно, CPS неMonadCont
, но это естественный шаг - использовать его и преобразовывать глубоко вложенные внутренние функции в нотации do.
4 ответа
Я бы сказал, что это плохая идея. Прежде всего, MonadCont
находится в MTL. GHC ничего не знает об этом, это будет означать, что компилятор будет зависеть от сторонней библиотеки, ick.
Во-вторых, callCC
непопулярен даже среди некоторых высокопрофессиональных шемеров, в основном потому, что он делает рассуждения о коде болезненными Во многом так же, как goto
трудно рассуждать о. Особенно, когда в Хаскеле, где бы нам пришлось беспокоиться
- Это исключение безопасно? (Что уже довольно сложно)
- Это
callCC
безопасный?
Наконец, нам это даже не нужно. Если вы хотите использовать продолжения и IO, используйте ContT IO
Это так же мощно. Тем не менее, я почти гарантирую, что он может быть заменен чем-то менее мощным, как monad-prompt
, Продолжение - кувалда, 9 из 10 раз, callCC
слишком мощный и может быть более приятно сформулирован, используя комбинацию функций более высокого порядка и лени.
Как пример, одно из прототипов использования callCC
это реализовать что-то вроде исключений, но в Haskell мы можем просто использовать монады:) (которые полагаются на функции более высокого порядка и лень).
По сути, то, что вы предлагаете, увеличивает сложность, означает объединение MTL с базой и целый ряд других неприятностей, которых нужно просто избегать. liftIO
,
RE Edits
- Конечно, вы можете сделать это, но тогда это не пример
MonadCont
конечно:) Это немного по-другому,
unsafePerformIO
предназначен для использования таким образом, чтобы побочные эффекты не были видны никому другому, поскольку у вас нет никаких гарантий, как и когда все будет выполнено.Если бы это было в случае с
callCCIO
тогда вы могли бы просто использоватьCont
!Стиль прохождения продолжения полезен, и у нас есть, с
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 может привести к коду, который невозможно понять и поддерживать
Я думаю, что это, вероятно, главная причина.