Что означает действия ввода-вывода в чистых функциях? - PullRequest
10 голосов
/ 22 марта 2012

Я думал, что в принципе система типов haskell запрещает вызывать нечистые функции (т.е. f :: a -> IO b) из чистых, но сегодня я понял, что, вызывая их с return, они компилируются просто отлично. Например:

h :: Maybe ()
h = do
    return $ putStrLn "???"
    return ()

Теперь, h работает в возможно монаде, но, тем не менее, это чистая функция. Компиляция и запуск просто возвращает Just (), как и следовало ожидать, фактически не выполняя никаких операций ввода-вывода. Я думаю, что лень haskell объединяет вещи (т. Е. Возвращаемое значение putStrLn не используется - и не может, поскольку его конструкторы значений скрыты, и я не могу сопоставить шаблон с ним), но почему этот код допустим? Существуют ли другие причины, которые делают это возможным?

В качестве бонуса, связанный с этим вопрос: вообще можно ли вообще запретить выполнение действий монады из других? Как?

Ответы [ 2 ]

19 голосов
/ 22 марта 2012

Действия ввода-вывода являются первоклассными значениями, как и любые другие; это то, что делает IO Haskell таким выразительным, что позволяет вам создавать структуры управления более высокого порядка (например, mapM_) с нуля. Лень здесь не актуальна, 1 просто вы на самом деле не выполняете действие. Вы просто создаете значение Just (putStrLn "???"), а затем выбрасываете его.

putStrLn "???" существующий не приводит к выводу строки на экран. Само по себе putStrLn "???" - это просто описание некоторого ввода-вывода, которое можно сделать, чтобы вызвать вывод строки на экран. Единственное, что происходит, это выполнение main, которое вы создали из других действий ввода-вывода или любых действий, которые вы вводите в GHCi. Для получения дополнительной информации см. Введение в IO .

Действительно, вполне возможно, что вы захотите манипулировать действиями IO внутри Maybe; представьте себе функцию String -> Maybe (IO ()), которая проверяет правильность строки и, если она действительна, возвращает действие ввода-вывода для вывода некоторой информации, полученной из строки. Это возможно именно благодаря первоклассным действиям ввода-вывода на Haskell.

Но монада не способна выполнять действия другой монады, если вы не дадите ей эту способность.

1 Действительно, h = putStrLn "???" `seq` return () также не приводит к выполнению ввода-вывода, даже если оно вызывает оценку putStrLn "???".

4 голосов
/ 22 марта 2012

Давайте расскажем!

h = do return (putStrLn "???"); return ()
-- rewrite (do foo; bar) as (foo >> do bar)
h = return (putStrLn "???") >> do return ()
-- redundant do
h = return (putStrLn "???") >> return ()
-- return for Maybe = Just
h = Just (putStrLn "???") >> Just ()
-- replace (foo >> bar) with its definition, (foo >>= (\_ -> bar))
h = Just (putStrLn "???") >>= (\_ -> Just ())

Теперь, что происходит, когда вы оцениваете h? * Ну, может быть,

(Just x) >>= f = f x
Nothing  >>= f = Nothing

Итак, мы сопоставляем шаблон с первым случаем

f x
-- x = (putStrLn "???"), f = (\_ -> Just ())
(\_ -> Just ()) (putStrLn "???")
-- apply the argument and ignore it
Just ()

Обратите внимание, что нам никогда не приходилось выполнять putStrLn "???", чтобы оценить это выражение.

* nb Несколько непонятно, в какой момент «десагеринг» прекращается и начинается «оценка».Это зависит от встроенных решений вашего компилятора.Чистые вычисления могут быть оценены полностью во время компиляции.

...