LINUX.ORG.RU

Зачем в Haskell IO?

 ,


0

2

Haskell язык ленивый. Для интерактивных программ достаточно вообще обычных строк:

main = interact realMain

realMain :: String -> String
realMain = unlines . f . lines
  where
    f x = "Введите своё имя: ":g x
    g (имя:числа) = ["Привет, " ++ имя, "Вводите числа, q для выхода"] ++ корни числа
    корни ("q":_) = []
    корни (x:xs) = (show . sqrt) (read x :: Double):корни xs

Вот программа, которая запрашивает имя пользователя, затем выводит корни введённых чисел пока не получит q. Никакого IO внутри realMain не требуется.

Если брать произвольное взаимодействие с окружающим миром, то достаточно main :: [inEvent] -> [outEvent].

Зачем нужен «магический» тип IO, делящий Haskell на фактически два разных языка?

★★★★★

Последнее исправление: monk (всего исправлений: 1)
Ответ на: комментарий от monk

error превращается в исключение, если в IO.

Нет, не превращается. Error можно перехватить, но так делать не надо за очень редким исключением. В Haskell слегка бардак с термином «исключение» и под ним часто понимаются совершенно разные вещи. Если вкратце, error вызовет у тебя асинхронное исключение, как и, например, деление на 0 или завершение треда извне. Исключения типа IOException – синхронные, и обрабатываются иначе.

Но функция error совершенно не обязана вызывать асинхронное исключение в принципе, это особенность реализации. Вот такая реализация тоже будет валидной, и твой код должен это учитывать.

Prelude> let error :: String -> a; error s = error s
Prelude> :t error
error :: String -> a

Список событий вполне работает и с потоками и с синхронизацией.

Я не понимаю, как в твоём представлении это работает. Как именно ты spawn реализуешь и какой тип будет иметь эта функция?

hateyoufeel ★★★★★
()
Последнее исправление: hateyoufeel (всего исправлений: 1)
Ответ на: комментарий от hateyoufeel

Исключения типа IOException – синхронные, и обрабатываются иначе.

Только внутри IO и только если их бросать через throwIO. Опять же, ещё одна «магия».

деление на 0

Это уже просто Infinity

Как именно ты spawn реализуешь и какой тип будет иметь эта функция?

spawn :: ([inEvent] -> [outEvent]) -> outEvent

Передаёт функцию в виде события для исполнителя (для выполнения в отдельном потоке).

monk ★★★★★
() автор топика
Ответ на: комментарий от monk

Только внутри IO и только если их бросать через throwIO. Опять же, ещё одна «магия».

У тебя вся программа внутри IO. Никакой магии в этом случае нет.

Это уже просто Infinity

Нет.

Prelude> 1 `div` 0
*** Exception: divide by zero

Передаёт функцию в виде события для исполнителя (для выполнения в отдельном потоке).

А синхронизация как будет происходить? И откуда InEvent будет приходить?

hateyoufeel ★★★★★
()
Ответ на: комментарий от hateyoufeel

У тебя вся программа внутри IO. Никакой магии в этом случае нет.

Тогда объясни без магии чем отличаются throw и throwIO.

А синхронизация как будет происходить?

Синхронизация по событию наружу типа outResult и ловли снаружи inResult.

И откуда InEvent будет приходить?

Откуда угодно. События ввода/вывода, работы с внешним миром, передача чего-нибудь от породившего потока.

Как в Эрланге.

monk ★★★★★
() автор топика
Ответ на: комментарий от monk

Тогда объясни без магии чем отличаются throw и throwIO.

throw e   `seq` x  ===> throw e
throwIO e `seq` x  ===> x

Вот этим. throwIO гарантирует порядок операций, throw – нет. Ну и, ИМХО, throw использовать вообще нигде и никогда не надо. Это чудовищных размеров костыль из древности, от которого надо избавиться. Языку больше 30 лет уже, там такого много.

Синхронизация по событию наружу типа outResult и ловли снаружи inResult.

Ловли чем? Ты упускаешь самое интересное, на самом деле, сосредотачиваясь только на одном аспекте.

Как в Эрланге.

Если ты хочешь как в Erlang, у тебя все функции должны возвращать IO, потому что чистых функций в Erlang нет. Как и статической типизации.

hateyoufeel ★★★★★
()
Ответ на: комментарий от hateyoufeel

Нет, не превращается. Error можно перехватить, но так делать не надо за очень редким исключением.

catch (... error ...) (\e ... (e :: SomeException) ...) 

ловит исключение. Значит error является исключением.

monk ★★★★★
() автор топика
Ответ на: комментарий от monk

Почитай, пожалуйста, про асинхронные исключения и чем они от обычных отличаются. И почему не надо использовать catch чтобы ловить исключения в обычном коде.

hateyoufeel ★★★★★
()
Ответ на: комментарий от hateyoufeel

Вот этим. throwIO гарантирует порядок операций, throw – нет.

С seq как раз и есть магия. Так как по определению seq вычисляет первый аргумент и возвращает второй, если первый не ошибка. То есть throwIO e не является ошибкой. А становится ею только когда её результат вычисляется внутри монады IO.

Ловли чем?

Я же написал псевдокод. Если первый элемент списка событий является событием от второго потока, его обрабатываем. Фактически, полный аналог receive/end из Эрланга.

Если ты хочешь как в Erlang, у тебя все функции должны возвращать IO, потому что чистых функций в Erlang нет. Как и статической типизации.

В моём варианте IO вообще нет и все функции чистые. Как и в раннем Хаскеле. Только ленивый список событий на входе и возврат другого ленивого списка событий на выходе.

monk ★★★★★
() автор топика
Последнее исправление: monk (всего исправлений: 1)
Ответ на: комментарий от monk

С seq как раз и есть магия.

Это не магия, это ленивость. seq вычисляет WHNF первого аргумента, и всё. Например, из этого следует что

Prelude> undefined `seq` 1
*** Exception: Prelude.undefined
CallStack (from HasCallStack):
  error, called at libraries/base/GHC/Err.hs:79:14 in base:GHC.Err
  undefined, called at <interactive>:1:1 in interactive:Ghci1
Prelude> Just undefined `seq` 1
1

С IO в данном случае примерно так же: seq вычисляет конструктор, но не его аргументы. Вот тебе примерное определение seq, чтобы было понятнее:

seq :: a -> b -> b
seq a b =
  case a of
    _ -> b

Как видишь, к IO это не имеет вообще никакого отношения, это ортогональные вещи.

Если первый элемент списка событий является событием от второго потока, его обрабатываем. Фактически, полный аналог receive/end из Эрланга.

Что будет с твоим списком, если сделать deepseq и вычислить его полностью? Всё повиснет? Согласись, так себе вариант.

В моём варианте IO вообще нет и все функции чистые. Как и в раннем Хаскеле. Только ленивый список событий на входе и возврат другого ленивого списка событий на выходе.

Ранний хацкелл не очень работал, и поэтому придумали IO. Где-то есть даже статья об этом, можешь её почитать.

hateyoufeel ★★★★★
()
Ответ на: комментарий от monk

Ты блин почитай, на что сам ссылаешься :)

But this catchAny function isn’t quite correct, due to asynchronous exceptions

Там дальше следует объяснение, почему не надо catch прямо брать и использовать, что такое асинхронные исключения, и почему их ловить не надо.

hateyoufeel ★★★★★
()
Ответ на: комментарий от hateyoufeel

Что будет с твоим списком, если сделать deepseq и вычислить его полностью? Всё повиснет? Согласись, так себе вариант.

Его вычисление примерно эквивалентно

main = sequence $ repeat getLine

Тоже всё повиснет.

Где-то есть даже статья об этом, можешь её почитать.

Самое близкое нашёл: https://stackoverflow.com/questions/13536761/what-other-ways-can-state-be-handled-in-a-pure-functional-language-besides-with/13538351#13538351

Впрочем, достаточные для меня ответы я получил.

monk ★★★★★
() автор топика
Ответ на: комментарий от hateyoufeel

Ты блин почитай, на что сам ссылаешься :)

Ты не дочитал. Смотри чуть выше от «We can now have complete* confidence in the values returned from catchAny»

monk ★★★★★
() автор топика
Ответ на: комментарий от monk

Ну да, я про это же. Там буквально написано, что не надо использовать catch просто так. Вместо этого, надо использовать хрень, которую они запилили и которая уже есть в стандартной библиотеке.

hateyoufeel ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.