Миран Липовача - Изучай Haskell во имя добра!
- Название:Изучай Haskell во имя добра!
- Автор:
- Жанр:
- Издательство:ДМК Пресс
- Год:2012
- Город:Москва
- ISBN:978-5-94074-749-9
- Рейтинг:
- Избранное:Добавить в избранное
-
Отзывы:
-
Ваша оценка:
Миран Липовача - Изучай Haskell во имя добра! краткое содержание
Язык Haskell имеет множество впечатляющих возможностей, но главное его свойство в том, что меняется не только способ написания кода, но и сам способ размышления о проблемах и возможных решениях. Этим Haskell действительно отличается от большинства языков программирования. С его помощью мир можно представить и описать нестандартным образом. И поскольку Haskell предлагает совершенно новые способы размышления о проблемах, изучение этого языка может изменить и стиль программирования на всех прочих.
Ещё одно необычное свойство Haskell состоит в том, что в этом языке придаётся особое значение рассуждениям о типах данных. Как следствие, вы помещаете больше внимания и меньше кода в ваши программы.
Вне зависимости от того, в каком направлении вы намерены двигаться, путешествуя в мире программирования, небольшой заход в страну Haskell себя оправдает. А если вы решите там остаться, то наверняка найдёте чем заняться и чему поучиться!
Эта книга поможет многим читателям найти свой путь к Haskell.
Отображения, монады, моноиды и другое! Всё сказано в названии: «Изучай Хаскель во имя добра!» – весёлый иллюстрированный самоучитель по этому сложному функциональному языку.
С помощью оригинальных рисунков автора, отсылке к поп-культуре, и, самое главное, благодаря полезным примерам кода, эта книга обучает основам функционального программирования так, как вы никогда не смогли бы себе представить.
Вы начнете изучение с простого материала: основы синтаксиса, рекурсия, типы и классы типов. Затем, когда вы преуспеете в основах, начнется настоящий мастер-класс от профессионала: вы изучите, как использовать аппликативные функторы, монады, застежки, и другие легендарные конструкции Хаскеля, о которых вы читали только в сказках.
Продираясь сквозь образные (и порой безумные) примеры автора, вы научитесь:
• Смеяться в лицо побочным эффектам, поскольку вы овладеете техниками чистого функционального программирования.
• Использовать волшебство «ленивости» Хаскеля для игры с бесконечными наборами данных.
• Организовывать свои программы, создавая собственные типы, классы типов и модули.
• Использовать элегантную систему ввода-вывода Хаскеля, чтобы делиться гениальностью ваших программ с окружающим миром.
Нет лучшего способа изучить этот мощный язык, чем чтение «Изучай Хаскель во имя добра!», кроме, разве что, поедания мозга его создателей. Миран Липовача (Miran Lipovača) изучает информатику в Любляне (Словения). Помимо его любви к Хаскелю, ему нравится заниматься боксом, играть на бас-гитаре и, конечно же, рисовать. У него есть увлечение танцующими скелетами и числом 71, а когда он проходит через автоматические двери, он притворяется, что на самом деле открывает их силой своей мысли.
Изучай Haskell во имя добра! - читать онлайн бесплатно ознакомительный отрывок
Интервал:
Закладка:
Итак, при использовании операции >>=
мы как бы «склеиваем» друг с другом два вычисления, обладающих состоянием. Второе вычисление скрыто внутри функции, которая принимает результат предыдущего вычисления. Поскольку функции pop
и push
уже являются вычислениями с состоянием, легко обернуть их в обёртку State
:
import Control.Monad.State
pop :: State Stack Int
pop = state $ \(x:xs) –> (x, xs)
push :: Int –> State Stack ()
push a = state $ \xs –> ((), a:xs)
Обратите внимание, как мы задействовали функцию state
, чтобы обернуть функцию в конструктор newtype State
, не прибегая к использованию конструктора значения State
напрямую.
Функция pop
– уже вычисление с состоянием, а функция push
принимает значение типа Int
и возвращает вычисление с состоянием. Теперь мы можем переписать наш предыдущий пример проталкивания числа 3
в стек и выталкивания двух чисел подобным образом:
import Control.Monad.State
stackManip :: State Stack Int
stackManip = do
push 3
a <���– pop
pop
Видите, как мы «склеили» проталкивание и два выталкивания в одно вычисление с состоянием? Разворачивая его из обёртки newtype
, мы получаем функцию, которой можем предоставить некое исходное состояние:
ghci> runState stackManip [5,8,2,1]
(5,[8,2,1])
Нам не требовалось привязывать второй вызов функции pop
к образцу a
, потому что мы вовсе не использовали этот образец. Значит, это можно было записать вот так:
stackManip :: State Stack Int
stackManip = do
push 3
pop
pop
Очень круто! Но что если мы хотим сделать что-нибудь посложнее? Скажем, вытолкнуть из стека одно число, и если это число равно 5
, просто протолкнуть его обратно в стек и остановиться. Но если число не равно 5
, вместо этого протолкнуть обратно 3
и 8
. Вот он код:
stackStuff :: State Stack ()
stackStuff = do
a <���– pop
if a == 5
then push 5
else do
push 3
push 8
Довольно простое решение. Давайте выполним этот код с исходным стеком:
ghci> runState stackStuff [9,0,2,1,0] ((),[8,3,0,2,1,0])
Вспомните, что выражения do
возвращают в результате монадические значения, и при использовании монады State
одно выражение do
является также функцией с состоянием. Поскольку функции stackManip
и stackStuff
являются обычными вычислениями с состоянием, мы можем «склеивать» их вместе, чтобы производить дальнейшие вычисления с состоянием:
moreStack :: State Stack ()
moreStack = do
a <���– stackManip
if a == 100
then stackStuff
else return ()
Если результат функции stackManip
при использовании текущего стека равен 100
, мы вызываем функцию stackStuff
; в противном случае ничего не делаем. Вызов return
()
просто сохраняет состояние как есть и ничего не делает.
Получение и установка состояния
Модуль Control.Monad.State
определяет класс типов под названием MonadState
, в котором присутствуют две весьма полезные функции: get
и put
. Для монады State
функция get
реализована вот так:
get = state $ \s –> (s, s)
Она просто берёт текущее состояние и представляет его в качестве результата.
Функция put
принимает некоторое состояние и создаёт функцию с состоянием, которая заменяет им текущее состояние:
put newState = state $ \s –> ((), newState)
Поэтому, используя их, мы можем посмотреть, чему равен текущий стек, либо полностью заменить его другим стеком – например, так:
stackyStack :: State Stack ()
stackyStack = do
stackNow <���– get
if stackNow == [1,2,3]
then put [8,3,1]
else put [9,2,1]
Также можно использовать функции get
и put
, чтобы реализовать функции pop
и push
. Вот определение функции pop
:
pop :: State Stack Int
pop = do
(x:xs) <���– get
put xs
return x
Мы используем функцию get
, чтобы получить весь стек, а затем – функцию put
, чтобы новым состоянием были все элементы за исключением верхнего. После чего прибегаем к функции return
, чтобы представить значение x
в качестве результата.
Вот определение функции push
, реализованной с использованием get
и put
:
push :: Int –> State Stack ()
push x = do
xs <���– get
put (x:xs)
Мы просто используем функцию get
, чтобы получить текущее состояние, и функцию put
, чтобы установить состояние в такое же, как наш стек с элементом x
на вершине.
Стоит проверить, каким был бы тип операции >>=
, если бы она работала только со значениями монады State
:
(>>=) :: State s a –> (a –> State s b) –> State s b
Видите, как тип состояния s
остаётся тем же, но тип результата может изменяться с a
на b
? Это означает, что мы можем «склеивать» вместе несколько вычислений с состоянием, результаты которых имеют различные типы, но тип состояния должен оставаться тем же. Почему же так?.. Ну, например, для типа Maybe
операция >>=
имеет такой тип:
(>>=) :: Maybe a –> (a –> Maybe b) –> Maybe b
Логично, что сама монада Maybe
не изменяется. Не имело бы смысла использовать операцию >>=
между двумя разными монадами. Для монады State
монадой на самом деле является State s
, так что если бы этот тип s
был различным, мы использовали бы операцию >>=
между двумя разными монадами.
Случайность и монада State
В начале этого раздела мы говорили о том, что генерация случайных чисел может иногда быть неуклюжей. Каждая функция, использующая случайность, принимает генератор и возвращает случайное число вместе с новым генератором, который должен затем быть использован вместо прежнего, если нам нужно сгенерировать ещё одно случайное число. Монада State
намного упрощает эти действия.
Функция random
из модуля System.Random
имеет следующий тип:
random :: (RandomGen g, Random a) => g –> (a, g)
Это значит, что она берёт генератор случайных чисел и производит случайное число вместе с новым генератором. Нам видно, что это вычисление с состоянием, поэтому мы можем обернуть его в конструктор newtype State
при помощи функции state
, а затем использовать его в качестве монадического значения, чтобы передача состояния обрабатывалась за нас:
import System.Random
import Control.Monad.State
randomSt :: (RandomGen g, Random a) => State g a
randomSt = state random
Поэтому теперь, если мы хотим подбросить три монеты ( True
– это «решка», а False
– «орёл»), то просто делаем следующее:
import System.Random
import Control.Monad.State
threeCoins :: State StdGen (Bool, Bool, Bool)
Интервал:
Закладка: