Миран Липовача - Изучай Haskell во имя добра!
- Название:Изучай Haskell во имя добра!
- Автор:
- Жанр:
- Издательство:ДМК Пресс
- Год:2012
- Город:Москва
- ISBN:978-5-94074-749-9
- Рейтинг:
- Избранное:Добавить в избранное
-
Отзывы:
-
Ваша оценка:
Миран Липовача - Изучай Haskell во имя добра! краткое содержание
Язык Haskell имеет множество впечатляющих возможностей, но главное его свойство в том, что меняется не только способ написания кода, но и сам способ размышления о проблемах и возможных решениях. Этим Haskell действительно отличается от большинства языков программирования. С его помощью мир можно представить и описать нестандартным образом. И поскольку Haskell предлагает совершенно новые способы размышления о проблемах, изучение этого языка может изменить и стиль программирования на всех прочих.
Ещё одно необычное свойство Haskell состоит в том, что в этом языке придаётся особое значение рассуждениям о типах данных. Как следствие, вы помещаете больше внимания и меньше кода в ваши программы.
Вне зависимости от того, в каком направлении вы намерены двигаться, путешествуя в мире программирования, небольшой заход в страну Haskell себя оправдает. А если вы решите там остаться, то наверняка найдёте чем заняться и чему поучиться!
Эта книга поможет многим читателям найти свой путь к Haskell.
Отображения, монады, моноиды и другое! Всё сказано в названии: «Изучай Хаскель во имя добра!» – весёлый иллюстрированный самоучитель по этому сложному функциональному языку.
С помощью оригинальных рисунков автора, отсылке к поп-культуре, и, самое главное, благодаря полезным примерам кода, эта книга обучает основам функционального программирования так, как вы никогда не смогли бы себе представить.
Вы начнете изучение с простого материала: основы синтаксиса, рекурсия, типы и классы типов. Затем, когда вы преуспеете в основах, начнется настоящий мастер-класс от профессионала: вы изучите, как использовать аппликативные функторы, монады, застежки, и другие легендарные конструкции Хаскеля, о которых вы читали только в сказках.
Продираясь сквозь образные (и порой безумные) примеры автора, вы научитесь:
• Смеяться в лицо побочным эффектам, поскольку вы овладеете техниками чистого функционального программирования.
• Использовать волшебство «ленивости» Хаскеля для игры с бесконечными наборами данных.
• Организовывать свои программы, создавая собственные типы, классы типов и модули.
• Использовать элегантную систему ввода-вывода Хаскеля, чтобы делиться гениальностью ваших программ с окружающим миром.
Нет лучшего способа изучить этот мощный язык, чем чтение «Изучай Хаскель во имя добра!», кроме, разве что, поедания мозга его создателей. Миран Липовача (Miran Lipovača) изучает информатику в Любляне (Словения). Помимо его любви к Хаскелю, ему нравится заниматься боксом, играть на бас-гитаре и, конечно же, рисовать. У него есть увлечение танцующими скелетами и числом 71, а когда он проходит через автоматические двери, он притворяется, что на самом деле открывает их силой своей мысли.
Изучай Haskell во имя добра! - читать онлайн бесплатно ознакомительный отрывок
Интервал:
Закладка:
ghci> :k Maybe Int
Maybe Int :: *
Так я и думал! Мы применили тип-параметр к типу Maybe
и получили конкретный тип. Можно провести параллель (но не отождествление: типы – это не то же самое, что и сорта) с тем, как если бы мы сделали :t isUpper
и :t isUpper 'A'
. У функции isUpper
тип Char –> Bool
; выражение isUpper 'A'
имеет тип Bool
, потому что его значение – просто False
. Сорт обоих типов, тем не менее, *
.
Мы используем команду :k
для типов, чтобы получить их сорт, так же как используем команду :t
для значений, чтобы получить их тип. Выше уже было сказано, что типы – это метки значений, а сорта – это метки типов; и в этом они схожи.
Посмотрим на другие сорта.
ghci> :k Either
Either :: * –> * –> *
Это говорит о том, что тип Either
принимает два конкретных типа для того, чтобы вернуть конкретный тип. Выглядит как декларация функции, которая принимает два значения и что-то возвращает. Конструкторы типов являются каррированными (так же, как и функции), поэтому мы можем частично применять их.
ghci> :k Either String
Either String :: * –> *
ghci> :k Either String Int
Either String Int :: *
Когда нам нужно было сделать для типа Either
экземпляр класса Functor
, пришлось частично применить его, потому что класс Functor
принимает типы только с одним параметром, в то время как у типа Either
их два. Другими словами, класс Functor
принимает типы сорта * –> *
, и нам пришлось частично применить тип Either
для того, чтобы получить сорт * –> *
из исходного сорта * –> * –> *
. Если мы посмотрим на определение класса Functor
ещё раз:
class Functor f where
fmap :: (a –> b) –> f a –> f b
то увидим, что переменная типа f
используется как тип, принимающий один конкретный тип для того, чтобы создать другой. Мы знаем, что возвращается конкретный тип, поскольку он используется как тип значения в функции. Из этого можно заключить, что типы, которые могут «подружиться» с классом Functor
, должны иметь сорт *
–>
*
.
Ну а теперь займёмся тип-фу. Посмотрим на определение такого класса типов:
class Tofu t where
tofu :: j a –> t a j
Объявление выглядит странно. Как мы могли бы создать тип, который будет иметь экземпляр такого класса? Посмотрим, каким должен быть сорт типа. Так как тип j a
используется как тип значения, который функция tofu
принимает как параметр, у типа j a
должен быть сорт *. Мы предполагаем сорт *
для типа a
и, таким образом, можем вывести, что тип j
должен быть сорта * –> *
. Мы видим, что тип t
также должен производить конкретный тип, и что он принимает два типа. Принимая во внимание, что у типа a
сорт *
и у типа j
сорт * –> *
, мы выводим, что тип t
должен быть сорта * –> (* –> *) –> *
. Итак, он принимает конкретный тип (a)
и конструктор типа, который принимает один конкретный тип (j),
и производит конкретный тип. Вау!
Хорошо, давайте создадим тип такого сорта: * –> (* –> *) –> *
. Вот один из вариантов:
data Frank a b = Frank {frankField :: b a} deriving (Show)
Откуда мы знаем, что этот тип имеет сорт * –> (* –> *) – > *
? Именованные поля в алгебраических типах данных сделаны для того, чтобы хранить значения, так что они по определению должны иметь сорт *
. Мы предполагаем сорт *
для типа a
; это означает, что тип b
принимает один тип как параметр. Таким образом, его сорт – * –>
*
. Теперь мы знаем сорта типов a
и b
; так как они являются параметрами для типа Frank
, можно показать, что тип Frank
имеет сорт * –> (* –> *) – > *
. Первая *
обозначает сорт типа a
; (*
–> *)
обозначает сорт типа b
. Давайте создадим несколько значений типа Frank
и проверим их типы.
ghci> :t Frank {frankField = Just "ХА-ХА"}
Frank {frankField = Just "ХА-ХА"} :: Frank [Char] Maybe
ghci> :t Frank {frankField = Node 'a' EmptyTree EmptyTree}
Frank {frankField = Node 'a' EmptyTree EmptyTree} :: Frank Char Tree
ghci> :t Frank {frankField = "ДА"}
Frank {frankField = "ДА"} :: Frank Char []
Гм-м-м… Так как поле frankField
имеет тип вида a b
, его значения должны иметь типы похожего вида. Например, это может быть Just "ХА-ХА"
, тип в этом примере – Maybe [Char]
, или ['Д','А']
(тип [Char]
; если бы мы использовали наш собственный тип для списка, это был бы List Char
). Мы видим, что значения типа Frank
соответствуют сорту типа Frank
. Сорт [Char]
– это *
, тип Maybe
имеет сорт * –> *
. Так как мы можем создать значение только конкретного типа и тип значения должен быть полностью определён, каждое значение типа Frank
имеет сорт *
.
Сделать для типа Frank
экземпляр класса Tofu
довольно просто. Мы видим, что функция tofu
принимает значение типа a j
(примером для типа такой формы может быть Maybe Int
) и возвращает значение типа t a j
. Если мы заменим тип Frank
на t
, результирующий тип будет Frank
Int
Maybe
.
instance Tofu Frank where
tofu x = Frank x
Проверяем типы:
ghci> tofu (Just 'a') :: Frank Char Maybe
Frank {frankField = Just 'a'}
ghci> tofu ["ПРИВЕТ"] :: Frank [Char] []
Frank {frankField = ["ПРИВЕТ"]}
Пусть и без особой практической пользы, но мы потренировали наше понимание типов. Давайте сделаем ещё несколько упражнений из тип-фу. У нас есть такой тип данных:
data Barry t k p = Barry { yabba :: p, dabba :: t k }
Ну а теперь определим для него экземпляр класса Functor
. Класс Functor
принимает типы сорта *
–>
*
, но непохоже, что у типа Barry
такой сорт. Каков же сорт у типа Barry
? Мы видим, что он принимает три типа-параметра, так что его сорт будет похож на ( нечто –> нечто –> нечто –> *)
. Наверняка тип p
– конкретный; он имеет сорт *
. Для типа k
мы предполагаем сорт *
; следовательно, тип t
имеет сорт * –> *
. Теперь соединим всё в одну цепочку и получим, что тип Barry
имеет сорт (* –> *) –> * –> * –> *
. Давайте проверим это в интерпретаторе GHCi:
ghci> :k Barry
Barry :: (* –> *) –> * –> * –> *
Ага, мы были правы. Как приятно! Чтобы сделать для типа Barry
экземпляр класса Functor
, мы должны частично применить первые два параметра, после чего у нас останется сорт * –> *
. Следовательно, начало декларации экземпляра будет таким:
instance Functor (Barry a b) where
Если бы функция fmap
была написана специально для типа Barry
, она бы имела тип
fmap :: (a –> b) –> Barry c d a –> Barry c d b
Здесь тип-параметр f
просто заменён частично применённым типом Barry c d
. Третий параметр типа Barry
должен измениться, и мы видим, что это удобно сделать таким образом:
Интервал:
Закладка: