# Haskell: ДЗ 2 -- Монады и парсер-комбинаторы
Второе домашнее задание проверяет, насколько хорошо у студентов развито понимание системы типов в Haskell, насколько хорошо они могут использовать типы для создания абстракций и решения проблем. А также проверяется возможность использовать уже существующие и широко используемые абстракции. Само задание помогает понять, как типы могут быть использованы для написания короткого, безопасного и переиспользуемого кода.
В данном домашнем задании от Вас требуется познакомиться с некоторыми библиотеками для тестирования и реализовать тесты с помощью них.
Тесты должны находиться в директории `test/`, Вы можете без труда найти ее в директории соответствующего пакета предложенного Вам [темплейта](https://github.com/pva701/fp-homework-templates).
Тесты должны запускаться командной `stack test`.
В качестве вспомогательного материала при выполнении этого ДЗ можно использовать данные [слайды](https://slides.com/fp-ctd/lecture-55)
## Блок 1: Базовые конструкции
### Задание 1
#### Основное задание
В ДЗ1 необходимо было написать функцию, которая суммирует числа в строке. В формулировке задания было допущено серьёзное ослабление, а именно -- данные на вход подаются валидные. В этом задании Вам необходимо реализовать безопасную функцию поиска суммы, а именно:
```haskell=
stringSum :: String -> Maybe Int
```
Числа в строке разделены одним или несколькими пробельными символами.
Если хотя бы один элемент строки нельзя сконвертировать в целое число, то необходиомо вернуть `Nothing`.
Функция должна использовать инстанс Traversable для листа.
#### Усложненное задание
Необходимо написать несколько простых юнит тестов на эту функцию.
### Задание 2
Дан следующий тип данных:
```haskell=
data Tree a
= Branch (Tree a) (Tree a)
| Leaf a
```
Реализуйте вручную инстансы `Functor`, `Applicative`, `Foldable` и `Traversable` для `Tree`.
Должны выполняться законы этих тайпклассов.
### Задание 3:
Дан следующий тип данных:
```haskell=
data NonEmpty a = a :| [a]
```
Реализуйте вручную инстансы `Functor`, `Applicative`, `Monad`, `Foldable` и `Traversable` для `NonEmpty`.
Во время реализации инстансов для `NonEmpty` можно использовать соответствующие инстансы для списка.
## Блок 2: Монады и монадические вычисления
### Задание 1
Арифметическое выражение (именно выражение, не результат его вычисления) можно представить рекурсивным алгебраическим типом данных. Реализуйте этот тип данных, чтобы с его помощью можно было задавать следующие операции:
* Целочисленные константы
* Сложение двух выражений
* Вычитание выражений
* Произведение выражений
* Деление выражений
* Возведение в степень выражений
После этого напишите функцию, которая принимает выражение и вычисляет его. Обратите внимание на то, что выражение может не получиться вычислить по разным причинам.
```haskell
eval :: Expr -> Either ArithmeticError Int
```
То есть Вы должны создать свой тип данных, который обозначает арифметическую ошибку и возвращать `Either` — либо ошибка, которая возникла, либо результат. Если выражение содержит несколько ошибок, то можно вернуть любую.
Достаточно проверять только на следующие арифметические ошибки:
1. Деленение на 0.
2. Возведение в отрицательную степень.
**Подсказка:** если реализовать функцию с `Either` сразу тяжело, то попробуйте `eval :: Expr -> Maybe Int`, после чего замените `Maybe` на `Either String`, а затем `String` можно будет заменить за свой тип данных.
**Тесты:** Это задание необходимо протестировать при помощи простых unit-тестов.
### Задание 2
Реализуйте [Simple Moving Average](https://en.wikipedia.org/wiki/Moving_average) алгоритм, используя State монаду.
```haskell
ghci> moving 4 [1, 5, 3, 8, 7, 9, 6]
[1.0, 3.0, 3.0, 4.25, 5.75, 6.75, 7.5]
ghci> moving 2 [1, 5, 3, 8, 7, 9, 6]
[1.0, 3.0, 4.0, 5.5, 7.5, 8.0, 7.5]
```
Реализация должна быть эффективной, и работать для листов размером в $10^4$ достаточно быстро (за 2-3 секунды).
### Задание 3
Помимо `Monad`, существуют еще и другие тайпклассы, похожие на `Monad`.
В данном задании от Вас требуется выразить одни через другие.
Пусть есть следующие тайпклассы.
```haskell
class MonadFish m where
returnFish :: a -> m a
(>=>) :: (a -> m b) -> (b -> m c) -> (a -> m c)
class MonadJoin m where
returnJoin :: a -> m a
join :: m (m a) -> m a
class Monad m where
(>>=) :: m a -> (a -> m b) -> m b
return :: a -> m a
```
Необходимо реализовать
* `MonadJoin`, имея `MonadFish`
* `Monad`, имея `MonadFish`
* `MonadFish`, имея `Monad`
* `MonadJoin`, имея `Monad`
Все инстансы должны иметь вид
```haskell
instance MonadA m => MonadB m where
...
```
Для этого вам понадобится расширения языка `UndecidableInstances` и `FlexibleInstances`.
## Блок 3: Парсер-комбинаторы
Это блок самый важный в этом домашнем задании. Реализация всех упражнений из этого блока поможет понять, как устроены парсер-комбинаторы, а это важно, потому что они крайне полезны на практике. Перед решением заданий убедитесь, что вы осознали материал лекции и можете прорешать базовые упражнения по следующим ссылкам:
* [Parser Combinators: Basics](http://www.seas.upenn.edu/~cis194/spring13/hw/10-applicative.pdf)
* [Parser Combinators: Implementing simple parser](http://www.seas.upenn.edu/~cis194/spring13/hw/11-applicative2.pdf)
Основная часть упражнений оттуда всё равно является частью этого домашнего задания.
**Тесты:** в заданиях блока необходимо написать несколько юнит-тестов при помощи `hspec`. Property-based тесты по желанию.
### Задание 1: Copy-paste
Имеется тип простого парсер-комбинатора:
```haskell=
data Parser s a = Parser { runParser :: [s] -> Maybe (a, [s]) }
```
В отличие от парсера предложенного на практике, он может работать не только со строкой, но и с любым потоком данных. Реализуйте вручную инстансы `Functor`, `Applicative`, `Monad` и `Alternative` для этого парсера.
### Задание 2: Базовые комбинаторы
Реализуйте следующие базовые комбинаторы:
1. `ok` --- парсер никогда не падает и не поглащает инпут.
2. `eof` --- проверяет, что парсер дошёл до конца потока данных (иначе падает).
3. `satisfy` --- парсер принимает предикат на элемент потока, и возвращает элемент, поглащая его из потока, если предикат на элемент равен `True`, иначе падает.
4. `element` и `stream` --- парсят один или несколько элементов потока (как `char` и `string`).
### Задание 3: Простые парсеры
Используя существующие комбинаторы (реализовав по необходимости остальные) напишите следующие парсеры строковых потоков:
1. Парсер правильных скобочных последовательностей (падает, если последовательность неправильная, и не падает, если правильная).
2. Парсер целого числа, перед которым может быть знак `+` или `-`.
### Задание 4: Непростой парсер
Написать парсер списка списков чисел, разделённых запятой. Парсер должен иметь тип:
```haskell
listlistParser :: Parser Char [[Int]]
```
Все числа перечисленны через запятую. В начале каждого списка находится число --- длина списка. Таким образом можно понять, где заканчивается каждый список. То есть список
```haskell=
[ [1, 10], [5, -7, 2] ]
```
в строковом виде может быть записан следующим образом:
```haskell=
"2, 1,+10 , 3,5,-7, 2"
```
## Бонусное задание
Данное задание направлено на изучение монады `Cont`
```haskell
newtype Cont r a = Cont { runCont :: (a -> r) -> r }
```
Данная монада не имеет имеет простого аналога в императивных языках, но может быть полезна в некоторых случаях.
В данном задании от Вас требуется разобраться или реализовать самостоятельно инстансы `Functor`, `Applicative`, и `Monad` для `Cont`.
Затем, используя эту монаду реализовать небольшое подмножество системных вызовов операционной системы.
А именно, необходимо поддержать `read`, `write`, `exit`, `yield`, `fork`.
Должна иметься возможность написать следующий код:
```haskell
main' = do
x <- readLine
let str = "Hello, " ++ show x ++ "!" in
writeLine str
exit Success
```
Требуется также реализовать функцию `kernel`, которая будет интерпретировать данный код.
Информацию о монаде Cont можно найти [здесь](https://slides.com/fp-ctd/lecture-55#/29),
а больше информации, как реализовать то что требуется [здесь](https://www.dropbox.com/s/z1e1kxi32zl8kbd/DDToOS.pdf?dl=0).