Что такое тип ввода-вывода в Haskell - PullRequest
2 голосов
/ 02 мая 2020

Я новичок в языке программирования Haskell, продолжаю спотыкаться о типе IO в качестве параметра функции или типа возврата.

playGame :: Screen -> IO ()

ИЛИ

gameRunner :: IO String -> (String -> IO ()) -> Screen -> IO ()

Как это работает, я немного запутался, потому что я знаю, что String ожидает слова, а Int ожидает числа. Что ожидает или возвращает IO, используемый в функциях?

Ответы [ 2 ]

3 голосов
/ 02 мая 2020

IO - это способ, которым Haskell различает код, который является прозрачным по отношению к нему, и код, который не является прозрачным. IO a - это тип действия ввода-вывода, которое возвращает a.

Вы можете думать о действии ввода-вывода как о фрагменте кода, который оказывает некоторое влияние на реальный мир, ожидающий выполнения. Из-за этого побочного эффекта действие ввода-вывода не является ссылочно прозрачным; следовательно, порядок исполнения имеет значение. Задача main программы Haskell состоит в правильной последовательности и выполнении всех операций ввода-вывода. Таким образом, когда вы пишете функцию, которая возвращает IO a, вы фактически пишете функцию, которая возвращает действие, которое в конечном итоге - при выполнении main - выполняет действие и возвращает a.

Еще несколько объяснений:

Ссылочная прозрачность означает, что вы можете заменить функцию на ее значение. Ссылочно-прозрачная функция не может иметь побочных эффектов; в частности, ссылочно-прозрачная функция не может получить доступ к каким-либо аппаратным ресурсам, таким как файлы, сеть или клавиатура, поскольку значение функции будет зависеть от чего-то другого, кроме ее параметров.

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

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

Реальные побочные эффекты не являются ссылочно прозрачными. Вы можете думать о реальном мире как о некоем неявном глобальном состоянии, в котором мутируют эффективные функции. Из-за этого состояния порядок выполнения имеет значение: имеет значение, если вы сначала читаете из базы данных, а затем обновляете ее, или наоборот.

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

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

Вы не видите фиктивный параметр состояния, потому что он скрыт за каким-то сахаром syntacti c: запись do в main и других действиях ввода-вывода и внутри типа IO.

3 голосов
/ 02 мая 2020

Вкратце:

f1 :: A -> B -> C

- это функция, которая принимает два аргумента типа A и B и возвращает C. Он не выполняет никаких операций ввода-вывода.

f2 :: A -> B -> IO C

аналогичен f1, но также может выполнять операции ввода-вывода.

f3 :: (A -> B) -> IO C

принимает в качестве аргумента функцию A -> B (которая выполняет не выполняет IO) и создает C, возможно, выполняющий IO.

f4 :: (A -> IO B) -> IO C

принимает в качестве аргумента функцию A -> IO B (которая может выполнять IO) и создает C, возможно, выполняющий IO.

f5 :: A -> IO B -> IO C

принимает в качестве аргумента значение типа A, действие ввода-вывода типа IO B и возвращает значение типа C, возможно выполняя ввод-вывод (например, выполняя действие ввода-вывода аргумент один или несколько раз).

Пример:

f6 :: IO Int -> IO Int
f6 action = do
   x1 <- action
   x2 <- action
   putStrLn "hello!"
   x3 <- action
   return (x1+x2+x3)

Когда функция возвращает IO (), она не возвращает полезного значения, но может выполнить IO. Аналогично, скажем, возвращению void в C или Java. Ваша

gameRunner :: IO String -> (String -> IO ()) -> Screen -> IO ()

функция может быть вызвана со следующими аргументами:

arg1 :: IO String
arg1 = do
   putStrLn "hello"
   s <- readLine
   return ("here: " ++ s)

arg2 :: String -> IO ()
arg2 str = do
   putStrLn "hello"
   putStrLn str
   putStrLn "hello again"

arg3 :: Screen
arg3 = ... -- I don't know what's a Screen in your context
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...