Миран Липовача - Изучай Haskell во имя добра!
- Название:Изучай Haskell во имя добра!
- Автор:
- Жанр:
- Издательство:ДМК Пресс
- Год:2012
- Город:Москва
- ISBN:978-5-94074-749-9
- Рейтинг:
- Избранное:Добавить в избранное
-
Отзывы:
-
Ваша оценка:
Миран Липовача - Изучай Haskell во имя добра! краткое содержание
Язык Haskell имеет множество впечатляющих возможностей, но главное его свойство в том, что меняется не только способ написания кода, но и сам способ размышления о проблемах и возможных решениях. Этим Haskell действительно отличается от большинства языков программирования. С его помощью мир можно представить и описать нестандартным образом. И поскольку Haskell предлагает совершенно новые способы размышления о проблемах, изучение этого языка может изменить и стиль программирования на всех прочих.
Ещё одно необычное свойство Haskell состоит в том, что в этом языке придаётся особое значение рассуждениям о типах данных. Как следствие, вы помещаете больше внимания и меньше кода в ваши программы.
Вне зависимости от того, в каком направлении вы намерены двигаться, путешествуя в мире программирования, небольшой заход в страну Haskell себя оправдает. А если вы решите там остаться, то наверняка найдёте чем заняться и чему поучиться!
Эта книга поможет многим читателям найти свой путь к Haskell.
Отображения, монады, моноиды и другое! Всё сказано в названии: «Изучай Хаскель во имя добра!» – весёлый иллюстрированный самоучитель по этому сложному функциональному языку.
С помощью оригинальных рисунков автора, отсылке к поп-культуре, и, самое главное, благодаря полезным примерам кода, эта книга обучает основам функционального программирования так, как вы никогда не смогли бы себе представить.
Вы начнете изучение с простого материала: основы синтаксиса, рекурсия, типы и классы типов. Затем, когда вы преуспеете в основах, начнется настоящий мастер-класс от профессионала: вы изучите, как использовать аппликативные функторы, монады, застежки, и другие легендарные конструкции Хаскеля, о которых вы читали только в сказках.
Продираясь сквозь образные (и порой безумные) примеры автора, вы научитесь:
• Смеяться в лицо побочным эффектам, поскольку вы овладеете техниками чистого функционального программирования.
• Использовать волшебство «ленивости» Хаскеля для игры с бесконечными наборами данных.
• Организовывать свои программы, создавая собственные типы, классы типов и модули.
• Использовать элегантную систему ввода-вывода Хаскеля, чтобы делиться гениальностью ваших программ с окружающим миром.
Нет лучшего способа изучить этот мощный язык, чем чтение «Изучай Хаскель во имя добра!», кроме, разве что, поедания мозга его создателей. Миран Липовача (Miran Lipovača) изучает информатику в Любляне (Словения). Помимо его любви к Хаскелю, ему нравится заниматься боксом, играть на бас-гитаре и, конечно же, рисовать. У него есть увлечение танцующими скелетами и числом 71, а когда он проходит через автоматические двери, он притворяется, что на самом деле открывает их силой своей мысли.
Изучай Haskell во имя добра! - читать онлайн бесплатно ознакомительный отрывок
Интервал:
Закладка:
3 + (4 + (5 + (6 + 0)))
Или, если записать оператор +
как префиксную функцию, получится:
(+) 3 ((+) 4 ((+) 5 ((+) 6 0)))
Аналогичным образом левая свёртка с бинарной функцией g
и аккумулятором z
является эквивалентом выражения
g (g (g (g z 3) 4) 5) 6
Если заменить бинарную функцию на flip (:)
и использовать []
как аккумулятор (выполняем обращение списка), подобная запись эквивалентна следующей:
flip (:) (flip (:) (flip (:) (flip (:) [] 3) 4) 5) 6
Если вычислить это выражение, мы получим [6,5,4,3]
.
Свёртка бесконечных списков
Взгляд на свёртки как на последовательное применение функции к элементам списка помогает понять, почему правая свёртка иногда отлично работает с бесконечными списками. Давайте реализуем функцию and
с помощью foldr
, а потом выпишем последовательность применений, как мы это делали в предыдущих примерах. Тогда мы увидим, как ленивость языка Haskell позволяет правой свёртке обрабатывать бесконечные списки.
Функция and
принимает список значений типа Bool
и возвращает False
, если хотя бы один из элементов равен False
; в противном случае она возвращает True
. Мы будем обходить список справа, используя True
как начальное значение. В качестве бинарной функции будем использовать операцию &&
, потому что должны вернуть True
только в том случае, когда все элементы списка истинны. Функция &&
возвращает False
, если хотя бы один из параметров равен False
, поэтому если мы встретим в списке False
, то аккумулятор будет установлен в значение False
и окончательный результат также будет False
, даже если среди оставшихся элементов списка обнаружатся истинные значения.
and' :: [Bool] -> Bool
and' xs = foldr (&&) True xs
Зная, как работает foldr
, мы видим, что выражение and' [True,False,True]
будет вычисляться следующим образом:
True && (False && (True && True))
Последнее True
здесь – это начальное значение аккумулятора, тогда как первые три логических значения взяты из списка [True,False,True]
. Если мы попробуем вычислить результат этого выражения, получится False
.
А что если попробовать то же самое с бесконечным списком, скажем, repeat False
? Если мы выпишем соответствующие применения, то получится вот что:
False && (False && (False && (False …
Ленивость Haskell позволит вычислить только то, что действительно необходимо. Функция &&
устроена таким образом, что если её первый параметр False
, то второй просто игнорируется, поскольку и так ясно, что результат должен быть False
.
Функция foldr
будет работать с бесконечными списками, если бинарная функция, которую мы ей передаём, не требует обязательного вычисления второго параметра, если значения первого ей достаточно для вычисления результата. Такова функция &&
– ей неважно, каков второй параметр, при условии, что первый — False
.
Сканирование
Функции scanl
и scanr
похожи на foldl
и foldr
, только они сохраняют все промежуточные значения аккумулятора в список. Также существуют функции scanl1
и scanr1
, которые являются аналогами foldl1
и foldr1
.
ghci> scanl (+) 0 [3,5,2,1]
[0,3,8,10,11]
ghci> scanr (+) 0 [3,5,2,1]
[11,8,3,1,0]
ghci> scanl1 (\acc x –> if x > acc then x else acc) [3,4,5,3,7,9,2,1]
[3,4,5,5,7,9,9,9]
ghci> scanl (flip (:)) [] [3,2,1]
[[],[3],[2,3],[1,2,3]]
При использовании функции scanl
финальный результат окажется в последнем элементе итогового списка, тогда как функция scanr
поместит результат в первый элемент.
Функции сканирования используются для того, чтобы увидеть, как работают функции, которые можно реализовать как свёртки. Давайте ответим на вопрос: как много корней натуральных чисел нам потребуется, чтобы их сумма превысила 1000? Чтобы получить сумму квадратов натуральных чисел, воспользуемся map sqrt [1..]
. Теперь, чтобы получить сумму, прибегнем к помощи свёртки, но поскольку нам интересно знать, как увеличивается сумма, будем вызывать функцию scanl1
. После вызова scanl1
посмотрим, сколько элементов не превышают 1000. Первый элемент в результате работы функции scanl1
должен быть равен единице. Второй будет равен 1 плюс квадратный корень двух. Третий элемент – это корень трёх плюс второй элемент. Если у нас x сумм меньших 1000, то нам потребовалось ( x +1) элементов, чтобы превзойти 1000.
sqrtSums :: Int
sqrtSums = length (takeWhile (< 1000) (scanl1 (+) (map sqrt [1..]))) + 1
ghci> sqrtSums
131
ghci> sum (map sqrt [1..131])
1005.0942035344083
ghci> sum (map sqrt [1..130])
993.6486803921487
Мы задействовали функцию takeWhile
вместо filter
, потому что последняя не работает на бесконечных списках. В отличие от нас, функция filter
не знает, что список возрастает, поэтому мы используем takeWhile
, чтобы отсечь список, как только сумма превысит 1000.
Применение функций с помощью оператора $

Пойдём дальше. Теперь объектом нашего внимания станет оператор $
, также называемый аппликатором функций . Прежде всего посмотрим, как он определяется:
($) :: (a –> b) –> a –> b
f $ x = f x
Зачем? Что это за бессмысленный оператор? Это просто применение функции! Верно, почти , но не совсем!.. В то время как обычное применение функции (с пробелом) имеет высший приоритет, оператор $
имеет самый низкий приоритет. Применение функции с пробелом левоассоциативно (то есть f a b c i
– это то же самое, что (((f a) b) c))
, в то время как применение функции при помощи оператора $
правоассоциативно.
Всё это прекрасно, но нам-то с того какая польза? Прежде всего оператор $
удобен тем, что с ним не приходится записывать много вложенных скобок. Рассмотрим выражение sum (map sqrt [1..130])
. Поскольку оператор $
имеет самый низкий приоритет, мы можем переписать это выражение как sum $ map sqrt [1..130]
, сэкономив драгоценные нажатия на клавиши. Когда в функции встречается знак $
, выражение справа от него используется как параметр для функции слева от него. Как насчёт sqrt 3 + 4 + 9
? Здесь складываются 9
, 4
и корень из 3
. Если мы хотим получить квадратный корень суммы, нам надо написать sqrt (3 + 4 + 9)
– или же (в случае использования оператора $
) sqrt $ 3 + 4 + 9
, потому что у оператора $
низший приоритет среди всех операторов. Вот почему вы можете представить символ $
как эквивалент записи открывающей скобки с добавлением закрывающей скобки в крайней правой позиции выражения.
Интервал:
Закладка: