Pull to refresh

Играем в Haskell

Reading time 10 min
Views 60K
Original author: Bob Ippolito


Я замечательно провел время изучая Haskell в последние месяцы, и мне кажется, что сделать первые шаги в этом занятии сложнее, чем это могло бы быть на самом деле. Мне повезло работать в нужное время и в нужном месте, и в Facebook я прошел курс по Haskell от Bryan O'Sullivan, но Вы определенно сможете влиться в тему и без чужой помощи. Для этого можно поиграть в Haskell на сайте Try Haskell, а в конечном счете установить себе GHC.

Устанавливаем Haskell Platform (GHC)


The Haskell Platform — это Glasgow Haskell Compiler (GHC) и стандартная библиотека «в полной комплектации». GHC не единственный компилятор Хаскеля, но Вам нужен именно он. Другая реализация, достойная внимания, Hugs, подходит больше в академических целях, чем в практических.

Инструкция написана для пользователей Mac OS X 10.8 с установленным Homebrew (и свежей версией Xcode), но разобраться с тем, как сделать то же самое на других платформах с помощью Haskell Platform должно быть просто. Текущая версия Haskell Platform на данный момент 2012.4.0.0.

$ brew install haskell-platform

Устанавливаем Cabal


Cabal — это Общая Архитектура Сборки Приложений и Библиотек (Common Architecture for Building Applications and Libraries) для Хаскеля. В паре с Hackage, Cabal похож по смыслу на такие инструменты, как CPAN для Perl, pip для Python или же gem для Ruby. Вероятно, Вы будете разочарованы, но всё же он не так уж и плох.

Cabal устанавливает свои пакеты в ~/.cabal/, а скрипты уходят в ~/.cabal/bin/. Вам нужно добавить эти пути в переменную окружения PATH. Достаточно чего-то подобного, но все зависит от Ваших предпочтений (лично я для этих целей пользую ~/.profile — прим. переводчика):

$ echo 'export PATH=$HOME/.cabal/bin:$PATH' >> ~/.bashrc

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

$ cabal update

На данный момент мы имеем ~/.cabal/config без включенного профилирования библиотек. Вы наверняка позже захотите им воспользоваться, и если не включить его сейчас, то в будущем придется все пересобирать заново. Чтобы включить его, измените строчку -- library-profiling: False на library-profiling: True в файле ~/.cabal/config.

$ for f in ~/.cabal/config; do \
    cp $f $f.old && \
    sed -E 's/(-- )?(library-profiling: )False/\2True/' < $f.old > $f; \
done

Самым первым Вашим пакетом должен стать инсталлятор Cabal:

$ cabal install cabal-install

Устанавливаем ghc-mod (улучшенная поддержка Emacs/Vim)


ghc-mod пригодится Вам для интеграции GHC с Emacs или Vim. Того же самого эффекта можно достичь в Sublime Text 2 и ghc-mod с помощью SublimeHaskell. Я пока что пользовался только интеграцией с Emacs. Пользователи Vim могут воспользоваться hdevtools, т.к. он гораздо быстрее, и настолько же точен (см. комментарий kamatsu)

$ cabal install ghc-mod

Настройку Вашего собственного Emacs я освещать не стану (но можете воспользоваться моим текущим ~/.emacs.d для примера).

Устанавливаем Cabal-dev (песочница для сборки)


Cabal-dev — это инструмент, который упростит Вам установку приложений на Хаскеле. Он похож на virtualenv для Python и rvm для Ruby, но подход к использованию различается. Он спасет Вас от «кабальского ада», в котором ни у кого не получается устанавливать одни пакеты из-за конфликтов с зависимостями других.

Используйте cabal-dev вместо простого cabal для сборки везде, где это возможно. Главный компромисс состоит в том, что Вам придется тратить (гораздо) больше времени на компиляцию пакетов, которые уже и так были установлены где-то в другом месте (и забивать дисковое пространство), но это безусловно справедливая плата.

Нормальная установка cabal-dev должна выглядеть как cabal install cabal-dev, но пока никто не закрыл баг #74, Вам придется собирать и устанавливать его из исходников:

$ git clone https://github.com/creswick/cabal-dev.git /tmp/cabal-dev-src && \
    (cd /tmp/cabal-dev-src; cabal install) && \
    rm -rf /tmp/cabal-dev-src

В данный момент ведутся некоторые работы над влючением поддержки Сборок в Песочнице и Изолированных Окружений в cabal-install, так что информация о cabal-dev, которая есть в этом посте, потеряет актуальность через несколько месяцев (лет?).

Установка инструментов при помощи cabal-dev


Если Вам хочется попробовать какой-нибудь инструмент, но желание загрязнять свою инсталляцию Хаскеля отсутствует, в таком случае нужно просто использовать cabal-dev. По умолчанию, песочница cabal-dev находится в ./cabal-dev, но Вы можете держать ее где угодно. В этом примере я установлю darcs 2.8.2 (распределенная система контроля версий, написанная на Хаскеле) в папку /usr/local/Cellar/darcs/2.8.2, и попрошу Homebrew сделать для меня симлинки. На других платформах Вам скорее всего придется использовать собственную структуру директорий, а также вручную редактировать PATH.

$ cabal-dev install -s /usr/local/Cellar/darcs/2.8.2 darcs-2.8.2
$ brew link --overwrite darcs

Тыдыж! Теперь darcs лежит в Вашем PATH, и больше не придется беспокоиться о конфликтующих версиях. Ну, к сожалению, конфликты все еще будут происходить, но теперь не так часто. В частности, cabal-dev устанавливает пакеты таким образом, что они выходят на самую верхушку выбранной песочницы. Это значит, что если два пакета обладают общими зависимостями (ОЧЕНЬ общими), то они спляшут джигу на симлинках друг друга, вплоть до таких вещей как файлы лицензионных соглашений и документации зависимостей. В таком случае использовать --overwrite можно почти безболезненно, но я рекомендую сначала прогонять с ключом --overwrite --dry-run. Это надоедает, но наверняка не испортит Вам целый день.

Если хочется увидеть доступные версии darcs, используйте use cabal info darcs и найдите секцию Versions available:.

Остальные веселые штуки, с которыми можно поиграть (порядок не имеет значения):
  • pandoc — швейцарский нож в контексте форматов разметки (markdown, reStructuredText, org-mode, LaTeX и т.д.)
  • gitit — wiki, работающая под управлением git, darcs или mercurical
  • pronk — инструмент для тестирования HTTP нагрузки, подобный ab и httperf, только более современный и простой

Для таких пакетов, как pronk, которые в данный момент отсутствуют в Hackage, установка через cabal-dev должна выглядеть примерно так:

$  git clone https://github.com/bos/pronk.git /tmp/pronk-src && \
    (cd /tmp/pronk-src; \
     cabal-dev install -s /usr/local/Cellar/pronk/$(git rev-parse --short HEAD)) && \
    rm -rf /tmp/pronk-src

Настройка GHCi


ghci — это интерактивный интерпретатор GHC (REPL, схожий с python или irb в терминале). За подробной документацией рекомендую обратиться к GHC Users Guide (Chapter 2. Using GHCi). Вы будете проводить там много времени, играя с кодом, так что предлагаю для начала укоротить приветствие. Оно выглядит так:

Prelude>

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

Prelude> :m + Data.List
Prelude Data.List> :m + Data.Maybe
Prelude Data.List Data.Maybe>

Это можно исправить при помощи файла .ghci. Я использую очень простой ASCII символ, хотя некоторые любят что-то вроде λ>.

echo ':set prompt "h> "' >> ~/.ghci

Можно также использовать команду :set prompt "h> " при каждом запуске GHCi, но это тоже лишнее.

$ ghci
h> putStrLn "Hello World!"
Hello World!
h>

Hackage хрупкий, но существуют (неофициальные) зеркала


К сожалению, Hackage не славится своей надежностью. Я не знаю в чем проблема, но надеюсь, что они с этим что-то сделают в ближайшее время. Есть обходной путь (Обходные пути в случае падения Hackage), нужно всего-лишь использовать репозиторий из hdiff на hdiff.luite.com или с hackage.csc.stanford.edu.

Для этого нужно поменять строчку в ~/.cabal/config:

remote-repo: hackage.haskell.org:http://hackage.haskell.org/packages/archive

На что-то вроде:

-- TODO When hackage is back up, set back to hackage.haskell.org!
-- remote-repo: hackage.haskell.org:http://hackage.haskell.org/packages/archive
remote-repo: hdiff.luite.com:http://hdiff.luite.com/packages/archive
-- remote-repo: hackage.csc.stanford.edu:http://hackage.scs.stanford.edu/packages/archive

После изменений, нужно обновить список пакетов

$ cabal update

И не забудьте вернуть все обратно спустя некоторое время!

Создаем проект (с помощью cabal-dev)


В конечном счете Вы бы и сами это осознали, но самый быстрый способ начать проект — начать его с cabal-dev. Вот что нужно сделать для простенькой программы.

Для собственных проектов, нужно убрать опцию -n, чтобы задать список необходимых опций вручную. Опция -n использует все настройки по умолчанию и ни о чем Вас не спрашивает.
$ mkdir -p ~/src/hs-hello-world
$ cd ~/src/hs-hello-world
$ touch LICENSE
$ cabal init -n --is-executable

Это сгенерирует файлы Setup.hs и hs-hello-world.cabal. Следующим шагом нужно изменить строчку main-is:, чтобы кабал знал, из какого исходника собирать исполняемый файл. Конечный результат должен получиться таким:

hs-hello-world.cabal

-- Initial hs-hello-world.cabal generated by cabal init.  For further 
-- documentation, see http://haskell.org/cabal/users-guide/
 
name:                hs-hello-world
version:             0.1.0.0
-- synopsis:            
-- description:         
license:             AllRightsReserved
license-file:        LICENSE
-- author:              
-- maintainer:          
-- copyright:           
-- category:            
build-type:          Simple
cabal-version:       >=1.8
 
executable hs-hello-world
  main-is:             HelloWorld.hs
  -- other-modules:       
  build-depends:       base ==4.5.*

Затем создайте HelloWorld.hs, с таким содержимым:

HelloWorld.hs

main :: IO ()
main = putStrLn "Hello, world!"

Вот что нужно, чтобы собрать и «установить» программу в текущей песочнице:
$ cabal-dev install
Resolving dependencies...
Configuring hs-hello-world-0.1.0.0...
Building hs-hello-world-0.1.0.0...
Preprocessing executable 'hs-hello-world' for hs-hello-world-0.1.0.0...
Installing executable(s) in /Users/bob/src/hs-hello-world/cabal-dev//bin
Installed hs-hello-world-0.1.0.0
$ ./cabal-dev/bin/hs-hello-world
Hello, world!

Исполняемый файл получается великоват, но также он статически линкован. Можно скопировать его на любую машину с такой же операционной системой и архитектурой, и он просто будет работать.

Можно сэкономить немного времени, пропустив шаг установки:

$ cabal-dev configure
Resolving dependencies...
Configuring hs-hello-world-0.1.0.0...
$ cabal-dev build
Building hs-hello-world-0.1.0.0...
Preprocessing executable 'hs-hello-world' for hs-hello-world-0.1.0.0...
[1 of 1] Compiling Main             ( HelloWorld.hs, dist/build/hs-hello-world/hs-hello-world-tmp/Main.o )
Linking dist/build/hs-hello-world/hs-hello-world ...
$ ./dist/build/hs-hello-world/hs-hello-world
Hello, world!

Так как у проекта нет зависимостей, которые нужно устанавливать, можно немного срезать углы.

Например, запустить с помощью интерпретатора, без компиляции:

$ runghc HelloWorld.hs
Hello, world!

$ ghci
GHCi, version 7.4.2: http://www.haskell.org/ghc/  :? for help
Loading package ghc-prim ... linking ... done.
Loading package integer-gmp ... linking ... done.
Loading package base ... linking ... done.
Prelude> :load HelloWorld
[1 of 1] Compiling Main             ( HelloWorld.hs, interpreted )
Ok, modules loaded: Main.
*Main> main
Hello, world!

Да и вовсе можно собрать его без cabal-dev (или cabal):

$ runghc Setup.hs configure
Configuring hs-hello-world-0.1.0.0...
$ runghc Setup.hs build
Building hs-hello-world-0.1.0.0...
Preprocessing executable 'hs-hello-world' for hs-hello-world-0.1.0.0...
[1 of 1] Compiling Main             ( HelloWorld.hs, dist/build/hs-hello-world/hs-hello-world-tmp/Main.o )
Linking dist/build/hs-hello-world/hs-hello-world ...

Но для более сложного проекта, можно использовать cabal-dev ghci (после cabal-dev configure && cabal-dev build). Прошу заметить, что в этом случае код будет загружен в интерпретатор автоматически:

$ cabal-dev ghci

on the commandline:
    Warning: -O conflicts with --interactive; -O ignored.
GHCi, version 7.4.2: http://www.haskell.org/ghc/  :? for help
Loading package ghc-prim ... linking ... done.
Loading package integer-gmp ... linking ... done.
Loading package base ... linking ... done.
Ok, modules loaded: Main.
h> main
Hello, world!

Основы GHCi


Некоторые базовые трюки GHC которые хорошо бы знать. Помимо тех, которые я указал здесь, советую изучить Chapter 2. Using GHCi.

:t показывает тип выражения

h> :t main
main :: IO ()
h> :t map
map :: (a -> b) -> [a] -> [b]
h> :t map (+1)
map (+1) :: Num b => [b] -> [b]

:i показывает информацию о названии (function, typeclass, type, ...)

h> :i Num
class Num a where
  (+) :: a -> a -> a
  (*) :: a -> a -> a
  (-) :: a -> a -> a
  negate :: a -> a
  abs :: a -> a
  signum :: a -> a
  fromInteger :: Integer -> a
    -- Defined in `GHC.Num'
instance Num Integer -- Defined in `GHC.Num'
instance Num Int -- Defined in `GHC.Num'
instance Num Float -- Defined in `GHC.Float'
instance Num Double -- Defined in `GHC.Float'
h> :info map
map :: (a -> b) -> [a] -> [b]   -- Defined in `GHC.Base'
h> :info Int
data Int = ghc-prim:GHC.Types.I# ghc-prim:GHC.Prim.Int#
    -- Defined in `ghc-prim:GHC.Types'
instance Bounded Int -- Defined in `GHC.Enum'
instance Enum Int -- Defined in `GHC.Enum'
instance Eq Int -- Defined in `ghc-prim:GHC.Classes'
instance Integral Int -- Defined in `GHC.Real'
instance Num Int -- Defined in `GHC.Num'
instance Ord Int -- Defined in `ghc-prim:GHC.Classes'
instance Read Int -- Defined in `GHC.Read'
instance Real Int -- Defined in `GHC.Real'
instance Show Int -- Defined in `GHC.Show'

:m добавляет модуль в текущую область видимости

h> :m + Data.List
h> sort [10,9..1]
[1,2,3,4,5,6,7,8,9,10]

:l подгружает модуль, :r перезагружает

h> :! echo 'hello = print "hello"' > Hello.hs
h> :l Hello
[1 of 1] Compiling Main             ( Hello.hs, interpreted )
Ok, modules loaded: Main.
h> hello
"hello"
h> :! echo 'hello = print "HELLO"' > Hello.hs
h> :r
[1 of 1] Compiling Main             ( Hello.hs, interpreted )
Ok, modules loaded: Main.
h> hello
"HELLO"

Рекомендованное чтение


Я отметил для себя следующие книги и сайты полезными, пока учил Хаскель самостоятельно.

Книги

  • Изучай Хаскель во имя добра!
    Эта книга оказалась для меня великолепной стартовой точкой, я рекомендую ее первой к прочтению. Она не заходит слишком глубоко, чтобы дать Вам ощущение ДЕЙСТВИТЕЛЬНО полного понимания GHC, но после ее прочтения я стал чувствовать себя комфорно в написании и понимании кода на Хаскеле.
  • Real World Haskell
    Это увесистая книга и по размерам, и по глубине, но это не мешает ей быть доступной даже новичку. Она охватывает множество вещей из Реального Мира: написание теста, профилирование, IO, параллелизм и т.д. Я до сих пор ее изучаю, но эта книга обязательна к прочтению.


Сайты

  • CS240h: Functional Systems in Haskell — был такой курс по Haskell в Стенфорде, который преподавали David Mazières и Bryan O'Sullivan. Он похож (но его охват шире) на тот курс, который я прошел в Facebook. Конспекты лекций и программа — просто фантастические, читайте их все!
  • Real World Haskell
  • Learn You a Haskell
  • haskell.org отличная вещь для погружения, там Вы найдете все эти ссылки + ГОРАЗДО больше. Рассчитывайте потратить там много времени!
  • H-99 содержит некоторые маленькие задачки, над которыми можно поработать. Он во многом похож на Euler project. Их довольно просто решить после прочтения LYAH.
  • Typeclassopedia отличный ресурс для изучения множества тайпклассов Haskell Platform
  • Hoogle — это поисковик по Haskell API, поддерживающий поиск по сигнатурам типов! Я провел за ним очень много времени.
  • Hayoo! еще один поисковик, подходит для случаев, когда невозможно найти необходимую информацию с помощью Hoogle
  • HWN — еженедельная рассылка Хаскеля, которая собирает в себе ключевые моменты из мэйл листов, вопросы со stackoverflow, реддит и т.п..
  • Haskell :: Reddit — сабреддит по Haskell
  • stackoverflow — haskell — вопросы по Haskell на stackoverflow, частенько достойны прочтения (если честно, очень часто там засиживаюсь после HWN)
  • C9 Lectures: FP Fundamentals 13 лекций по Основам Функционального Программирования (на Хаскеле), рассказанные Dr. Erik Meijer (я еще их не посмотрел, но их советовал Adam Breen).

IRC

#haskell на Freenode — то место, где Вы в любое время найдете несколько сотен человек, заинтересованных в Хаскеле. Отличное место для поиска помощи.

Предложения?


Я планирую пытаться поддерживать все это в актуальном состоянии, в зависимости от предложений. Если я пропустил что-то важное, дайте мне знать! Я не претендую на cоздание полной картины, мне кажется Haskell wiki с этим справляется гораздо лучше. Но основные моменты хотелось бы захватить.
Tags:
Hubs:
+43
Comments 17
Comments Comments 17

Articles