Методические указания

Цель практического задания — разработка законченного приложения или программной библиотеки на языке программирования Haskell.

Групповая работа

Каждое задание может быть выполнено как в одиночку, так и группой в несколько человек (максимальное количество человек в группе зависит от задания).

В каждом задании выделена базовая часть. Все участники должны разбираться в реализации базовой части и быть в состоянии воспроизвести её.

Помимо базовой части в задании присутствует несколько дополнительных частей, по одной на каждого участника. Таким образом, если задание выполняется в одиночку, студент должен реализовать базовую и хотя бы одну дополнительную часть.

Для эффективной работы (в том числе в одиночку) предлагается:

  • обсуждать структуру проекта и каждой части до реализации (работать вместе проще, когда вы работаете в одном направлении);
  • разбивать реализацию на логические модули (например, дополнительные части заданий должны находиться в разных модулях и минимально зависеть друг от друга);
  • пользоваться системой контроля версий (например, Git);
  • придерживаться правил оформления исходного кода (хорошо написанный код легко читать, проверять и модифицировать);
  • проводить просмотр кода (code review) других участников проекта (это позволяет улучшить общее представление о проекте, уменьшает шанс логических и других ошибок в общем проекте, позволяет поддерживать исходный код оформленным).

Правила оформления исходного кода

Программирование — это в равной мере искусство и ремесло. И, как известно любому художнику, ограничения расширяют, а не подавляют творческие способности.

Правила оформления исходного кода:

  • используйте camelCase для именования функций и переменных;
  • используйте описательные названия функций, пусть они будут настолько длинные, насколько необходимо, но не длинее этого. Хорошо: solveRemaining. Плохо: slv. Ужасно: solveAllTheCasesWhichWeHaven'tYetProcessed.
  • не используйте символы табуляции: Haskell чувствителен к отступам и табуляция может сильно испортить вам жизнь. Заметьте, что это не значит, что вам придётся нажимать пробел миллион раз: ваш любимый текстовый редактор умеет заменять табуляцию на пробелы автоматически.
  • старайтесь не писать строчки длинее 80 символов. Код, который не приходится пролистывать по горизонтали читать обычно удобнее.
  • описывайте тип для каждой функции на верхнем уровне. Сигнатура типа значительно улучшает документацию и способствует правильному мышлению. Также явное указание типов способствует лучшим сообщениям об ошибках при компиляции. Локально определенные функции и константы (определенные при помощи let и where) не нуждаются в сигнатурах, но их выписывание не навредит (это также улучшает сообщения об ошибках).
  • формулируйте развёрнутый комментарий для каждой функции на верхнем уровне.
  • используйте флаг -Wall при компиляции или напишите {-# OPTIONS_GHC -Wall #-} на самом начале файла с кодом. Этот флаг включает все предупредительные сообщения компилятора.
  • по возможности разбивайте ваш код на простые функции (каждая с одним ясным предназначением) и составляйте из них программу;
  • пишите всюду определённые функции: они не должны завершаться аварийно ни на одном возможном входе.

Средства разработки

Stack

Для разработки проекта рекомендуется использовать Stack.

Stack может установить нужную версию компилятора и необходимых библиотек. Для установки компилятора необходимо запустить команду stack setup.

Для установки зависимостей Stack использует Stackage — стабильный репозиторий пакетов. Для выполнения практического задания рекомендуется использовать последний доступный LTS Haskell.

Для сборки проекта используйте команду stack build или stack test.

Для запуска интерпретатора GHCi с автоматической загрузкой модулей проекта используйте команду stack ghci.

Для запуска исполняемой программы (например, графического интерфейса) используйте команду stack exec <имя программы>.

Для автоматической сборки документации проекта используйте команду stack haddock.

Haddock

По возможности, используйте используйте разметку Haddock при написании комментариев в коде. Таким образом вы можете легко получить полноценную документацию кода вашего проекта. Вы можете локально собрать Haddock документацию, используя команду stack haddock.

Вам и вашей команде будет проще разобраться в проекте, если он будет хорошо документирован. Кроме того, навык написания хорошей документации не раз может пригодиться вам за пределами данного курса.

Рекомендации по оформлению задания

Каждое практическое задание можно реализовать множеством способов с различными наборами возможностей. Для того, чтобы другим людям (в том числе соучастникам и преподавателям) было проще разобраться в законченном проекте, рекомендуется:

  • добавить файл README:
    • с описанием проекта;
    • инструкциями по установке и запуску;
    • описанием реализованных возможностей;
  • оформить код в виде проекта Stack:
    • для сборки проекта должно быть достаточно выполнить команду stack build;
    • запуск интерпретатора GHCi с автоматической загрузкой модулей проекта осуществляется командой stack ghci;
    • запуск исполняего файла проекта — командой stack exec <имя программы>;
    • тестирование проекта осуществляется командой stack test;
  • если вы храните основную версию репозитория на GitHub, вы можете использовать Travis CI для автоматической сборки и тестирования вашего проекта.

Рекомендации по выбору библиотек

При реализации некоторых частей практических заданий может потребоваться использование сторонних библиотек — например, для графических интерфейсов, клиент-серверной архитектуры, работы с базой данных, генерации кода и пр.

Синтаксический разбор

Для синтаксического разбора рекомендуется использовать комбинаторные библиотеки — например, Parsec или attoparsec. Parsec предоставляет более выразительные средства и лучше подходит для разбора исходного кода и конфигурационных файлов. attoparsec предлагает более простой интерфейс и меньше возможностей, но на несколько порядков лучше по производительности и подходит для разбора сетевых протоколов, логов, бинарных данных.

Генерация кода

Для генерации объектного кода проще всего использовать существующий низкоуровневый язык программирования, из которого уже можно легко получить объектный код. К таким языкам относятся C, C– и язык LLVM. Последний часто используется в компиляторах, поскольку специально создан для этой цели.

Генерация кода для LLVM на Haskell реализуется при помощи библиотеки llvm-general.

Графический интерфейс

Библиотека gloss предоставляет простой и удобный интерфейс для работы с векторной 2D графикой. Для игр рекомендуется использование модулей Graphics.Gloss.Interface.Pure.Game или Graphics.Gloss.Interface.IO.Game. Для моделирования можно использовать модули Graphics.Gloss.Interface.Pure.Simulate или Graphics.Gloss.Interface.IO.Simulate.

Для игр также стоит использовать библиотеку gloss-game, которая предоставляет несколько удобных функций для работы со сценами.

Клиент-серверная архитектура

Для большинства практических заданий в качестве протокола общения между клиентом и сервером можно использовать HTTP. При реализации HTTP сервера рекомендуется использовать архитектуру REST.

Существует множество web-фреймворков для реализации серверной части. Для выполнения практических заданий рекомендуется использовать использовать один из следующих: - servant — относительно простой в использовании и в то же время

мощный фреймворк для работы с REST API; в отличие от большинства других фреймворков покрывает не только серверную, но и клиентскую части, а так же автоматическую документацию, инструменты для тестирования, генерация клиентского кода для других языков программирования;
  • spock — неплохой фреймворк с неплохой документацией; некоторые возможности требуют хорошенько разобраться, но для выполнения практического задания они необязательны; использовать в паре с wreq для клиентской части;
  • scotty — наверное, самый простой фреймворк; использовать в паре с wreq для клиентской части;

Для более тесной связи клиента и сервера можно использовать протокол TCP. Соответствующая библиотека — network-simple.

Для передачи данных по сети рекомендуется использовать сериализацию/десериализацию данных. В случае HTTP предлагается использовать формат JSON (используя библиотеку aeson). В случае TCP — бинарное представление (используя библиотеку binary).

Многопоточность

Серверные приложения используют многопоточность, чтобы взаимодействовать одновременно с множеством клиентов. Приложения с графическим интерфейсом используют многопоточность, чтобы избежать эффекта замирания во время потенциально длительных расчётов (например, в реализации ИИ) или сетевого взаимодействия.

Для использования общей памяти между потоками одного приложения в Haskell используется программная транзакционная память. Соответствующая библиотека stm входит в список стандартных пакетов. В практических заданиях достаточно использования TVar и, возможно, TChan.

Для ознакомления с программной транзакционной памятью, рекомендуется прочтение статьи Software Transactional Memory.

База данных

Для работы с базой данных рекомендуется использовать библиотеку persistent. Эта библиотека предоставляет интерфейс, не зависящий от конкретной используемой СУБД и поддерживает как минимум PostgreSQL, SQLite, MySQL and MongoDB. Для сложных запросов (например, по нескольким таблицам) предлагается использовать библиотеку esqueleto, которая работает поверх библиотеки persistent.