Изучай Haskell во имя добра! - Миран Липовача
- Дата:07.05.2025
- Категория: Компьютеры и Интернет / Программирование
- Название: Изучай Haskell во имя добра!
- Автор: Миран Липовача
- Просмотров:0
- Комментариев:0
Аудиокнига "Изучай Haskell во имя добра!"
📚 Хотите погрузиться в мир функционального программирования и освоить новый язык программирования? Тогда аудиокнига "Изучай Haskell во имя добра!" от автора Мирана Липовача - это то, что вам нужно!
Главный герой книги, начинающий программист, отправляется в увлекательное путешествие по Haskell, чтобы понять его принципы и особенности. Слушая эту аудиокнигу, вы узнаете, как создавать функциональные программы, работать с типами данных, рекурсией и многим другим.
Автор книги, Миран Липовач, является экспертом в области функционального программирования и преподавателем. Он делится своими знаниями и опытом, помогая слушателям легко освоить новый язык программирования.
На сайте knigi-online.info вы можете бесплатно и без регистрации слушать аудиокниги на русском языке. Здесь собраны бестселлеры и лучшие произведения различных жанров, включая программирование.
Не упустите возможность познакомиться с увлекательным миром Haskell и функционального программирования через аудиокнигу "Изучай Haskell во имя добра!" от Мирана Липовача. Погрузитесь в новые знания и расширьте свой кругозор!
Погрузитесь в мир программирования с категорией аудиокниг: Программирование.
Шрифт:
Интервал:
Закладка:
Каковы шансы появления каждой из этих букв? Если бы мы должны были изобразить просто четыре коробки, каждая из которых содержит вероятность, какими были бы эти вероятности? Чтобы узнать это, достаточно умножить каждую вероятность на все вероятности, которые в ней содержатся. Значение 'a' появилось бы один раз из восьми, как и 'b', потому что если мы умножим одну четвёртую на одну четвёртую, то получим одну восьмую. Значение 'c' появилось бы три раза из восьми, потому что три четвёртых, умноженные на одну вторую, – это три восьмых. Значение 'd' также появилось бы три раза из восьми. Если мы сложим все вероятности, они по-прежнему будут давать в сумме единицу.
Вот эта ситуация, выраженная в форме списка вероятностей:
thisSituation :: Prob (Prob Char)
thisSituation = Prob
[(Prob [('a',1 % 2),('b',1 % 2)], 1 % 4)
,(Prob [('c',1 % 2),('d',1 % 2)], 3 % 4)
]
Обратите внимание, её тип – Prob (Prob Char). Поэтому теперь, когда мы поняли, как разгладить вложенный список вероятностей, всё, что нам нужно сделать, – написать для этого код. Затем мы можем определить операцию >>= просто как join (fmap f m), и заполучим монаду! Итак, вот функция flatten, которую мы будем использовать, потому что имя join уже занято:
flatten :: Prob (Prob a) –> Prob a
flatten (Prob xs) = Prob $ concat $ map multAll xs
where multAll (Prob innerxs, p) = map ((x, r) –> (x, p*r)) innerxs
Функция multAll принимает кортеж, состоящий из списка вероятностей и вероятности p, которая к нему приложена, а затем умножает каждую внутреннюю вероятность на p, возвращая список пар элементов и вероятностей. Мы отображаем каждую пару в нашем списке вероятностей с помощью функции multAll, а затем просто разглаживаем результирующий вложенный список.
Теперь у нас есть всё, что нам нужно. Мы можем написать экземпляр класса Monad!
instance Monad Prob where
return x = Prob [(x,1 % 1)]
m >>= f = flatten (fmap f m)
fail _ = Prob []
Поскольку мы уже сделали всю тяжелую работу, экземпляр очень прост. Мы определили функцию fail, которая такова же, как и для списков, поэтому если при сопоставлении с образцом в выражении do происходит неудача, неудача случается в контексте списка вероятностей.
Важно также проверить, что для только что созданной нами монады выполняются законы монад:
1. Первое правило говорит, что выражение return x >>= f должно равняться выражению f x. Точное доказательство было бы довольно громоздким, но нам видно, что если мы поместим значение в контекст по умолчанию с помощью функции return, затем отобразим это с помощью функции, используя fmap, а потом отобразим результирующий список вероятностей, то каждая вероятность, являющаяся результатом функции, была бы умножена на вероятность 1 % 1, которую мы создали с помощью функции return, так что это не повлияло бы на контекст.
2. Второе правило утверждает, что выражение m >> return ничем не отличается от m. Для нашего примера доказательство того, что выражение m >> return равно просто m, аналогично доказательству первого закона.
3. Третий закон утверждает, что выражение f <=< (g <=< h) должно быть аналогично выражению (f <=< g) <=< h. Это тоже верно, потому что данное правило выполняется для списковой монады, которая составляет основу для монады вероятностей, и потому что умножение ассоциативно. 1 % 2 * (1 % 3 * 1 % 5) равно (1 % 2 * 1 % 3) * 1 % 5.
Теперь, когда у нас есть монада, что мы можем с ней делать? Она может помочь нам выполнять вычисления с вероятностями. Мы можем обрабатывать вероятностные события как значения с контекстами, а монада вероятностей обеспечит отражение этих вероятностей в вероятностях окончательного результата.
Скажем, у нас есть две обычные монеты и одна монета, с одной стороны налитая свинцом: она поразительным образом выпадает на решку девять раз из десяти и на орла – лишь один раз из десяти. Если мы подбросим все монеты одновременно, какова вероятность того, что все они выпадут на решку? Во-первых, давайте создадим значения вероятностей для подбрасывания обычной монеты и для монеты, налитой свинцом:
data Coin = Heads | Tails deriving (Show, Eq)
coin :: Prob Coin
coin = Prob [(Heads,1 % 2),(Tails,1 % 2)]
loadedCoin :: Prob Coin
loadedCoin = Prob [(Heads,1 % 10),(Tails,9 % 10)]
И наконец, действие по подбрасыванию монет:
import Data.List (all)
flipThree :: Prob Bool
flipThree = do
a <– coin
b <– coin
c <– loadedCoin
return (all (==Tails) [a,b,c])
При попытке запустить его видно, что вероятность выпадения решки у всех трёх монет не так высока, даже несмотря на жульничество с нашей налитой свинцом монетой:
ghci> getProb flipThree
[(False,1 % 40),(False,9 % 40),(False,1 % 40),(False,9 % 40),
(False,1 % 40),(False,9 % 40),(False,1 % 40),(True,9 % 40)]
Все три приземлятся решкой вверх 9 раз из 40, что составляет менее 25%!.. Видно, что наша монада не знает, как соединить все исходы False, где все монеты не приземляются решкой вверх, в один исход. Впрочем, это не такая серьёзная проблема, поскольку написание функции для вставки всех одинаковых исходов в один исход довольно просто (это упражнение я оставляю вам в качестве домашнего задания).
В этом разделе мы перешли от вопроса («Что если бы списки также содержали информацию о вероятностях?») к созданию типа, распознанию монады и, наконец, созданию экземпляра и выполнению с ним некоторых действий. Думаю, это очаровательно! К этому времени у вас уже должно сложиться довольно неплохое понимание монад и их сути.
15
Застёжки
Хотя чистота языка Haskell даёт море преимуществ, вместе с тем он заставляет нас решать некоторые проблемы не так, как мы решали бы их в нечистых языках.
Из-за прозрачности ссылок одно значение в языке Haskell всё равно что другое, если оно представляет то же самое. Поэтому если у нас есть дерево, заполненное пятёрками (или, может, пятернями?), и мы хотим изменить одну из них на шестёрку, мы должны каким-то образом понимать, какую именно пятёрку в нашем дереве мы хотим изменить. Нам нужно знать, где в нашем дереве она находится. В нечистых языках можно было бы просто указать, где в памяти находится пятёрка, и изменить её. Но в языке Haskell одна пятёрка – всё равно что другая, поэтому нельзя проводить различие исходя из их расположения в памяти.
К тому же на самом деле мы не можем что-либо изменять. Когда мы говорим, что «изменяем дерево», то на самом деле имеем в виду, что мы берём дерево и возвращаем новое, аналогичное первоначальному, но немного отличающееся.
Одна вещь, которую мы можем сделать, – запомнить путь от корня дерева до элемента, который следует изменить. Мы могли бы сказать: «Возьми это дерево, пойди влево, пойди вправо, а затем опять влево и измени находящийся там элемент». Хотя это и работает, но может быть неэффективно. Если позднее мы захотим изменить элемент, находящийся рядом с элементом, изменённым нами ранее, нам снова нужно будет пройти весь путь от корня дерева до нашего элемента!
В этой главе вы увидите, как взять некую структуру данных и снабдить её тем, что называется застёжкой, чтобы фокусироваться на части структуры данных таким образом, который делает изменение её элементов простым, а прохождение по ней – эффективным. Славно!
Прогулка
Как вы узнали на уроках биологии, есть множество различных деревьев, поэтому давайте выберем зёрнышко, которое мы используем, чтобы посадить наше. Вот оно:
data Tree a = Empty | Node a (Tree a) (Tree a) deriving (Show)
Наше дерево или пусто, или является узлом, содержащим элемент и два поддерева. Вот хороший пример такого дерева, которое я отдаю вам, читатель, просто задаром!
freeTree :: Tree Char
freeTree =
Node 'P'
(Node 'O'
(Node 'L'
(Node 'N' Empty Empty)
(Node 'T' Empty Empty)
)
(Node 'Y'
(Node 'S' Empty Empty)
(Node 'A' Empty Empty)
)
)
(Node 'L'
(Node 'W'
(Node 'C' Empty Empty)
(Node 'R' Empty Empty)
)
(Node 'A'
(Node 'A' Empty Empty)
(Node 'C' Empty Empty)
)
)
А вот это дерево, представленное графически:
Заметили символ W в дереве? Предположим, мы хотим заменить его символом P. Как нам это сделать? Ну, один из подходящих способов – сопоставление нашего дерева с образцом до тех пор, пока мы не найдём элемент, сначала двигаясь вправо, а затем влево. Вот соответствующий код:
- Право на ошибку (СИ) - Устинова Юлия "Julia Joe" - Короткие любовные романы
- Современная философия – жертва неадекватного перевода западных философов - Иван Деревянко - Науки: разное
- Под звуки музыки - Олег Синицын - Боевая фантастика
- Правила в исключениях. Записки репетитора по английскому языку - Кирилл Шатилов - Языкознание
- Английский язык за 30 минут в день. Легкий способ выучить английский язык по методу Майкла Томаса - Майкл Томас - Руководства