Миран Липовача - Изучай Haskell во имя добра!
- Название:Изучай Haskell во имя добра!
- Автор:
- Жанр:
- Издательство:ДМК Пресс
- Год:2012
- Город:Москва
- ISBN:978-5-94074-749-9
- Рейтинг:
- Избранное:Добавить в избранное
-
Отзывы:
-
Ваша оценка:
Миран Липовача - Изучай Haskell во имя добра! краткое содержание
Язык Haskell имеет множество впечатляющих возможностей, но главное его свойство в том, что меняется не только способ написания кода, но и сам способ размышления о проблемах и возможных решениях. Этим Haskell действительно отличается от большинства языков программирования. С его помощью мир можно представить и описать нестандартным образом. И поскольку Haskell предлагает совершенно новые способы размышления о проблемах, изучение этого языка может изменить и стиль программирования на всех прочих.
Ещё одно необычное свойство Haskell состоит в том, что в этом языке придаётся особое значение рассуждениям о типах данных. Как следствие, вы помещаете больше внимания и меньше кода в ваши программы.
Вне зависимости от того, в каком направлении вы намерены двигаться, путешествуя в мире программирования, небольшой заход в страну Haskell себя оправдает. А если вы решите там остаться, то наверняка найдёте чем заняться и чему поучиться!
Эта книга поможет многим читателям найти свой путь к Haskell.
Отображения, монады, моноиды и другое! Всё сказано в названии: «Изучай Хаскель во имя добра!» – весёлый иллюстрированный самоучитель по этому сложному функциональному языку.
С помощью оригинальных рисунков автора, отсылке к поп-культуре, и, самое главное, благодаря полезным примерам кода, эта книга обучает основам функционального программирования так, как вы никогда не смогли бы себе представить.
Вы начнете изучение с простого материала: основы синтаксиса, рекурсия, типы и классы типов. Затем, когда вы преуспеете в основах, начнется настоящий мастер-класс от профессионала: вы изучите, как использовать аппликативные функторы, монады, застежки, и другие легендарные конструкции Хаскеля, о которых вы читали только в сказках.
Продираясь сквозь образные (и порой безумные) примеры автора, вы научитесь:
• Смеяться в лицо побочным эффектам, поскольку вы овладеете техниками чистого функционального программирования.
• Использовать волшебство «ленивости» Хаскеля для игры с бесконечными наборами данных.
• Организовывать свои программы, создавая собственные типы, классы типов и модули.
• Использовать элегантную систему ввода-вывода Хаскеля, чтобы делиться гениальностью ваших программ с окружающим миром.
Нет лучшего способа изучить этот мощный язык, чем чтение «Изучай Хаскель во имя добра!», кроме, разве что, поедания мозга его создателей. Миран Липовача (Miran Lipovača) изучает информатику в Любляне (Словения). Помимо его любви к Хаскелю, ему нравится заниматься боксом, играть на бас-гитаре и, конечно же, рисовать. У него есть увлечение танцующими скелетами и числом 71, а когда он проходит через автоматические двери, он притворяется, что на самом деле открывает их силой своей мысли.
Изучай Haskell во имя добра! - читать онлайн бесплатно ознакомительный отрывок
Интервал:
Закладка:
(True,"Бешеный взвод.Размер банды сравнён с 9.")
Результаты аналогичны предшествующим, только теперь количеству бандитов сопутствует журнал, который включён в окончательный журнал.
Вот ещё несколько примеров использования applyLog
:
ghci> ("Тобин","Вне закона.") `applyLog` (\x –> (length x "Длина."))
(5,"Вне закона.Длина.")
ghci> ("Котопёс","Вне закона.") `applyLog` (\x –> (length x "Длина."))
(7,"Вне закона.Длина.")
Смотрите, как внутри анонимной функции образец x
является просто нормальной строкой, а не кортежем, и как функция applyLog
заботится о добавлении записей журнала.
Моноиды приходят на помощь
Убедитесь, что вы на данный момент знаете, что такое моноиды!
Прямо сейчас функция applyLog
принимает значения типа (a,String)
, но есть ли смысл в том, чтобы тип журнала был String
? Он использует операцию ++ для добавления записей журнала – не будет ли это работать и в отношении любого типа списков, не только списка символов? Конечно же, будет! Мы можем пойти дальше и изменить тип этой функции на следующий:
applyLog :: (a,[c]) –> (a –> (b,[c])) –> (b,[c])
Теперь журнал является списком. Тип значений, содержащихся в списке, должен быть одинаковым как для изначального списка, так и для списка, который возвращает функция; в противном случае мы не смогли бы использовать операцию ++
для «склеивания» их друг с другом.
Сработало бы это для строк байтов? Нет причины, по которой это не сработало бы! Однако тип, который у нас имеется, работает только со списками. Похоже, что нам пришлось бы создать ещё одну функцию applyLog
для строк байтов. Но подождите! И списки, и строки байтов являются моноидами. По существу, те и другие являются экземплярами класса типов Monoid
, а это значит, что они реализуют функцию mappend
. Как для списков, так и для строк байтов функция mappend
производит конкатенацию. Смотрите:
ghci> [1,2,3] `mappend` [4,5,6]
[1,2,3,4,5,6]
ghci> B.pack [99,104,105] `mappend` B.pack [104,117,97,104,117,97]
Chunk "chi" (Chunk "huahua" Empty)
Круто! Теперь наша функция applyLog
может работать для любого моноида. Мы должны изменить тип, чтобы отразить это, а также реализацию, потому что следует заменить вызов операции ++
вызовом функции mappend
:
applyLog :: (Monoid m) => (a,m) –> (a –> (b,m)) –> (b,m)
applyLog (x,log) f = let (y,newLog) = f x
in (y,log `mappend` newLog)
Поскольку сопутствующее значение теперь может быть любым моноидным значением, нам больше не нужно думать о кортеже как о значении и журнале, но мы можем думать о нём как о значении с сопутствующим моноидным значением. Например, у нас может быть кортеж, в котором есть имя предмета и цена предмета в виде моноидного значения. Мы просто используем определение типа newtype Sum
, чтобы быть уверенными, что цены добавляются, пока мы работаем с предметами. Вот функция, которая добавляет напиток к обеду какого-то ковбоя:
import Data.Monoid
type Food = String
type Price = Sum Int
addDrink :: Food –> (Food,Price)
addDrink "бобы" = ("молоко", Sum 25)
addDrink "вяленое мясо" = ("виски", Sum 99)
addDrink _ = ("пиво", Sum 30)
Мы используем строки для представления продуктов и тип Int
в обёртке типа newtype Sum
для отслеживания того, сколько центов стоит тот или иной продукт. Просто напомню: выполнение функции mappend
для значений типа Sum
возвращает сумму обёрнутых значений.
ghci> Sum 3 `mappend` Sum 9
Sum {getSum = 12}
Функция addDrink
довольно проста. Если мы едим бобы, она возвращает "молоко"
вместе с Sum
25
; таким образом, 25
центов завёрнуты в конструктор Sum
. Если мы едим вяленое мясо, то пьём виски, а если едим что-то другое – пьём пиво. Обычное применение этой функции к продукту сейчас было бы не слишком интересно, а вот использование функции applyLog
для передачи продукта с указанием цены в саму функцию представляет интерес:
ghci> ("бобы", Sum 10) `applyLog` addDrink
("молоко",Sum {getSum = 35})
ghci> ("вяленое мясо", Sum 25) `applyLog` addDrink
("виски",Sum {getSum = 124})
ghci> ("собачатина", Sum 5) `applyLog` addDrink
("пиво",Sum {getSum = 35})
Молоко стоит 25 центов, но если мы заедаем его бобами за 10 центов, это обходится нам в 35 центов. Теперь ясно, почему присоединённое значение не всегда должно быть журналом – оно может быть любым моноидным значением, и то, как эти два значения объединяются, зависит от моноида. Когда мы производили записи в журнал, они присоединялись в конец, но теперь происходит сложение чисел.
Поскольку значение, возвращаемое функцией addDrink
, является кортежем типа (Food,Price)
, мы можем передать этот результат функции addDrink
ещё раз, чтобы функция сообщила нам, какой напиток будет подан в сопровождение к блюду и сколько это нам будет стоить. Давайте попробуем:
ghci> ("собачатина", Sum 5) `applyLog` addDrink `applyLog` addDrink
("пиво",Sum {getSum = 65})
Добавление напитка к какой-нибудь там собачатине вернёт пиво и дополнительные 30 центов, то есть ("пиво", Sum 35)
. А если мы используем функцию applyLog
для передачи этого результата функции addDrink
, то получим ещё одно пиво, и результатом будет ("пиво", Sum 65)
.
Тип Writer
Теперь, когда мы увидели, что значение с присоединённым моноидом ведёт себя как монадическое значение, давайте исследуем экземпляр класса Monad
для типов таких значений. Модуль Control.Monad.Writer
экспортирует тип Writer w a
со своим экземпляром класса Monad
и некоторые полезные функции для работы со значениями такого типа.
Прежде всего, давайте исследуем сам тип. Для присоединения моноида к значению нам достаточно поместить их в один кортеж. Тип Writer w a
является просто обёрткой newtype
для кортежа. Его определение несложно:
newtype Writer w a = Writer { runWriter :: (a, w) }
Чтобы кортеж мог быть сделан экземпляром класса Monad
и его тип был отделён от обычного кортежа, он обёрнут в newtype
. Параметр типа a
представляет тип значения, параметр типа w
– тип присоединённого значения моноида.
Экземпляр класса Monad
для этого типа определён следующим образом:
instance (Monoid w) => Monad (Writer w) where
return x = Writer (x, mempty)
(Writer (x,v)) >>= f = let (Writer (y, v')) = f x
in Writer (y, v `mappend` v')

Во-первых, давайте рассмотрим операцию >>=
. Её реализация по существу аналогична функции applyLog
, только теперь, поскольку наш кортеж обёрнут в тип newtype Writer
, мы должны развернуть его перед сопоставлением с образцом. Мы берём значение x
и применяем к нему функцию f
. Это даёт нам новое значение Writer
w
a
, и мы используем выражение let
для сопоставления его с образцом. Представляем y
в качестве нового результата и используем функцию mappend
для объединения старого моноидного значения с новым. Упаковываем его вместе с результирующим значением в кортеж, а затем оборачиваем с помощью конструктора Writer
, чтобы нашим результатом было значение Writer
, а не просто необёрнутый кортеж.
Интервал:
Закладка: