Миран Липовача - Изучай Haskell во имя добра!
- Название:Изучай Haskell во имя добра!
- Автор:
- Жанр:
- Издательство:ДМК Пресс
- Год:2012
- Город:Москва
- ISBN:978-5-94074-749-9
- Рейтинг:
- Избранное:Добавить в избранное
-
Отзывы:
-
Ваша оценка:
Миран Липовача - Изучай Haskell во имя добра! краткое содержание
Язык Haskell имеет множество впечатляющих возможностей, но главное его свойство в том, что меняется не только способ написания кода, но и сам способ размышления о проблемах и возможных решениях. Этим Haskell действительно отличается от большинства языков программирования. С его помощью мир можно представить и описать нестандартным образом. И поскольку Haskell предлагает совершенно новые способы размышления о проблемах, изучение этого языка может изменить и стиль программирования на всех прочих.
Ещё одно необычное свойство Haskell состоит в том, что в этом языке придаётся особое значение рассуждениям о типах данных. Как следствие, вы помещаете больше внимания и меньше кода в ваши программы.
Вне зависимости от того, в каком направлении вы намерены двигаться, путешествуя в мире программирования, небольшой заход в страну Haskell себя оправдает. А если вы решите там остаться, то наверняка найдёте чем заняться и чему поучиться!
Эта книга поможет многим читателям найти свой путь к Haskell.
Отображения, монады, моноиды и другое! Всё сказано в названии: «Изучай Хаскель во имя добра!» – весёлый иллюстрированный самоучитель по этому сложному функциональному языку.
С помощью оригинальных рисунков автора, отсылке к поп-культуре, и, самое главное, благодаря полезным примерам кода, эта книга обучает основам функционального программирования так, как вы никогда не смогли бы себе представить.
Вы начнете изучение с простого материала: основы синтаксиса, рекурсия, типы и классы типов. Затем, когда вы преуспеете в основах, начнется настоящий мастер-класс от профессионала: вы изучите, как использовать аппликативные функторы, монады, застежки, и другие легендарные конструкции Хаскеля, о которых вы читали только в сказках.
Продираясь сквозь образные (и порой безумные) примеры автора, вы научитесь:
• Смеяться в лицо побочным эффектам, поскольку вы овладеете техниками чистого функционального программирования.
• Использовать волшебство «ленивости» Хаскеля для игры с бесконечными наборами данных.
• Организовывать свои программы, создавая собственные типы, классы типов и модули.
• Использовать элегантную систему ввода-вывода Хаскеля, чтобы делиться гениальностью ваших программ с окружающим миром.
Нет лучшего способа изучить этот мощный язык, чем чтение «Изучай Хаскель во имя добра!», кроме, разве что, поедания мозга его создателей. Миран Липовача (Miran Lipovača) изучает информатику в Любляне (Словения). Помимо его любви к Хаскелю, ему нравится заниматься боксом, играть на бас-гитаре и, конечно же, рисовать. У него есть увлечение танцующими скелетами и числом 71, а когда он проходит через автоматические двери, он притворяется, что на самом деле открывает их силой своей мысли.
Изучай Haskell во имя добра! - читать онлайн бесплатно ознакомительный отрывок
Интервал:
Закладка:
Когда ваши недетерминированные значения взаимодействуют, вы можете воспринимать их вычисление как дерево, где каждый возможный результат в списке представляет отдельную ветку. Вот предыдущее выражение, переписанное в нотации do
:
listOfTuples :: [(Int,Char)]
listOfTuples = do
n <���– [1,2]
ch <���– ['a','b']
return (n,ch)
Такая запись делает чуть более очевидным то, что образец n
принимает каждое значение из списка [1,2]
, а образец ch
– каждое значение из списка ['a','b']
. Как и в случае с типом Maybe
, мы извлекаем элементы из монадического значения и обрабатываем их как обычные значения, а операция >>=
беспокоится о контексте за нас. Контекстом в данном случае является недетерминированность.
Нотация do и генераторы списков
Использование списков в нотации do
может напоминать вам о чём-то, что вы уже видели ранее. Например, посмотрите на следующий кусок кода:
ghci> [(n,ch) | n <���– [1,2], ch <���– ['a','b']]
[(1,'a'),(1,'b'),(2,'a'),(2,'b')]
Да! Генераторы списков! В нашем примере, использующем нотацию do
, образец n
принимал значения всех результатов из списка [1,2]
. Для каждого такого результата образцу ch
был присвоен результат из списка ['a','b']
, а последняя строка помещала пару (n,
ch)
в контекст по умолчанию (одноэлементный список) для возврата его в качестве результата без привнесения какой-либо дополнительной недетерминированности. В генераторе списка произошло то же самое, но нам не нужно было писать вызов функции return
в конце для возврата пары (n,
ch)
в качестве результата, потому что выводящая часть генератора списка сделала это за нас.
На самом деле генераторы списков являются просто синтаксическим сахаром для использования списков как монад. В конечном счёте генераторы списков и списки, используемые в нотации do
, переводятся в использование операции >>=
для осуществления вычислений, которые обладают недетерминированностью.
Класс MonadPlus и функция guard
Генераторы списков позволяют нам фильтровать наши выходные данные. Например, мы можем отфильтровать список чисел в поиске только тех из них, которые содержат цифру 7
:
ghci> [x | x <���– [1..50], '7' `elem` show x]
[7,17,27,37,47]
Мы применяем функцию show
к параметру x
чтобы превратить наше число в строку, а затем проверяем, является ли символ '7'
частью этой строки.
Чтобы увидеть, как фильтрация в генераторах списков преобразуется в списковую монаду, мы должны рассмотреть функцию guard
и класс типов MonadPlus
.
Класс типов MonadPlus
предназначен для монад, которые также могут вести себя как моноиды. Вот его определение:
class Monad m => MonadPlus m where
mzero :: m a
mplus :: m a –> m a –> m a
Функция mzero
является синонимом функции mempty
из класса типов Monoid
, а функция mplus
соответствует функции mappend
. Поскольку списки являются моноидами, а также монадами, их можно сделать экземпляром этого класса типов:
instance MonadPlus [] where
mzero = []
mplus = (++)
Для списков функция mzero
представляет недетерминированное вычисление, которое вообще не имеет результата – неуспешно окончившееся вычисление. Функция mplus
сводит два недетерминированных значения в одно. Функция guard
определена следующим образом:
guard :: (MonadPlus m) => Bool –> m ()
guard True = return ()
guard False = mzero
Функция guard
принимает значение типа Bool
. Если это значение равно True
, функция guard
берёт пустой кортеж ()
и помещает его в минимальный контекст, который по-прежнему является успешным. Если значение типа Bool
равно False
, функция guard
создаёт монадическое значение с неудачей в вычислениях. Вот эта функция в действии:
ghci> guard (5 > 2) :: Maybe ()
Just ()
ghci> guard (1 > 2) :: Maybe ()
Nothing
ghci> guard (5 > 2) :: [()]
[()]
ghci> guard (1 > 2) :: [()]
[]
Выглядит интересно, но чем это может быть полезно? В списковой монаде мы используем её для фильтрации недетерминированных вычислений:
ghci> [1..50] >>= (\x –> guard ('7' `elem` show x) >> return x)
[7,17,27,37,47]
Результат аналогичен тому, что был возвращён нашим предыдущим генератором списка. Как функция guard
достигла этого? Давайте сначала посмотрим, как она функционирует совместно с операцией >>
:
ghci> guard (5 > 2) >> return "клёво" :: [String]
["клёво"]
ghci> guard (1 > 2) >> return "клёво" :: [String]
[]
Если функция guard
срабатывает успешно, результатом, находящимся в ней, будет пустой кортеж. Поэтому дальше мы используем операцию >>
, чтобы игнорировать этот пустой кортеж и предоставить что-нибудь другое в качестве результата. Однако если функция guard
не срабатывает успешно, функция return
впоследствии тоже не сработает успешно, потому что передача пустого списка функции с помощью операции >>=
всегда даёт в результате пустой список. Функция guard
просто говорит: «Если это значение типа Bool
равно False
, верни неуспешное окончание вычислений прямо здесь. В противном случае создай успешное значение, которое содержит в себе значение-пустышку ()
». Всё, что она делает, – позволяет вычислению продолжиться.
Вот предыдущий пример, переписанный в нотации do
:
sevensOnly :: [Int]
sevensOnly = do
x <���– [1..50]
guard ('7' `elem` show x)
return x
Если бы мы забыли представить образец x
в качестве окончательного результата, используя функцию return
, то результирующий список состоял бы просто из пустых кортежей. Вот определение в форме генератора списка:
ghci> [x | x <���– [1..50], '7' `elem` show x]
[7,17,27,37,47]
Поэтому фильтрация в генераторах списков – это то же самое, что использование функции guard
.
Ход конём
Есть проблема, которая очень подходит для решения с помощью недетерминированности. Скажем, у нас есть шахматная доска и на ней только одна фигура – конь. Мы хотим определить, может ли конь достигнуть определённой позиции в три хода. Будем использовать пару чисел для представления позиции коня на шахматной доске. Первое число будет определять столбец, в котором он находится, а второе число – строку.

Создадим синоним типа для текущей позиции коня на шахматной доске.
type KnightPos = (Int, Int)
Теперь предположим, что конь начинает движение с позиции (6, 2)
. Может ли он добраться до (6, 1)
именно за три хода? Какой ход лучше сделать следующим из его нынешней позиции? Я знаю: как насчёт их всех?! К нашим услугам недетерминированность, поэтому вместо того, чтобы выбрать один ход, давайте просто выберем их все сразу! Вот функция, которая берёт позицию коня и возвращает все его следующие ходы:
Интервал:
Закладка: