Язык программирования Haskell. Широкая область применения

Функциональное программирование на Haskell

Часть 1. Введение

Серия контента:

Императивное программирование

Наиболее распространены императивные языки , в которых вычисление сводится к последовательному выполнению инструкций. Решение задачи на императивном языке сводится к описанию того, что необходимо проделать, чтобы получить результат. Классические примеры – FORTRAN (1957), ALGOL (1958) и C (1972).

Императивные языки близки к архитектуре фон Неймана . В них весомую часть кода занимают присваивания значений переменным.

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

Пример императивного кода – процедура для вычисления факториала на C:

int fac (int n) { int x = 1; while (n > 0) { x = x*n; n = n-1; } return x; }

Здесь повторение операции умножения выражено через цикл. В процессе вычисления изменяются значения переменных x и n .

Инициализация: n:= 3; x:= 1; Первый виток цикла: x:= 1*3 = 3; n:= 3-1 = 2; Второй виток цикла: x:= 3 * 2 = 6; n:= 2 - 1 = 1; Третий виток цикла: x:= 6 * 1 = 6; n:= 1 - 1 = 0; Результат - 6

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

Функции и функциональность

В математическом смысле функция f: X → Y – это правило, сопоставляющее элементу x из множества X (области ) элемент y = f x из множества Y (кообласти ).


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

Если значения определены для всех элементов области, то функция называется всюду определенной; в противном случае она называется частичной.

Как и в математике, в интересующих нас функциональных языках не существует ячеек, хранящих различные значения. Переменные являются просто именами значений .

Обычно для задания функции в функциональном языке используются уравнения. Например,

fac 0 = 1 fac n = n * fac (n-1)

n – формальный аргумент функции fac . В правой части после знака = описано, что функция делает со своим аргументом. Базовые функции для умножения и вычитания записаны через инфиксные (указываемые между аргументами) операторы * и - .

Здесь уравнений два. При вычислении функции уравнения просматриваются по порядку. Если n = 0, то будет использовано первое уравнение. Если n ≠ 0, то оно не подойдет, и задействуется второе.

Обратите внимание: аргументы функции перечисляются рядом с ее именем через пробел. Это удобно, потому что применение функции очень часто встречается в функциональных программах (таблица 1).

Запись в математике Запись в Haskell
f(x) f x
f(x,y) f x y
f(g(x)) f (g x)
f(g(x),h(y)) f (g x) (h y)
f(x) + g(x) f x + g x
f (x+y) f (x+y)
f(-x) f (-x)

Таблица 1. Запись применения функции в математике и в Haskell

Уравнения для fac составляют точное определение функции, а не последовательность действий по вычислению, как это было в императивном коде.

Используется рекурсия, т. е. fac определяется через саму себя. Такое определение работает, потому что fac выражается через более простой случай и, в конечном счете (если n ≥ 0), доходит до базового случая n = 0. Вычисление fac 3 по такому определению можно произвести так (на каждом шаге подчеркнуты упрощаемые выражения):

fac 3 → 3 * fac 2 → 3 * (2 * fac 1) → 3 * (2 * (1 * fac 0)) → 3 * (2 * (1 * 1)) → 3 * (2 * 1) → 3 * 2 → 6

Здесь мы применили f к значению 3 . При этом 3 называется фактическим аргументом .

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

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

Обратите внимание, что для n < 0 наше определение будет вычисляться бесконечно, поэтому наша функция является частичной (если ее областью считать все целые числа).

Теперь запишем функцию, вычисляющую для целых k и вещественных r биномиальный коэффициент

При k ≥ 0 мы имеем выражение, где в знаменателе стоит только что определенный факториал числа k, а в числителе – убывающая факториальная степень (falling factorial power)

Запишем для нее рекурсивное определение:

ffp r 0 = 1 ffp r k = (r-k+1) * ffp r (k-1)

В первом уравнении r не используется, поэтому можно использовать заменитель _ и писать ffp _ 0 = 1 .

Можно убедиться, что

(проверьте это). Поэтому уравнения факториала можно заменить на

fac n = ffp n n

Как только мы записали уравнения функции ffp , ее можно рассматривать как абстрактный черный ящик с двумя входами и одним выходом не заботясь о том, что именно происходит внутри. Всё, что для нас теперь важно, – это корректное отображение входов в выход.

Рисунок 2. Черный ящик, вычисляющий убывающую факториальную степень

Возьмем еще один черный ящик (/) с двумя входами x и y и выходом, равным частному x/y:

будет вычислять такая схема:

Рисунок 3. Схема из черных ящиков, вычисляющая биномиальный коэффициент

Она соответствует выражению

ffp r k / fac k

Определим искомую функцию:

binc r k | k >= 0 = ffp r k / fac k | otherwise = 0

Такая запись называется уравнением с условиями (сравните с математической записью определения). После | и до знака = стоят условия. « otherwise » означает «иначе». Подробно это будет рассмотрено в последующих статьях.

Пример вычисления binc 6 2:

binc 6 2 → ffp 6 2 / fac 2 → (5 * ffp 6 1) / fac 2 → (5 * (6 * ffp r 0)) / fac 2 → (5 * (6 * 1)) / fac 2 → (5 * 6) / fac 2 → 30 / fac 2 → 30 / ffp 2 2 → 30 / (1 * ffp 2 1) → 30 / (1 * (2 * ffp r 0)) → 30 / (1 * (2 * 1)) → 30 / (1 * 2) → 30 / 2 → 15

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

Это приводит к важному понятию чистоты.

Чистота

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

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

Если побочных эффектов много, то программирование усложняется, в коде возникает больше ошибок, и его корректность становится тяжело проверить как формальными, так и неформальными средствами.

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

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

Каждое выражение в функциональном языке соответствует своему значению; вычисление только модифицирует выражение, но на значение не влияет.

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

Язык без побочных эффектов называется чисто функциональным .

Такие языки, как ML и Scheme, в целом являются функциональными по стилю, однако допускают как присваивания, так и побочные эффекты.

Примеры чисто функциональных языков: Miranda , Haskell и Clean .

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

Ленивость и нестрогость

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

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

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

Например, наша функция binc всегда будет требовать значение k , но значение r требуется, только если k ≥ 0.

В соответствии с этим, говорят о языках со строгой семантикой и языках с нестрогой семантикой. («Нестрогость» и «ленивость» – не одинаковые, но близкие понятия.)

Строгий язык вычисляет аргументы, прежде чем применять функцию. Таким образом, если вычисление выражения e не завершается (зацикливается или останавливается с ошибкой), то применение функции f e также не вычислится.

В нестрогом языке вычисление производится по необходимости. Если f нестрогая, то вычисление f e может завершиться, даже если вычисление e не завершается.

Например,

обозначает список из трех элементов. Вычисление fac (-1) зацикливается. Значит, вычисление списка также зациклится.

Пусть теперь функция length возвращает длину списка. Выражение

length

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

Примеры нестрогих языков: Miranda и Haskell. Строгие языки – ML и Scheme.

Вопрос строгости достаточно тонок, и его нельзя свести к простому «Что удобнее и эффективнее?» >Передача отложенных вычислений вместо значений в ходе вызова функции связана с определенными затратами. Вместе с тем во многих ситуациях ленивость позволяет экономить на вычислениях или записывать такие выражения, которые бы не вычислились или вычислялись бы бесконечно в энергичном языке.

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

Краткая история

Функциональное программирование черпает идеи из комбинáторной логики и лямбда-исчисления .

Одними из первых языков с функциональным стилем были LISP (1958), APL (1964), ISWIM (1966) и (1977).

К концу 1980-х годов уже имелось много функциональных языков. Среди тех, которые оказали значительное влияние на Haskell:

  • (1973) – первый язык с типизацией Хиндли–Милнера.
  • SASL , KRC , Miranda (1972–1985) – одни из первых ленивых языков.
  • Hope (1980) – один из первых языков с алгебраическими типами данных. Haskell разрабатывался многочисленной группой исследователей с 1987 г. Он назван в честь Хаскелла Карри .

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

Нововведения Haskell – монады и классы типов. Другие сильные стороны, заимствованные из других языков – каррирование, алгебраические типы данных, сопоставление с образцом. (Здесь мы просто приводим набор ключевых слов; все эти понятия скоро будут разъяснены.)

Последнее зафиксированное описание – Haskell 98, однако язык постоянно развивается и усложняется; сейчас планируется выход Haskell" .

Haskell стал самым популярным нестрогим чисто функциональным языком. На Haskell реализовано много сложных проектов:

  • Компиляторы и другие средства разработки.
  • Распределенная система управления версиями Darcs .
  • Оконный менеджер xmonad .
  • Сервер Web-приложений HAppS .
  • Интерпретатор/компилятор Pugs для языка Perl 6.
  • Операционная система House .
  • Язык описания аппаратных средств Lava .
  • Система обработки натурального языка LOLITA.
  • Системы доказательства теорем Equinox / Paradox и Agda .

Основные источники информации по Haskell

У Haskell имеется широкое и дружелюбное сообщество.

Основной источник информации – сервер haskell.org .

  • [email protected] – свободное обсуждение.
  • [email protected] – более простые темы для новичков.

    Есть очень оживленный IRC-канал #haskell на irc.freenode.net . В нем можно быстро получить любезный ответ практически на любой вопрос.

    Множество тематических блогов собирается на http://planet.haskell.org/

  • Хорошим введением в Haskell может быть руководство A Gentle Introduction to Haskell 98 .
  • Документацию по обширным библиотекам смотрите по адресу http://haskell.org/ghc/docs/latest/html/libraries/
  • Формальный отчет – The Haskell 98 Report .

Редактирование и выполнение кода

Реализации Haskell

Формально, Haskell не имеет какой-то одной «стандартной» реализации.

Для интерактивной работы подойдет легковесный интерпретатор Hugs .

Также есть интересный проект Yhc , компилирующий исходные тексты в байткод, и Helium – учебный вариант Haskell, дружественный к новичкам (например, выдающий более ясные сообщения об ошибках). Однако они еще находятся в процессе разработки.

Де-факто стандартным стал компилятор/интерпретатор GHC. Он является наиболее продвинутым, во всём соответствует стандарту и предлагает ряд экспериментальных расширений. Мы сконцентрируемся на нем.

GHC можно загрузить по адресу http://haskell.org/ghc/ . Если вы используете GNU/Linux, то посмотрите готовые сборки в своем дистрибутиве. Для MacOS X и Windows можно также найти бинарные файлы. (Учтите, что сборка GHC прямо из исходников может быть довольно утомительным занятием.)

Нас в первую очередь будет интересовать интерактивная программа ghci, в которой удобно испытывать учебные примеры.

Итак, после установки GHC вы можете запустить в терминале ghci:

Здесь Prelude – это название загруженного модуля. В Prelude содержатся основные определения, и он всегда задействуется по умолчанию. Изучая или переписывая самостоятельно код Prelude, начинающие могут узнать много нового. (Мы с вами тоже отчасти будем это делать.)

Символ > означает, что ghci ожидает пользовательский ввод. Это могут быть выражения Haskell, а также команды для интерпретатора.

Клавишами ← и → можно перемещать курсор по командной строке ghci. и ↓ пролистывают историю команд назад и вперед.

Вместо Prelude> или других имен модулей мы дальше будем писать ghci> (если хотите сделать так же, выполните в ghci команду:set prompt "ghci> ").

Для начала ghci можно использовать как продвинутый калькулятор:

ghci> 1*2 + 2*3 + 3*5 23 ghci> 23^23 ghci> gcd 3020199 1161615 232323

Операторы совпадают с принятыми в других языках (таблица 2).

Таблица 2. Арифметические операторы из Prelude

Для них используется инфиксная запись и соответствующий приоритет. Например, 2*3+4 соответствует (2*3)+4 , а 2^3^4 – 2^(3^4) . Чтобы изменить принятый приоритет, можно расставить скобки.

В ghci имеется специальная переменная it , равная значению последнего успешно вычисленного выражения.

ghci> 15 - 2 13 ghci> it + 10 23

Редактирование исходного кода

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

  • Расширение для Emacs : http://www.iro.umontreal.ca/~monnier/elisp/
  • Расширение для Vim : http://projects.haskell.org/haskellmode-vim/

Другие средства разработки описаны на странице http://haskell.org/haskellwiki/Libraries_and_tools/Program_development

Для кода Haskell используется расширение файлов.hs .

Если вы запишете код на Haskell в файл foo.hs , то определения загружаются в ghci командой:load foo . Параллельно файл можно редактировать и при необходимости перезагружать определения при помощи:reload .

Текущая директория изменяется командой:cd (например, :cd /home/bob).

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

$ ghci ghci, version 6.10.3: http://www.haskell.org/ghc/ :? for help Loading package ghc-prim ... linking ... done. Loading package integer ... linking ... done. Loading package base ... linking ... done. Prelude> :load fph01.lhs Compiling Main (fph01.lhs, interpreted) Ok, modules loaded: Main. *Main> ffp 6 6 720 *Main> fac 6 720 *Main> binc 6 2 15.0 *Main> binc 6.5 4 23.4609375

Эти и другие команды можно сокращать – вместо:load использовать:l , вместо:reload – :r и так далее.

Список команд интерпретатора выводит:help . Для выхода из ghci нужно набрать:quit .

В ходе нашего изложения часто будут встречаться простые примеры, состоящие из одного-двух уравнений. Их можно сразу вводить в ghci при помощи let:

ghci> let double x = 2*x ghci> double 23 46

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

ghci> let { fac 0 = 1; fac n = n * fac (n-1) } ghci> fac 23 25852016738884976640000

Заключение

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



Теоретические основы императивного программирования были заложены ещё в 30-х годах XX века Аланом Тьюрингом и Джоном фон Нейманом. Теория, положенная в основу функционального подхода, формировалась в 20-х и 30-х годах. В числе разработчиков математических основ функционального программирования - Мозес Шёнфинкель (Германия и Россия) и Хаскелл Карри (Англия), а также Алонзо Чёрч (США). Шёнфинкель и Карри заложили основы комбинаторной логики, а Чёрч является создателем лямбда-исчисления.

Функциональное программирование как раз основано на идеях из комбинаторной логики и лямбда-исчисления.

Но теория так и оставалась теорией, пока в начале 50-х прошлого века Джон МакКарти не разработал язык Lisp (1958), который стал первым почти функциональным языком программирования. На протяжении многих лет у Lisp не было конкурентов. Позднее появились функциональные языки программирования APL (1964), ISWIM (1966) и FP (1977), которые не получили столь широкого распространения.

Со временем Lisp перестал удовлетворять некоторым требованиям разработчиков программ, особенно с ростом объема и сложности программного кода. В связи с этим обстоятельством всё большую роль стала играть типизация. В конце 70-х - начале 80-х годов XX века интенсивно разрабатывались модели типизации, подходящие для функциональных языков.

Большинство этих моделей включали в себя поддержку таких мощных механизмов как абстракция данных и полиморфизм. Появилось множество типизированных функциональных языков: ML, Scheme, Hope, Miranda, Clean и многие другие. Вдобавок постоянно увеличивалось число диалектов.

ML (1973) – первый язык с типизацией Хиндли–Милнера;
Scheme (1975) - это один из двух самых популярных диалектов языка Lisp;
SASL, KRC, Miranda (1972–1985) – одни из первых ленивых языков;
Hope (1980) – один из первых языков с алгебраическими типами данных.


Хаскелл Карри

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

История языка Haskell начинается в 1987 году. Один за другим появлялись новые функциональные языки программирования. После выхода Miranda (Research Software Ltd, 1985 год) возрос интерес к ленивым вычислениям: к 1987 году возникло более дюжины нестрогих чисто функциональных языков программирования.

Miranda использовался наиболее широко, но это было запантетованное ПО. На конференции по функциональным языкам программирования и компьютерной архитектуре (FPCA, 1987) в Портленде (Орегон) участники пришли к соглашению, что должен быть создан комитет для определения открытого стандарта для подобных языков. Целью комитета являлось объединение существующих функциональных языков в один общий, который бы предоставлял базис для будущих исследований в разработке функциональных языков программирования.

Так появился Haskell. Он был назван в честь одного из основателей комбинаторной логики Хаскела Кaрри (Haskell Curry).

К концу 1980-х годов было создано много функциональных языков. Часть из них оказали значительное влияние на Haskell:

Новый язык должен был стать свободным языком, пригодным для исследовательской деятельности и решения практических задач. Свободные языки основаны на стандарте, который формулируется комитетом разработчиков. Дальше любой желающий может заняться реализацией стандарта, написать компилятор языка. Первая версия стандарта Haskell была опубликована 1 апреля 1990 года.

Haskell 1.0 - 1.4

Первая версия Haskell (Haskell 1.0) была выпущена в 1990г. Попытки комитета вылились в серию реализаций языка (1.0, 1.1, 1.2, 1.3, 1.4).

Haskell 98

В конце 1997 года версии Haskell 98 необходимо было специфицировать в одну стабильную, минимальную и портативную версию языка и сопроводительную стандартную библиотеку для изучения, как основу будущего развития. Комитет положительным образом принял создание расширений и вариаций haskell 98 путем добавления и внедрения экспериментальных особенностей.

В феврале 1999 года стандарт языка Haskell 98 впервые был опубликован, как «The Haskell 98 Report». В январе 2003 года измененная версия была опубликована как «Haskell 98 Language and Libraries: The Revised Report». Язык продолжил стремительно развиваться, реализация компилятора Glasgow Haskell Compiler (GHC) представляет фактический стандарт языка.

Haskell 2010

Современный стандарт Haskell - Haskell 2010 - был анонсирован 24 ноября 2009 года; GHC поддерживает его с версии 7.0.1.

По сравнению с Haskell "98 он содержал следующие изменения:

Do и If Then Else
Иерархические модули
Объявления пустых переменных
Решение устойчивости
Интерфейс сторонних функций
Линейный синтаксис комментариев
Охраняющие паттерны
Облегченных анализ зависимостей
Указания для языка (pragma)
Отсутствие паттернов n+k

Отсутствие контекста типов данных
Маркированные списки переменных

Haskell продолжает развиваться и сегодня. Но стабильные версии опираются на стандарты 1998 и 2010 года соответственно. Но кроме них в Haskell включается множество расширений, постоянно вносятся новые идеи. Над языком работают нескольких странах мира - это Англия, Нидерланды, Америка и Австралия. Интерес к Haskell вызван популярностью многопроцессорных технологий. Модель Haskell хорошо подходит для параллельных вычислений.

От создателя Haskell

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

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

Популярность

На сайте Github сейчас занимает 23-ю строчку по популярности среди языков программирования.

Интерпретатор/компилятор Pugs для языка Perl 6.

Язык описания аппаратных средств Lava .

Система обработки натурального языка LOLITA.

Системы доказательства теорем Equinox / Paradox и Agda .

Facebook

Фильтрация спама - одна из самых главных задач, которую решают инженеры Facebook. Крупнейшая социальная сеть обрабатывает сообщения от более 1,5 миллиарда человек, так что можно оценить масштаб проблемы. В 2015 году компания внедрила новые антиспамерские фильтры, для разработки которых использовала язык программирования Haskell.

Несмотря на молодость, экспериментальный статус и относительно низкую популярность, Facebook выбрал именно Haskell для создания важного модуля. Один из инженеров Луис Брэнди (Louis Brandy), который входит в группу разработчиков нового антиспамерского фильтра, провел два года за этим проектом вместе с коллегами. В интервью Wired он объяснил, чем они руководствовались.

Луис Брэнди, аккуратно подбирая слова, назвал Haskell идеально подходящим для реализации спам-фильтра в Facebook, потому что он отлично обрабатывает параллельные задачи и позволяет разработчикам легко и удобно устанавливать эти задачи на лету. Facebook настолько большой проект, а спамеры настолько быстро меняют тактику, что необходим инструмент для разработки и постоянной модификации спам-фильтров, которые вступят в действие немедленно.

Если посмотреть на развитие современного интернета, то по этому пути должны пойти многие интернет-проекты, для которых важны масштабируемость и реагирование в реальном времени. По мнению разработчиков Facebook, у языка Haskell есть все шансы на широкую популярность. Мешает только тот факт, что Haskell довольно сильно отличается от других языков - и это затрудняет массовую миграцию на него.

Тем не менее, индустрия точно двигается в нужном направлении, как показывает пример новых языков программирования, ориентированных на выполнение параллельных процессов, например, Go от Google и Rust от Mozilla. Пусть они не такие эффективные, как Haskell, зато проще в изучении. В любом случае, Haskell можно поблагодарить за то, что он подтолкнул к развитию другие языки программирования и способствовал запуску новых перспективных проектов.

Евгений Козлов в своем блоге рассказал о впечатлениях от работы с Haskell:

Недостатки
В первую очередь – высокий порог вхождения в язык. Да, Haskell прекрасен, чист и лаконичен, но достигается это не бесплатно, а путём долгого перестроения мозга и изучения сложных абстракций вроде монад, монадных трансформеров, линз и машин.

Во-вторых – это сложность написания производительного кода. Так как в Haskell весь код ленив, то можно запросто достичь такой ситуации, когда в памяти накоплены гигабайты отложенных вычислений, но они не вычисляются, потому что ещё не затребованы.

Положительные стороны
В первую очередь, конечно же чистота языка. Чистота в обоих смыслах: чистота функций и полное отсутствие ООП-парадигмы. Это очень здорово, что ты можешь посмотреть на сигнатуру функции и понять, может ли она производить побочные эффекты или нет. Отсутствие ООП означает, что нет возможности сделать страшные вещи вроде непроверенных приведений из базового класса в подкласс.

Если написан код, то его сложно интерпретировать двумя способами. Например, нельзя перепутать применение функции и ссылку на функцию, как в Scala, так как в Haskell функции являются объектами первого класса. Или, например, нельзя перепутать функцию с типом или классом, так как все функции должны начинаться с маленькой буквы, а типы/классы – с большой.

Свойство языка, без которого разговор о Haskell был бы бессмысленным – это полиморфизм. Вряд ли будет преувеличением, если я скажу, что Haskell является языком с максимальным количеством переиспользованного кода. Любая хоть сколько-нибудь повторяющаяся функциональность выносится в абстракцию.

В 1985 году был разработан первый полностью функциональный язык, названный Мирандой (предшественником его был Lisp, который нельзя считать чисто функциональным). Он был очень популярен в среде программистов, однако, к сожалению, принадлежал только одной компании. Поэтому его дальнейшее развитие было сильно ограничено, что привело к возникновению множества похожих на него независимых языков. В 1987 в Орегоне был собран комитет, состоящий из энтузиастов функционального программирования, который постановил, что им необходим единый язык, который бы сосредоточил в себе лучшие достижения ФП за последние годы. В результате этого в 1990 году вышла первая версия, названная по имени Хаскелла Карри, известного математика. В 1999 был опубликован стандарт языка, а компилятор GHC, разработанный в университете Глазго стал самой известной реализацией языка (кстати, GHC, примерно на 80 % написан на самом хаскелле).

Философия языка Haskell

Haskell, мягко говоря, не похож на большинство своих объектно-ориентированных собратьев. Например, здесь нет привычных операторов присваивания значения переменной (вообще, переменных тоже нет) или циклов (вместо циклов используется рекурсия). А ещё, он с самого начала поддерживал такие вещи, как лямбда-исчисления, нестрогую семантику, монадическую систему. Некоторые элементы из этого (лямбда-выражения, которые происходят от лямбда-исчислений) только недавно внедрили в такие языки, как Java, Python или JavaScript.

Особо стоить отметить так называемые ленивые (отложенные) вычисления. Суть их сводится к тому, что вычисления не производятся, пока они не будут необходимы. Так, например, если мы определили некоторые выражения (пусть будут две разных суммы чисел или два разных произведения), то они не будут вычислены до тех пор, пока их не потребуется сравнить между собой или произвести с ними другие операции. Отсюда же происходит и другая потрясающая возможность Haskell - манипулирование бесконечными последовательностями (списками). Если на объектно ориентированном языке попробовать работать с такими данными, то получим просто переполнение памяти. А ленивые вычисления позволяют получать только необходимые элементы из этих списков, поэтому программа на хаскелле будет иметь большое преимущество перед другими. Например, в задачах по сортировке данных. Это также позволяет сокращать число шагов выполнения и экономить ресурсы оперативной памяти и решить проблему распараллеливания программы не прикладывая никаких усилий.

Будет также нелишним рассказать о HaskellDB. Это библиотека, которая позволяет писать запросы к базе данных при помощи функций Haskell, не используя SQL. Её главное преимущество в том, что запросы проверяются во время компиляции. Но если необходимо, то в хаскелле можно использовать и просто SQL запросы, без данной библиотеки.

Где применяется Haskell

Haskell некоторое время назад был известен только в сфере фанатов математики и функционального программирования, однако, вопреки слухам, это уже давно не так. Сейчас его используют в Facebook для фильтров спама и он успешно справляется со своей задачей. Возможности Хаскель изучали специалисты в Microsoft Research, в результате чего появилась урезанная версии языка, названная F#, которая сейчас доступна в Visual Studio.

Разработчики промышленных приложений тоже уже оценили преимущества функционального Haskell. В СНГ некоторые компании использовали Haskell для разработки систем управления услугами,а в Европе его многократно использовали в сфере финансового программирования (инвестиционные банки).

Haskell также хорошо подходит для создания оконных приложений (GUI). Можно найти сделанные на Haskell текстовые редакторы, оконные менеджеры, торрент-клиенты, игры (шутеры и логические), браузеры, движки для рендеринга 3D и.т.д. Уже существуют веб-фреймворки на нем, а также приложения для работы с базами данных. Также известен инструмент для криптографии под названии Cryptol и система управления версиями Darcs.

Сложность обучения Haskell

О сложности Haskell трудно судить. С одной стороны, в нем есть много вещей, которые могут облегчить понимание для новичков, с другой в нем много математических абстракций. Однако, для начинающих изучение Haskell гораздо меньшая проблема, чем для тех, кто уже знает один из объектно-ориентированных языков. В этом случае придется забыть, всё чему тебя учили раньше и перестраивать своё мышление под новый стиль. Не многие из опытных программистов могут это преодолеть (поэтому чаще всего критикуют Haskell за сложность и непонятность).

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

Плюсы/минусы Haskell

Плюсы Хаскелля - это его красота и лаконичность. Программы на Haskell проще читать и понимать (даже не зная некоторых деталей языка). Его синтаксис защищает программиста от типичных ошибок. На нем проще спроектировать и создать даже большие сложные программы. И он лишен многих типичных проблемы других языков.

Из минусов:

Haskell - это язык будущего. Но сейчас в мире господствует ООП, что мешает его популяризации. Не все программисты хотят бросить привычный стиль и перейти на нечто непривычное для них. Это можно сравнить с появлением нового религиозного учения, которое не признают, но именно оно в итоге станет доминирующими, а предыдущее будет практически забыто или спустится с лидерских позиций на несколько ступенек ниже.

Поэтому, на текущий момент, будет трудно найти вакансию программиста на этом языке (разве что, в некоторых лабораториях, разрабатывающих искусственный интеллект).

Сопутствующие технологии

GHC - самый популярный, на сегодняшний день, компилятор для хаскелля.

Snap, Yesod, Happstack - фреймворки, предназначенные для веб-разработки.

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

Bluespec SystemVerilog - расширение для хаскелля, применяется для проектирования полупроводниковых схем.

Мне задавали их множество раз. Отвечаю.

«Что такое этот ваш Haskell?»

Haskell - чисто функциональный язык программирования общего назначения, может быть использован для решения самого широкого круга задач. Компилируемый, но может вести себя и как скриптовый. Кроссплатформенный. Ленивый, со строгой статической типизацией. И он не похож на другие языки. Совсем.

«Это что, какой-то новый язык?»

Вовсе нет. История Haskell началась ещё в 1987 году. Этот язык был рождён в математических кругах, когда группа людей решила создать лучший функциональный язык программирования. В 1990 году вышла первая версия языка, названного в честь известного американского математика Хаскелла Карри . В 1998 году язык был стандартизован, а начиная с 2000-х началось его медленное вхождение в мир практического программирования. За эти годы язык совершенствовался, и вот в 2010 мир увидел его обновлённый стандарт. Так что мы имеем дело с языком, который старше Java.

«И кто его сделал?»

Haskell создавался многими людьми. Наиболее известная реализация языка нашла своё воплощение в компиляторе GHC (The Glasgow Haskell Compiler), родившегося в 1989 году в Университете Глазго. У компилятора было несколько главных разработчиков, из которых наиболее известны двое, Simon Peyton Jones и Simon Marlow . Впоследствии весомый вклад в разработку GHC внесли ещё несколько сотен человек. Исходный код компилятора GHC открыт . Кстати, сам компилятор на 82% написан на Haskell.

Для любопытных: исчерпывающее повествование об истории Haskell и GHC читайте .

«А библиотеки для Haskell имеются?»

О да! Их даже не сотни - их тысячи. В процессе чтения вы познакомитесь со многими из них.

«И что, его уже можно в production?»

Он уже в production. С момента выхода первого стандарта язык улучшался, развивалась его экосистема, появлялись новые библиотеки, выходили в свет книги. Haskell полностью готов к серьёзному коммерческому использованию, о чём свидетельствуют истории успешного внедрения Haskell в бизнесе, в том числе крупном .

«А порог вхождения в Haskell высокий?»

И да и нет. Освоение Haskell сложно в первую очередь из-за его непохожести на остальные языки, поэтому людям, имеющим опыт работы с другими языками, мозги поломать придётся. Именно поломать, а не просто пошевелить ими: Haskell заставляет иначе взглянуть даже на привычные вещи. С другой стороны, Haskell проще многих известных языков. Не верьте мне на слово, вскоре вы и сами в этом убедитесь. И знайте: многие люди, узнав вкус Haskell, категорически не желают возвращаться к другим языкам. Я вас предупредил.

«А я слышал ещё про какие-то монады…»

Да, есть такое дело. Некоторые вещи из мира Haskell не имеют прямых аналогов в других языках программирования, и это вводит новичков в ступор. Но не беспокойтесь: я сам прошёл через этот ступор и хорошо вас понимаю. Помните: новое лишь кажется страшным.

«А если сравнить его с C++/Python/Scala…»

Сравнение Haskell с другими языками выходит за рамки этой книги. Несколько раз вы встретите здесь кусочки кода на других языках, но я привожу их исключительно для того, чтобы подчеркнуть различие с Haskell, а вовсе не для сравнения в контексте «лучше/хуже». И вообще, я буду изо всех сил стараться не восхвалять Haskell без меры, я хочу лишь рассказать вам правду о нём. Мой вывод об этом языке я уже сделал, а свой вывод о нём вы должны сделать сами.



Есть вопросы?

Сообщить об опечатке

Текст, который будет отправлен нашим редакторам: