Миран Липовача - Изучай Haskell во имя добра!
- Название:Изучай Haskell во имя добра!
- Автор:
- Жанр:
- Издательство:ДМК Пресс
- Год:2012
- Город:Москва
- ISBN:978-5-94074-749-9
- Рейтинг:
- Избранное:Добавить в избранное
-
Отзывы:
-
Ваша оценка:
Миран Липовача - Изучай Haskell во имя добра! краткое содержание
Язык Haskell имеет множество впечатляющих возможностей, но главное его свойство в том, что меняется не только способ написания кода, но и сам способ размышления о проблемах и возможных решениях. Этим Haskell действительно отличается от большинства языков программирования. С его помощью мир можно представить и описать нестандартным образом. И поскольку Haskell предлагает совершенно новые способы размышления о проблемах, изучение этого языка может изменить и стиль программирования на всех прочих.
Ещё одно необычное свойство Haskell состоит в том, что в этом языке придаётся особое значение рассуждениям о типах данных. Как следствие, вы помещаете больше внимания и меньше кода в ваши программы.
Вне зависимости от того, в каком направлении вы намерены двигаться, путешествуя в мире программирования, небольшой заход в страну Haskell себя оправдает. А если вы решите там остаться, то наверняка найдёте чем заняться и чему поучиться!
Эта книга поможет многим читателям найти свой путь к Haskell.
Отображения, монады, моноиды и другое! Всё сказано в названии: «Изучай Хаскель во имя добра!» – весёлый иллюстрированный самоучитель по этому сложному функциональному языку.
С помощью оригинальных рисунков автора, отсылке к поп-культуре, и, самое главное, благодаря полезным примерам кода, эта книга обучает основам функционального программирования так, как вы никогда не смогли бы себе представить.
Вы начнете изучение с простого материала: основы синтаксиса, рекурсия, типы и классы типов. Затем, когда вы преуспеете в основах, начнется настоящий мастер-класс от профессионала: вы изучите, как использовать аппликативные функторы, монады, застежки, и другие легендарные конструкции Хаскеля, о которых вы читали только в сказках.
Продираясь сквозь образные (и порой безумные) примеры автора, вы научитесь:
• Смеяться в лицо побочным эффектам, поскольку вы овладеете техниками чистого функционального программирования.
• Использовать волшебство «ленивости» Хаскеля для игры с бесконечными наборами данных.
• Организовывать свои программы, создавая собственные типы, классы типов и модули.
• Использовать элегантную систему ввода-вывода Хаскеля, чтобы делиться гениальностью ваших программ с окружающим миром.
Нет лучшего способа изучить этот мощный язык, чем чтение «Изучай Хаскель во имя добра!», кроме, разве что, поедания мозга его создателей. Миран Липовача (Miran Lipovača) изучает информатику в Любляне (Словения). Помимо его любви к Хаскелю, ему нравится заниматься боксом, играть на бас-гитаре и, конечно же, рисовать. У него есть увлечение танцующими скелетами и числом 71, а когда он проходит через автоматические двери, он притворяется, что на самом деле открывает их силой своей мысли.
Изучай Haskell во имя добра! - читать онлайн бесплатно ознакомительный отрывок
Интервал:
Закладка:
Эй, привет
Т-Е-В-И-Р-П- -,-Й-Э
Выражение intersperse '-' . reverse . map toUpper
берёт строку, отображает её с помощью функции toUpper
, применяет функцию reverse
к этому результату, а затем применяет к нему выражение intersperse '-'
. Это более красивый способ записи следующего кода:
(\xs –> intersperse '-' (reverse (map toUpper xs)))
Функции в качестве функторов
Другим экземпляром класса Functor
, с которым мы всё время имели дело, является (–>) r
. Стойте!.. Что, чёрт возьми, означает (–>) r
? Тип функции r –> a
может быть переписан в виде (–>) r a
, так же как мы можем записать 2 + 3
в виде (+) 2 3
. Когда мы воспринимаем его как (–>) r a
, то (–>)
представляется немного в другом свете. Это просто конструктор типа, который принимает два параметра типа, как это делает конструктор Either
.
Но вспомните, что конструктор типа должен принимать в точности один параметр типа, чтобы его можно было сделать экземпляром класса Functor
. Вот почему нельзя сделать конструктор (–>)
экземпляром класса Functor
; однако, если частично применить его до (–>) r
, это не составит никаких проблем. Если бы синтаксис позволял частично применять конструкторы типов с помощью сечений – подобно тому как можно частично применить оператор +,
выполнив (2+)
, что равнозначно (+)
2
, – вы могли бы записать (–>)
r
как (r
–>)
.
Каким же образом функции выступают в качестве функторов? Давайте взглянем на реализацию, которая находится в модуле Control.Monad.Instances
.
instance Functor ((–>) r) where
fmap f g = (\x –> f (g x))
Сначала подумаем над типом метода fmap
:
fmap :: (a –> b) –> f a –> f b
Далее мысленно заменим каждое вхождение идентификатора f
, являющегося ролью, которую играет наш экземпляр функтора, выражением (–>) r
. Это позволит нам понять, как функция fmap
должна вести себя в отношении данного конкретного экземпляра. Вот результат:
fmap :: (a –> b) –> ((–>) r a) –> ((–>) r b)
Теперь можно записать типы (–>) r a
и (–>) r b
в инфиксном виде, то есть r
–>
a
и r
–>
b
, как мы обычно поступаем с функциями:
fmap :: (a –> b) –> (r –> a) –> (r –> b)
Хорошо. Отображение одной функции с помощью другой должно произвести функцию, так же как отображение типа Maybe
с помощью функции должно произвести тип Maybe
, а отображение списка с помощью функции – список. О чём говорит нам предыдущий тип? Мы видим, что он берёт функцию из a
в b
и функцию из r
в a
и возвращает функцию из r
в b
. Напоминает ли это вам что-нибудь? Да, композицию функций!.. Мы присоединяем выход r –> a
ко входу a –> b
, чтобы получить функцию r –> b
, чем в точности и является композиция функций. Вот ещё один способ записи этого экземпляра:
instance Functor ((–>) r) where
fmap = (.)
Код наглядно показывает, что применение функции fmap
к функциям – это просто композиция функций.
В исходном коде импортируйте модуль Control.Monad.Instances
, поскольку это модуль, где определён данный экземпляр, а затем загрузите исходный код и попробуйте поиграть с отображением функций:
ghci> :t fmap (*3) (+100)
fmap (*3) (+100) :: (Num a) => a –> a
ghci> fmap (*3) (+100) 1
303
ghci> (*3) `fmap` (+100) $ 1
303
ghci> (*3) . (+100) $ 1
303
ghci> fmap (show . (*3)) (*100) 1
"300"
Мы можем вызывать fmap
как инфиксную функцию, чтобы сходство с оператором .
было явным. Во второй строке ввода мы отображаем (+100)
с помощью (*3)
, что даёт функцию, которая примет ввод, применит к нему (+100)
, а затем применит к этому результату (*3
). Затем мы применяем эту функцию к значению 1
.
Как и все функторы, функции могут восприниматься как значения с контекстами. Когда у нас есть функция вроде (+3)
, мы можем рассматривать значение как окончательный результат функции, а контекстом является то, что мы должны применить эту функцию к чему-либо, чтобы получить результат. Применение fmap (*3)
к (+100)
создаст ещё одну функцию, которая действует так же, как (+100)
, но перед возвратом результата к этому результату будет применена функция (*3)
.
Тот факт, что функция fmap
является композицией функций при применении к функциям, на данный момент не слишком нам полезен, но, по крайней мере, он вызывает интерес. Это несколько меняет наше сознание и позволяет нам увидеть, как сущности, которые действуют скорее как вычисления, чем как коробки ( IO
и (–>) r
), могут быть функторами. Отображение вычисления с помощью функции возвращает тот же самый тип вычисления, но результат этого вычисления изменён функцией.
Перед тем как перейти к законам, которым должна следовать fmap
, давайте ещё раз задумаемся о типе fmap
:
fmap :: (a –> b) –> f a –> f b
Если помните, введение в каррированные функции в главе 5 началось с утверждения, что все функции в языке Haskell на самом деле принимают один параметр. Функция a –> b –> c
в действительности берёт только один параметр типа a
, после чего возвращает функцию b –> c
, которая принимает один параметр типа b
и возвращает значение типа c
. Вот почему вызов функции с недостаточным количеством параметров (её частичное применение) возвращает нам обратно функцию, принимающую несколько параметров, которые мы пропустили (если мы опять воспринимаем функции так, как если бы они принимали несколько параметров). Поэтому a –> b –> c
можно записать в виде a –>
(b
–> c)
, чтобы сделать каррирование более очевидным.

Аналогичным образом, записав fmap :: (a –> b)
–>
(f
a
–>
f
b)
, мы можем воспринимать fmap
не как функцию, которая принимает одну функцию и значение функтора и возвращает значение функтора, но как функцию, которая принимает функцию и возвращает новую функцию, которая такая же, как и прежняя, за исключением того, что она принимает значение функтора в качестве параметра и возвращает значение функтора в качестве результата. Она принимает функцию типа a –> b
и возвращает функцию типа f a –> f b
. Это называется «втягивание функции» . Давайте реализуем эту идею, используя команду :t
в GHCi:
ghci> :t fmap (*2)
fmap (*2) :: (Num a, Functor f) => f a –> f a
ghci> :t fmap (replicate 3)
fmap (replicate 3) :: (Functor f) => f a –> f [a]
Интервал:
Закладка: