Изучай Haskell во имя добра! (Липовача) - страница 153

явно указывает, что результатом может быть значение типа >b, завёрнутое в конструктор >Just, или значение >Nothing. Тип функции отличается от простого >a –> b, и если мы попытаемся использовать один тип вместо другого, компилятор будет «жаловаться» на нас.

Кроме алгебраических типов, хорошо представляющих вычисления, которые могут закончиться неудачей, язык Haskell имеет поддержку исключительных ситуаций, так как они приобретают особое значение в контексте ввода-вывода. Всё может пойти вкривь и вкось, если вы работаете с внешним миром, который столь ненадёжен! Например, при открытии файла может случиться всякое. Он может быть заблокирован, его может не оказаться по заданному пути, или не будет такого диска, или ещё что-нибудь…

При возникновении исключительной ситуации хорошо бы иметь возможность перейти на некоторый код обработки ошибки. Хорошо, код для ввода-вывода (то есть «грязный» код) может вызывать исключения. Имеет смысл. Ну а как насчёт чистого кода? Он тоже может вызывать исключения! Вспомним функции >div и >head. Их типы – >(Integral a) => a –> a –> a и >[a] –> a соответственно. Никаких значений типа >Maybe или >Either в возвращаемом типе, и тем не менее они могут вызвать ошибку! Функция >div взорвётся у вас в руках, если вы попытаетесь разделить на нуль, а функция >head выпадет в осадок, если передать ей пустой список.

>ghci> 4 `div` 0

>*** Exception: divide by zero

>ghci> head []

>*** Exception: Prelude.head: empty list

Чистый код может выбрасывать исключения, но они могут быть перехвачены только в части кода, работающей с системой ввода-вывода (когда мы внутри блока >do в функции >main). Причина в том, что вы не знаете, когда что-то будет (если вообще будет!) вычислено в чистом коде, так как он ленив и не имеет жёстко определённого порядка выполнения, в то время как код для ввода-вывода такой порядок имеет.

Раньше мы говорили, что нам желательно проводить как можно меньше времени в части нашей программы, посвящённой вводу-выводу. Логика программы должна располагаться главным образом в чистых функциях, поскольку их результат зависит только от параметров, с которыми функции были вызваны. При работе с чистыми функциями вы должны думать только о том, что функции возвращают, так как они не могут сделать чего-либо другого. Это облегчит вам жизнь!.. Даже несмотря на то, что некоторая логика в коде для ввода-вывода необходима (например, открытие файлов и т. п.), она должна быть сведена к минимуму. Чистые функции по умолчанию ленивы; следовательно, мы не знаем, когда они будут вычислены – это не должно иметь значения. Но как только чистые функции начинают вызывать исключения, становится важным момент их выполнения. Вот почему мы можем перехватывать исключения из чистых функций в части кода, посвящённой вводу-выводу. И это плохо: ведь мы стремимся оставить такую часть настолько маленькой, насколько возможно!… Однако если мы не перехватываем исключения, наша программа «падает». Решение? Не надо мешать исключения и чистый код! Пользуйтесь преимуществами системы типов языка Haskell и используйте типы вроде