Try   HackMD

Haskell: Build tools (настраиваем окружение)

Данный документ описывает рекомендуемые способы по работе с Haskell в плане сборки проектов.

Build tools (установка)

Программы на языке Haskell пишутся в файлах с расширением .hs.

На самом деле есть ещё .lhs, но в рамках курса мы эту тему затрагивать не будем.

Haskell является компилируемым языком. Компилятором Haskell (основным и, можно считать, единственным) является GHC. Предполагается, что решения домашних заданий будут разделены на несколько файлов (называемых модулями) и будут использовать сторонние библиотеки. Поэтому намного удобней организовать решения в проекты. А чтобы работать с проектами, необходимо использовать билд-тулу (программу, занимающеся компиляцией целого проекта, состоящего из нескольких модулей и используещего различные библиотеки).

На данный момент существует несколько сборщиков проектов:

  1. stack
  2. cabal-install (далее просто cabal)

Ниже будут описансы способы по установке инструментов компиляции проектов.

stack (рекомендуемый)

Рекомендации по установке stack находятся по этой ссылке:

Там есть инструкции для каждой системы.

С помощью команды stack new project можно создать проект с названием project.

После этого удалите файл package.yaml из проекта, если такой файл создался

Скомпилировать проект: stack build
Запустить ghci из проекта: stack ghci

Шаблон проектов в ДЗ

Требуется использовать следующую структуру проекта:

В README этого репозитория есть команда, которая создает требуемую структуру.

То есть структура директорий будет выглядеть примерно следующим образом:

├── hw1
│   ├── hw1.cabal
│   └── src
│       └── Task1.hs
│       └── Task2.hs
├── hw2
│   ├── hw2.cabal
│   ├── app
│   │   └── Main.hs
│   └── src
│       └── Task1.hs
│       └── Task2.hs
│       └── Task3.hs
├── hw3
│   ├── hw3.cabal
│   ├── app
│   │   └── Main.hs
│   ├── src
│       └── Task1.hs
│   └── test
│       └── Spec.hs
└── stack.yaml

Каждому домашнему заданию отведен отдельный пакет (hw1, hw2, hw3, etc). stack.yaml общий для всех пакетов (домашних заданий). Если Вам потребовалось создать executable для пакета, он должен находиться в поддиректории app/, тесты - в test/.

Чтобы собрать пакет hw1 используйте команду: stack build hw1
Чтобы запустить тесты пакета hw1 используйте команду stack test hw1
Чтобы запустить ghci в рамках пакета hw1 используйте команду
stack ghci hw1

cabal (для общего развития)

NOTE: инструкции ниже для Ubuntu. Надеюсь, пользователи других дистрибутивов смогут разобраться.

Шаг 1: Добавить PPA репозиторий

Пакеты GHC и cabal поддерживается в PPA репозитории для Ubuntu:

Надо просто набрать:

sudo add-apt-repository ppa:hvr/ghc
sudo apt update

Шаг 2: Установка GHC и cabal

Последняя вышедшая версия компилятора GHC: 8.2.2. Во время курса будем использовать её. cabal-install надо ставить cамой последней версии HEAD.

sudo apt install ghc-8.2.2 cabal-install-head

Можно проверить, что используются последние версии (версия cabal на момент прохождения инструкции может быть новее).

$ /opt/ghc/8.2.2/bin/ghc --version
The Glorious Glasgow Haskell Compilation System, version 8.2.2
$ /opt/cabal/head/bin/cabal --version
cabal-install version 2.1.0.0
compiled using version 2.1.0.0 of the Cabal library 

cabal необходимо добавить в переменную окружения $PATH. Это можно сделать следующим образом:

$ echo 'export PATH="$PATH:/opt/cabal/head/bin"' >> ~/.profile 
$ . ~/.profile 

Тоже самое рекомендуется сделать для ghc.

Шаг 3: Создание первого проекта

Создайте приватный репозиторий на GitHub. В репозитории создайте папку hw1. Далее перейдите в эту папку и запустите команду cabal init. Эта команда позволяет создать проект в интерактивном режиме. На этом шаге должно быть всё более-менее понятно. После результата выполнения этой команды из папки hw1 содержимое папки должно выглядеть следующим образом:

$ tree . . ├── ChangeLog.md ├── LICENSE ├── Setup.hs ├── src └── hw1.cabal 1 directory, 4 files

Шаг 4: Создание первого модуля

Создайте в папочке src файл с названием Dummy.hs и поместите в него следующий код:

module Dummy where inc :: Int -> Int inc x = x + 1

После этого отредактируйте hw1.cabal файл, добавив в него новый модуль. Для этого надо заменить:

  -- exposed-modules:

на

  exposed-modules: Dummy

NOTE: все модули проекта должны быть перечислены в exposed-modules, чтобы они компилировались.

Шаг 5: Сборка проекта

Сначала из проекта надо запустить команду cabal new-update. Эту команду нужно запускать каждый раз, когда вы удаляете папочку ~/.cabal.

А обычно, если что-то не билдится или идёт не так, то удаление этой папочки решает проблемы.

После этого надо набрать команду cabal new-build, чтобы скомпилировать проект.

Важно! В cabal есть команды build, update и прочие. Необходимо использовать команды только с префиксом new-. Команды с этим префиксом совсем другие и они работают хорошо.

Вы должны увидеть, что файл Dummy скомпилировался.

После этого можно запустить ghci, и вызвать оттуда функцию inc, чтобы убедиться в её работе!

Итого:

$ cabal new-update $ cabal new-build $ cabal new-repl ... Prelude> inc 4 5

Если хочется поиграться только с ghci, то шаг с new-build можно опускать. Он необходим, только если Вы будете создавать исполняемые файлы.

Сравнение

В мире Haskell, к сожалению, очень давно не утихают споры, какой инструмент сборки проектов лучше. Вместо субъективных суждений, данный документ просто опишет некоторые особенности.

Термин cabal неоднозначный. В мире Haskell под ним может подразумеваться разное в зависимости от контекста:

  1. cabal как cabal-install инструмент сборки проектов.
  2. cabal как формат конфигурации проектов на Haskell с расширением .cabal. Вся конфигурация проекта (все модули проекта, зависимости проекта, метаданные) находятся в файле project-name.cabal и описана в специальном синтаксисе. Инструменты сборки (оба cabal и stack) используют информацию из этого файла для сборки проекта.
  3. cabal как библиотека Cabal, которая парсит файлы в формате .cabal.

В этом документе и на лекциях под cabal неявно подразумевается cabal-install, а в остальных случаях идёт явное уточнение.

Все библиотеки, которые нужны вашему проекту, надо перечислять в поле build-depends. По умолчанию Ваш проект использует библиотеку base стандартную библиотеку. Но также есть, например, библиотека containers, содержащая типы данных Map, Set (реализованные на сбалансированных двоичных деревьев).

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

Если Вы используете stack, то границы в .cabal файле указывать нет необходимости. stack использует информацию из stack.yaml. В этом файле указан resolver, который используется для сборки вашего проекта (например, lts-10.5). Resolver по сути снимок версий пакетов с Hackage. В вашем проекте будут использоваться только те версии, которые указаны в резолвере. Их можно найти здесь, например:

В первом домашнем задании не потребуются библиотеки кроме base, поэтому пока можно сильно не задумываться о зависимостях.