Миран Липовача - Изучай Haskell во имя добра!
- Название:Изучай Haskell во имя добра!
- Автор:
- Жанр:
- Издательство:ДМК Пресс
- Год:2012
- Город:Москва
- ISBN:978-5-94074-749-9
- Рейтинг:
- Избранное:Добавить в избранное
-
Отзывы:
-
Ваша оценка:
Миран Липовача - Изучай Haskell во имя добра! краткое содержание
Язык Haskell имеет множество впечатляющих возможностей, но главное его свойство в том, что меняется не только способ написания кода, но и сам способ размышления о проблемах и возможных решениях. Этим Haskell действительно отличается от большинства языков программирования. С его помощью мир можно представить и описать нестандартным образом. И поскольку Haskell предлагает совершенно новые способы размышления о проблемах, изучение этого языка может изменить и стиль программирования на всех прочих.
Ещё одно необычное свойство Haskell состоит в том, что в этом языке придаётся особое значение рассуждениям о типах данных. Как следствие, вы помещаете больше внимания и меньше кода в ваши программы.
Вне зависимости от того, в каком направлении вы намерены двигаться, путешествуя в мире программирования, небольшой заход в страну Haskell себя оправдает. А если вы решите там остаться, то наверняка найдёте чем заняться и чему поучиться!
Эта книга поможет многим читателям найти свой путь к Haskell.
Отображения, монады, моноиды и другое! Всё сказано в названии: «Изучай Хаскель во имя добра!» – весёлый иллюстрированный самоучитель по этому сложному функциональному языку.
С помощью оригинальных рисунков автора, отсылке к поп-культуре, и, самое главное, благодаря полезным примерам кода, эта книга обучает основам функционального программирования так, как вы никогда не смогли бы себе представить.
Вы начнете изучение с простого материала: основы синтаксиса, рекурсия, типы и классы типов. Затем, когда вы преуспеете в основах, начнется настоящий мастер-класс от профессионала: вы изучите, как использовать аппликативные функторы, монады, застежки, и другие легендарные конструкции Хаскеля, о которых вы читали только в сказках.
Продираясь сквозь образные (и порой безумные) примеры автора, вы научитесь:
• Смеяться в лицо побочным эффектам, поскольку вы овладеете техниками чистого функционального программирования.
• Использовать волшебство «ленивости» Хаскеля для игры с бесконечными наборами данных.
• Организовывать свои программы, создавая собственные типы, классы типов и модули.
• Использовать элегантную систему ввода-вывода Хаскеля, чтобы делиться гениальностью ваших программ с окружающим миром.
Нет лучшего способа изучить этот мощный язык, чем чтение «Изучай Хаскель во имя добра!», кроме, разве что, поедания мозга его создателей. Миран Липовача (Miran Lipovača) изучает информатику в Любляне (Словения). Помимо его любви к Хаскелю, ему нравится заниматься боксом, играть на бас-гитаре и, конечно же, рисовать. У него есть увлечение танцующими скелетами и числом 71, а когда он проходит через автоматические двери, он притворяется, что на самом деле открывает их силой своей мысли.
Изучай Haskell во имя добра! - читать онлайн бесплатно ознакомительный отрывок
Интервал:
Закладка:
Простейший способ выполнить действие, которое потенциально может вызвать исключение,– воспользоваться функцией try
:
try :: Exception e => IO a -> IO (Either e a)
Функция try
пытается выполнить переданное ей действие ввода-вывода и возвращает либо Right <���результат действия>
либо Left <���исключение>
, например:
ghci> try (print $ 5 `div` 2) :: IO (Either ArithException ())
2
Right ()
ghci> try (print $ 5 `div` 0) :: IO (Either ArithException ())
Left divide by zero
Обратите внимание, что в данном случае потребовалось явно указать тип выражения, поскольку для вывода типа информации недостаточно. Помимо прочего, указание типа исключения позволяет обрабатывать не все исключения, а только некоторые. В следующем примере исключение функцией try
обнаружено не будет:
> try (print $ 5 `div` 0) :: IO (Either IOException ())
*** Exception: divide by zero
Указание типа SomeException
позволяет обнаружить любое исключение:
ghci> try (print $ 5 `div` 0) :: IO (Either SomeException ())
Left divide by zero
Попробуем написать программу, которая принимает два числа в виде параметров командной строки, делит первое число на второе и наоборот и выводит результаты. Нашей первой целью будет корректная обработка ошибки деления на ноль.
import Control.Exception
import System.Environment
printQuotients :: Integer -> Integer -> IO ()
printQuotients a b = do
print $ a `div` b
print $ b `div` a
params :: [String] -> (Integer, Integer)
params [a,b] = (read a, read b)
main = do
args <- getArgs
let (a, b) = params args
res <- try (printQuotients a b) :: IO (Either ArithException ())
case res of
Left e -> putStrLn "Деление на 0!"
Right () -> putStrLn "OK"
putStrLn "Конец программы"
Погоняем программу на различных значениях:
$ ./quotients 20 7
2
0
OK
Конец программы
$ ./quotients 0 7
0
Деление на 0!
Конец программы
$ ./quotients 7 0
Деление на 0!
Конец программы
Понятно, что пока эта программа неустойчива к другим видам ошибок. В частности, мы можем «забыть» передать параметры командной строки или передать их не в том количестве:
$ ./quotients
quotients: quotients.hs:10:1-31: Non-exhaustive patterns in function params
$ ./quotients 2 3 4
quotients: quotients.hs:10:1-31: Non-exhaustive patterns in function params
Это исключение генерируется при вызове функции params
, если переданный ей список оказывается не двухэлементным. Можно также указать нечисловые параметры:
$ ./quotients a b
quotients: Prelude.read: no parse
Исключение здесь генерируется функцией read
, которая не в состоянии преобразовать переданный ей параметр к числовому типу.
Чтобы справиться с любыми возможными исключениями, выделим тело программы в отдельную функцию, оставив в функции main
получение параметров командной строки и обработку исключений:
mainAction :: [String] -> IO ()
mainAction args = do
let (a, b) = params args
printQuotients a b
main = do
args <- getArgs
res <- try (mainAction args) :: IO (Either SomeException ())
case res of
Left e -> putStrLn "Ошибка"
Right () -> putStrLn "OK"
putStrLn "Конец программы"
Мы были вынуждены заменить тип исключения на SomeException
и сделать сообщение об ошибке менее информативным, поскольку теперь неизвестно, исключение какого вида в данном случае произошло.
$ ./quotients a b
Ошибка
Конец программы
$ ./quotients
Ошибка
Конец программы
Понятно, что в общем случае обработка исключения должна зависеть от её типа. Предположим, что у нас имеется несколько обработчиков для исключений разных типов:
handleArith :: ArithException -> IO ()
handleArith _ = putStrLn "Деление на 0!"
handleArgs :: PatternMatchFail -> IO ()
handleArgs _ = putStrLn "Неверное число параметров командной строки!"
handleOthers :: SomeException -> IO ()
handleOthers e = putStrLn $ "Неизвестное исключение: " ++ show e
К сожалению, чтобы увидеть исключение от функции read
, нужно воспользоваться наиболее общим типом SomeException
.
Вместо того чтобы вручную вызывать функцию обработчика при анализе результата try
, можно применить функцию catch
, вот её тип:
ghci> :t catch
catch :: Exception e => IO a -> (e -> IO a) -> IO a
ПРИМЕЧАНИЕ.Модуль Prelude
экспортирует старую версию функции catch
, которая способна обрабатывать только исключения ввода-вывода. Чтобы использовать новый вариант её определения, необходимо использовать скрывающий импорт: import Prelude hiding (catch)
.
Функция catch
принимает в качестве параметров действие и обработчик исключения: если при выполнении действия генерируется исключение, то вызывается его обработчик. Тип обработчика определяет, какие именно исключения будут обработаны. Рассмотрим примеры, в которых функция mainAction
вызывается непосредственно в GHCi:
ghci> mainAction ["2","0"]
*** Exception: divide by zero
ghci> mainAction ["0","2"] `catch` handleArith
0
Деление на 0!
ghci> mainAction ["2","0"] `catch` handleArgs
*** Exception: divide by zero
ghci> mainAction ["2","0"] `catch` handleOthers
Неизвестное исключение: divide by zero
ghci> mainAction ["a", "b"] `catch` handleArgs
*** Exception: Prelude.read: no parse
ghci> mainAction ["a", "b"] `catch` handleOthers
Неизвестное исключение: Prelude.read: no parse
Если строка, выводимая GHCi, начинается с ***
, то соответствующее исключение не было обработано. Обратите внимание на обычный для функции catch
инфиксный способ вызова. Заметьте также, что обработчик handleOthers
способен обработать любое исключение.
Вернёмся к основной программе. Нам хочется, чтобы возникшее исключение было обработано наиболее подходящим образом: если произошло деление на ноль, то следует выполнить handleArith
, при неверном числе параметров командной строки – handleArgs
, в остальных случаях – handleOthers
. В этом нам поможет функция catches
, посмотрим на её тип:
> :t catches
catches :: IO a -> [Handler a] -> IO a
Функция catches
принимает в качестве параметров действие и список обработчиков (функций, которые упакованы конструктором данных Handler
) и возвращает результат действия. Если в процессе выполнения происходит исключение, то вызывается первый из подходящих по типу исключения обработчиков (поэтому, в частности, обработчик handleOthers
должен быть последним). Перепишем функцию main
так, чтобы корректно обрабатывались все возможные исключительные ситуации:
main = do
args <- getArgs
mainAction args `catches`
[Handler handleArith,
Handler handleArgs,
Handler handleOthers]
putStrLn "Конец программы"
Посмотрим, как она теперь работает:
$ ./quotients 20 10
Интервал:
Закладка: