Первое домашнее задание проверяет понимание базовых конструкций языка. А также стандартные функции и интерфейсы. Обратите внимание, что задание соответствует материалу, который рассказан в темах со 2 по 5ю отсюда.
В качестве вспомогательного материала про парсер-комбинаторы и тестирование при выполнении этого ДЗ рекомендуется использовать данные слайды
Некоторые задания имеют усложнённые версии, которые необходимо выполнять дополнительно к базовой, но они также будут оценены дополнительными баллами.
В данном домашнем задании от Вас требуется познакомиться с некоторыми библиотеками для тестирования и реализовать тесты с помощью них.
Тесты должны находиться в директории test/
Тесты должны запускаться командной stack test
.
В заданиях явно указано, какие тесты для них должны быть реализованы.
00:00 (UTC+3) 27 Марта 2020.
В этом блоке разрешено использовать автоматический deriving
только для Show
. Остальные инстансы необходимо реализовывать самостоятельно.
Тесты: Это задание необходимо протестировать при помощи простых unit-тестов.
Определите свой тип данных для Дней недели. Реализуйте следующие функции:
nextDay
: возвращает следующий за переданным день недели.afterDays
: возвращает день недели, который наступит после заданного через переданное число дней.isWeekend
: проверяет, является ли день недели выходным.daysToParty
: выводит число дней, оставшихся до пятницы.
Тесты: Это задание необходимо протестировать при помощи простых unit-тестов. Property-based тесты по желанию (оцениваются дополнительными баллами).
Этот тип данных для натуральных чисел определяется следующим образом:
data Nat = Z | S Nat
Реализуйте следующие операции (которые должны быть реализованы полностью самостоятельно):
- Сложение двух натуральных чисел.
- Умножение двух натуральных чисел.
- Вычитание натуральных чисел.
- Превращение целых чисел в натуральные и наоборот.
- Проверка натуральных чисел на равенство.
- Сравнение натуральных чисел.
Дополнительно требуется реализовать следующие функции:
- Проверка натурального числа на чётность.
- Целочисленное деление натуральных чисел.
- Остаток от деления натурального числа на другое.
Тесты: Это задание необходимо протестировать при помощи простых unit-тестов.
Тип данных для двоичного дерева имеет два конструктора:
- Лист дерева, не содержит данных.
- Узел дерева. Содержит непустой список одинаковых значений и имеет двух детей.
Двоичное дерево называется двоичным деревом поиска если оно удовлетворяет следующему условию: значения всех элементов в левом поддереве меньше значения в узле, а значения элементов в правом поддереве больше значения в узле.
Реализуйте следующие операции с двоичным деревом поиска:
- Проверка дерева на пустоту.
- Подсчёт размера дерева (то есть числа элементов в нём).
- Поиск заданного элемента в дереве (используйте тот факт, что дерево является деревом поиска).
- Вставка нового элемента в двоичное дерево поиска. Если вставляемый элемент уже находится в дереве, то необходимо добавить его в список того узла, в котором этот элемент находится. Тут следует обратить внимание, что если в узле дерева есть список элементов, то этот список всегда непустой.
- Функцию
fromList
, которая создаёт дерево из списка элементов. - Функцию, которая удаляет заданный элемент из дерева.
Тесты: Это задание необходимо протестировать при помощи Property-based тестов.
В этом задании обязательно использовать расширение языка -XInstanceSigs
и указывать типы функций в инстансах. При этом необходимо реализовать обе функции foldMap
и foldr
.
Реализуйте инстанс Foldable
для типа Tree
.
Инстанс должен быть реализован таким образом, чтобы выполнялось свойство: toList . fromList ≡ sort
.
Тесты: Это задание необходимо протестировать при помощи простых unit-тестов. Property-based тесты по желанию (оцениваются дополнительными баллами).
Используя свёртку, реализуйте функцию splitOn
, которая разбивает список на подсписки по элементу.
ghci> splitOn '/' "path/to/file"
["path", "to", "file"]
Стоит обратить внимание, что функция splitOn
всегда возвращает непустой список элементов. Это должно быть отражено в типе. Пример приведён для обычного списка, хотя это решение не полностью корректное.
Дополнительно реализуйте функцию (опять же, используя свёртку) joinWith
, обратную splitOn
. При этом Ваши реализации должны удовлетворять свойству:
joinWith x . splitOn x ≡ id
.
ghci> joinWith '/' ["path", "to", "file"]
"path/to/file"
Стоит обратить внимание на то, что функция joinWith
принимает непустой список.
Тесты: Это задание необходимо протестировать при помощи простых unit-тестов.
Напишите функцию, принимающую список списков внутри Maybe
и возвращающую конкатенацию всех внутренних списков.
ghci> maybeConcat [Just [1,2,3], Nothing, Just [4,5]]
[1,2,3,4,5]
Функция должна принимать произвольный набор Either
, где и Left
и Right
содержат некоторые моноидальные элементы, и необходимо вернуть пару из результатов моноидального объединения отдельно элементов внутри Left
и отдельно элементов внутри Right
.
ghci> eitherConcat [Left (Sum 3), Right [1,2,3], Left (Sum 5), Right [4,5]]
(Sum {getSum = 8}, [1,2,3,4,5])
Тесты: Это задание необходимо протестировать при помощи простых unit-тестов.
Реализуйте инстансы алгебраических классов типов для следующих структур данных. Ваши инстансы должны удовлетворять законам для этих структур.
Semigroup
дляdata NonEmpty a = a :| [a]
.Semigroup
для типа данныхdata ThisOrThat a b = This a | That b | Both a b
.
Дополнительно реализуйте следующие инстансы:
Semigroup
иMonoid
для строк, объединяемых при помощи'.'
.
ghci> Name "root" <> Name "server"
Name "root.server"
Semigroup
иMonoid
дляnewtype Endo a = Endo { getEndo :: a -> a }
.
Тесты: Это задание необходимо протестировать при помощи простых unit-тестов.
В ДЗ1 необходимо было написать функцию, которая суммирует числа в строке. В формулировке задания было допущено серьёзное ослабление, а именно -- данные на вход подаются валидные. В этом задании Вам необходимо реализовать безопасную функцию поиска суммы, а именно:
stringSum :: String -> Maybe Int
Числа в строке разделены одним или несколькими пробельными символами.
Если хотя бы один элемент строки нельзя сконвертировать в целое число, то необходиомо вернуть Nothing
.
Функция должна использовать инстанс Traversable для листа.
Необходимо написать несколько простых юнит тестов на эту функцию.
Дан следующий тип данных:
data Tree a
= Branch (Tree a) (Tree a)
| Leaf a
Реализуйте вручную инстансы Functor
, Applicative
, Foldable
и Traversable
для Tree
.
Должны выполняться законы этих тайпклассов.
Дан следующий тип данных:
data NonEmpty a = a :| [a]
Реализуйте вручную инстансы Functor
, Applicative
, Monad
, Foldable
и Traversable
для NonEmpty
.
Во время реализации инстансов для NonEmpty
можно использовать соответствующие инстансы для списка.
Тесты: Это задание необходимо протестировать при помощи простых unit-тестов.
Арифметическое выражение (именно выражение, не результат его вычисления) можно представить рекурсивным алгебраическим типом данных. Реализуйте этот тип данных, чтобы с его помощью можно было задавать следующие операции:
- Целочисленные константы
- Сложение двух выражений
- Вычитание выражений
- Произведение выражений
- Деление выражений
- Возведение в степень выражений
После этого напишите функцию, которая принимает выражение и вычисляет его. Обратите внимание на то, что выражение может не получиться вычислить по разным причинам.
eval :: Expr -> Either ArithmeticError Int
То есть Вы должны создать свой тип данных, который обозначает арифметическую ошибку и возвращать Either
— либо ошибка, которая возникла, либо результат. Если выражение содержит несколько ошибок, то можно вернуть любую.
Достаточно проверять только на следующие арифметические ошибки:
- Деленение на 0.
- Возведение в отрицательную степень.
Подсказка: если реализовать функцию с Either
сразу тяжело, то попробуйте eval :: Expr -> Maybe Int
, после чего замените Maybe
на Either String
, а затем String
можно будет заменить за свой тип данных.
Реализуйте Simple Moving Average алгоритм, используя State монаду.
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]
Это блок самый важный в этом домашнем задании. Реализация всех упражнений из этого блока поможет понять, как устроены парсер-комбинаторы, а это важно, потому что они крайне полезны на практике. Перед решением заданий убедитесь, что вы осознали материал лекции и можете прорешать базовые упражнения по следующим ссылкам:
Основная часть упражнений оттуда всё равно является частью этого домашнего задания.
Тесты: в заданиях этого блока необходимо написать несколько юнит-тестов при помощи hspec
. Property-based тесты по желанию.
Имеется тип простого парсер-комбинатора:
data Parser s a = Parser { runParser :: [s] -> Maybe (a, [s]) }
В отличие от парсера предложенного на практике, он может работать не только со строкой, но и с любым потоком данных. Реализуйте вручную инстансы Functor
, Applicative
, Monad
и Alternative
для этого парсера.
Реализуйте следующие базовые комбинаторы:
ok
--- парсер никогда не падает и не поглащает инпут.eof
--- проверяет, что парсер дошёл до конца потока данных (иначе падает).satisfy
--- парсер принимает предикат на элемент потока, и возвращает элемент, поглащая его из потока, если предикат на элемент равенTrue
, иначе падает.element
иstream
--- парсят один или несколько элементов потока (какchar
иstring
).
Используя существующие комбинаторы (реализовав по необходимости остальные) напишите следующие парсеры строковых потоков:
- Парсер правильных скобочных последовательностей (падает, если последовательность неправильная, и не падает, если правильная).
- Парсер целого числа, перед которым может быть знак
+
или-
.
Написать парсер списка списков чисел, разделённых запятой. Парсер должен иметь тип:
listlistParser :: Parser Char [[Int]]
Все числа перечисленны через запятую. В начале каждого списка находится число --- длина списка. Таким образом можно понять, где заканчивается каждый список. То есть список
[ [1, 10], [5, -7, 2] ]
в строковом виде может быть записан следующим образом:
"2, 1,+10 , 3,5,-7, 2"
Данное задание направлено на изучение монады Cont
newtype Cont r a = Cont { runCont :: (a -> r) -> r }
Данная монада не имеет имеет простого аналога в императивных языках, но может быть полезна в некоторых случаях.
В данном задании от Вас требуется разобраться или реализовать самостоятельно инстансы Functor
, Applicative
, и Monad
для Cont
.
Затем, используя эту монаду реализовать небольшое подмножество системных вызовов операционной системы.
А именно, необходимо поддержать read
, write
, exit
, yield
, fork
.
Должна иметься возможность написать следующий код:
main' = do
x <- readLine
let str = "Hello, " ++ show x ++ "!" in
writeLine str
exit Success
Требуется также реализовать функцию kernel
, которая будет интерпретировать данный код.
Информацию о монаде Cont можно найти здесь, а больше информации, как реализовать то что требуется здесь.