HPUNIX Сайт о ОС и не только!

Предпосылки, по которым мне нравится Haskell

2 апреля 2012 - unix
Причины, по которым мне нравится Haskell

Последний гиковcкий выпуск Radio-T (номер 253) вышел на уникальность увлекательным. Речь зашла о Scala, рефакторинге, TDD, багтрекерах, и даже (наконец!) о моем возлюбленном Haskell. К огорчению, тема «чем же так неплох этот ваш Haskell» не была в подабающей мере раскрыта.

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

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

Невзирая на то, что пункты пронумерованы, они никаким образом не отсортированы. Другими словами, нет такового, что 1-ый пункт важнее 5-ого либо вроде того.

Принципиально! Рекомендуется читать статью медлительно и задумчиво, а не потому что вы как правило это делаете :)

1. Краса и декларативность языка

Будучи вещью личной и не имеющей серьезного определения, «красота языка программирования» является прелестной темой для холиваров. Поэтому скажу так — мне Haskell кажется очень прекрасным языком. Некие считают, что Haskell сложен, но по сути это не так.

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

Печальный и злосчастный Java-программист, который обязан писать всякие private static final transient volatile List<Pair<Integer, GoddamnLongJavaBean>> dramaticallyTerribleList = new ArrayList<Pair<Integer,GoddamnLongJavaBean>>; когда кое-где совершенно рядом есть расчудесный Haskell // «о себе» 1-го хабраюзера

Приведу мало цифр. Описание языка в «Справочнике по языку Haskell» Романа Душкина занимает всего только 100 страничек. Другие Четыреста 30 страничек занимает описание модулей, которое мне еще никогда не пригодилось.

Объем книжки Learn You a Haskell for Great Good! (кстати, скоро будет издан российский перевод) составляет Четыреста страничек. Мне кажется, что при желании ее можно ужать в два раза, ничего при всем этом не утратив.

Для сопоставления, Язык программирования Си Кернигана и Ритчи имеет объем Триста страничек, а Язык программирования C++ Страуструпа — практически Одна тыща 100 страничек. Вы вправду считаете сложным язык, описание которого даже по самым умеренным оценкам имеет приблизительно тот же объем, что и описание языка Си, которое в свою очередь в 3-4 раза короче описания языка C++?

Здесь самое время привести незначительно кода, продемонстрировав, как обожают выражаться некие программеры, «высокую декларативность» языка. К примеру, вот так задается функция, возвращающая перечень делителей некоторого числа num:

getDivisors num
| num < Один = []
  | otherwise = [x | x <- [1..num], num `mod` x == Нуль ]
  -- ^ тривиальная оптимизация - [2..(floor.sqrt.fromIntegral) num + 1]
Причины, по которым мне нравится Haskell

Прямо как написать определение! Если обозначить огромное количество делителей числа n, как D(n), и перевести написанное выше на язык арифметики, то получим D(n) = x ∈ ?+, x ≤ n, mod(n, x) = Нуль , где n ∈ ?+. А сейчас давайте напишем функцию проверки числа на простоту:

isPrime num
  | num <= Один = False
  | otherwise = getDivisors num == [1,num]
Причины, по которым мне нравится Haskell

И опять — будто бы пишешь математическую формулу. Сейчас получим перечень всех обычных чисел:

Причины, по которым мне нравится Haskell
allPrimeNumbers = [2] ++ filter isPrime [3,5..]

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

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

Если думаете, что «высокая декларативность» Haskell распространяется лишь на математические задачки, то вы ошибаетесь. Поглядите, например, как смотрится код GUI-приложения на Haskell. Либо как можно писать запросы к базе данных при помощи библиотеки HaskellDB.

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

А еще бойцы против оператора goto будут рады выяснить, что в Haskell его нет.

2. Автоматическое управление памятью

Haskell на сто процентов берет управление памятью на себя. Цитата из WikiBooks:

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

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

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

Кстати, далековато не все из перечисленных заморочек по-настоящему решены в таких высокоуровневых языках, как Java, Perl и Python (о дилеммах C++ я вообщем молчу). К примеру, в книжке Python — подробный справочник Дэвида Бизли приводится пример программки (в моем экземпляре — на стр 174), использующей паттерн «наблюдатель», в какой собиратель мусора не в состоянии высвободить память, если только не пользоваться weakptr.

Люди, 21-ый век на дворе! Будем еще Девяносто лет управлять памятью при помощи костылей типа счетчиков ссылок и weakptr, а то и вообщем вручную? Либо в конце концов забудем, как ужасный сон, и будем двигаться далее?

3. Незапятнанные функции

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

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

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

Вот что написано в книжке Роберта Мартина Незапятнанный код — создание, анализ и рефакторинг по поводу побочных эффектов:

Побочные эффекты сущность ересь. Ваша функция обещает сделать что-то одно, но делает что-то другое, скрытое от юзера.

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

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

Дело в том, что в Haskell функции поделены на незапятнанные и «грязные», другими словами недетерминированные и имеющие побочные эффекты. «Грязные» функции употребляются для ввода данных, передачи их в незапятнанные функции и вывода результата. Другими словами, существует этакий маленькой процедурный мирок снутри незапятнанного многофункционального языка.

Либо напротив, это еще как поглядеть. Все превосходное — просто!

Причины, по которым мне нравится Haskell

4. Быстродействие

Повторюсь на счет ленивых вычислений. В Haskell что-то начинает рассчитываться только тогда, когда оно вправду пригодится.

Вы сможете объявить перечень из Девять тыщ частей, но если для вас пригодятся только какой-то из них (и он не будет зависеть от других частей перечня), то будет вычислено значение только этого 1-го элемента. И этот принцип работает всюду, а не только лишь в перечнях. В каком-нибудь C++ такое тоже можно сделать, но придется самому написать соответственный код либо подключить какую-нибудь библиотеку, а в Haskell все уже есть «из коробки».

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

Разглядим другой пример. Допустим, у нас есть некоторая функция f(a, b). Пусть аргументы a и b — результаты вычислений некоторых других функций, которые по радостному стечению событий являются незапятнанными.

В данном случае a и b могут быть вычислены параллельно! Согласитесь, в эру многоядерных микропроцессоров это важно. В ООП языках такую оптимизацию провести намного труднее.

Не считая того, что мы получаем прирост производительности, мы также автоматом распараллеливаем программку и синхронизируем потоки выполнения! А ведь многопоточное программирование с его дэдлоками — это чуть не 2-ая по значимости неувязка современного программирования после ручного управления памятью.

Если вы думаете, что описанное выше — только теория, поглядите перечень расширений компилятора GHC и проект Data Parallel Haskell. Все уже реализовано!

Дополнение: См также проект Haskell STM. Транзакционная память увлекательна тем, что позволяет избавиться от блокировок в многопоточных приложениях.

К огорчению, мне не удалось отыскать ни 1-го бэнчмарка программ на Haskell, не считая shootout.alioth.debian.org. «К сожалению» — так как у меня к этому бенчмарку много вопросов. Я отказываюсь веровать, что программки на Pascal производятся вдвое медлительнее программ на Си.

Также вызывают сомнения погрешности в стиле ±100% для скриптовых языков. Все же, если веровать этому бенчмарку, Haskell на сегодня является самым резвым языком многофункционального программирования. По скорости он сравним с языками Java и C#.

5. Меньше рефакторинга

Давайте откроем оглавление книжки Рефакторинг — улучшение имеющегося кода Фаулера и поглядим, какие типы рефакторинга бывают. Выделение способа, встраивание способа, перемещение способа, встраивание временной переменной, подмена временной переменной вызовом способа, введение объяснительной переменной, расщепление временной переменной, подмена ссылки значением, подмена значения ссылкой… как считаете, почти все ли из перечисленного применимо в Haskell при условии, что в этом языке нет ни ссылок, ни переменных (let и where не числятся)?

Не так давно я провел на Хабре два опроса, один — в блоге «Java», 2-ой — в блоге «Haskell». Вопрос был одним и этим же — «Как нередко для вас приходится создавать рефакторинг кода на %PROGRAMMING_LANGUAGE%?». На этих опросах я слил практически всю свою карму (кому-то правда глаза разрезает?), но оно того стоило:

Рефакторинг в Java и Haskell

Я готов признать, что часть опрошенных могла ответить, что не занимается рефакторингом на Haskell, так как не пишет на нем, но чуть ли это очень исказило картину. Во-1-х, в опросах есть кнопка «воздержаться», а во-2-х, возможно, посреди читателей блога «Java» также далековато не все пишут на Java.

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

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

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

6. А необходимы ли TDD и UML?

Вспомним, чему нас учит хоть какой учебник по Python либо Java. Все является объектом. Используйте объекты, и ваш код станет намного легче аккомпанировать, расширять и повторно использовать.

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

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

Что-что, простите? В каком это смысле «всего только надстройка над процедурным программированием»?! Вы что, не слушали?

Инкапсуляция, наследование, полиморфизм!

Нет, правда, вы никогда не думали, сколько необходимо использовать костылей, чтоб ООП работало? Скачайте хотя бы уже упомянутый 253-ий выпуск Radio-T и послушайте, что там молвят о TDD. Практически, нам предлагают делать в два, а то и втрое (считая UML) больше работы!

Причины, по которым мне нравится Haskell

Люди время от времени спрашивают, «Что служит аналогом UML для Haskell?». Когда меня в первый раз спросили об этом 10 годов назад, я помыслил, «Ума не приложу. Может быть, нам стоит придумать собственный UML». На данный момент я думаю, «Это просто типы!». // Саймон Пейтон Джонс

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

Возьмите 10 случайных модулей и поглядите, сколько из их вправду употребляют ООП (да, в Perl есть ООП), а не просто оформляют модуль в виде класса. Либо загляните на Hackage и посчитайте, сколько модулей полностью удачно решают практические задачки вообщем без одного намека на ООП.

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

Почему мне кажется, что «костылей» должно быть меньше при использовании Haskell? Ну, во-1-х, раз нет классов (не путать с классами типов) — не надо отрисовывать их диаграммы :) На теоретическом уровне ничто не мешает отрисовывать диаграммы классов типов, но мне такие еще никогда не попадались.

Во-2-х, как мы уже узнали, Haskell — «очень декларативный» язык. Другими словами, обычно мы пишем на нем, что желаем получить, а не как. Таким макаром, программка документирует сама себя.

И в-3-х, строгая статическая типизация языка позволяет устранить целые классы ошибок еще на шаге компиляции программки. Это не отменяет необходимость периодически писать испытания, но значительно уменьшает их количество.

Кстати, необходимыми качествами Haskell являются отсутствие побочных эффектов и кроссплатформенность языка, притом реальная кроссплатформенность, а не как у C++. Программка, написанная один раз, идиентично отлично работает под хоть какой ОС и хоть какой архитектурой микропроцессора без необходимости писать какие-то макросы либо вроде того. Это приводит к тому, что программеры на Haskell нередко (!) вообщем не компилируют свои программки.

Знаю, звучит несуразно. Сам не веровал, пока не попробовал. Это смотрится приблизительно так.

Создаем новый модуль. Вводим пару новых типов, объявляем первую функцию. Запускаем интерпретатор ghci, проверяем функцию на обычных данных и паре краевых случаев.

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

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

7. Широкая область внедрения

Распространенное заблуждение в отношении Haskell состоит в том, что этот язык типо подходящ для решения только узенького круга хитроумных математических задач. И вправду, как прикажете решать «обычные» задачки, если у нас даже циклов нет?

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

Вообще-то, конкретно злоупотребление ООП в конечном счете приводит к мышлению по шаблону, и выводам в стиле «видимо, эта задача с олимпиады по спортивному программированию, раз я не знаю, как ее решить».

Но вернемся к области внедрения. Как я уже отмечал, Haskell отлично подходит для написания GUI приложений. Писать на нем для интернет я еще не пробовал, но, судя по отзывам, Haskell и здесь совладевает не ужаснее PHP:

Я только-только окончил написание простой FastCGI программки на Haskell. Мне хотелось осознать, как работают веб-приложения на Haskell и подходит ли этот язык для сотворения веб-сайта, посвященного исследованию языков. Haskell не только лишь совладал с задачей. Оказалось, что писать на нем намного веселее, чем на PHP // jekor.com, свободный перевод

Еще примеры веб-сайтов на Haskell — hpaste.org и блог lymar.ru. Если вы предпочитаете работать с фреймворками, то будете рады выяснить, что для Haskell написано достаточно много готовых веб-фрейморков.

Дополнение: Обратите также внимание на российский перевод книжки о веб-фреймворке Yesod.

Haskell отлично подходит для работы с сетью, базами данных, постоянными выражениями, написания компиляторов и многого другого. Код на Haskell может вести взаимодействие с кодом на других языках и быть скомпилирован в байткод LLVM, JVM и .NET, также в JavaScript. На Haskell можно писать даже модули ядра Linux.

Посреди увлекательных проектов на Haskell мне хотелось бы отметить фреймовый оконный менеджер Xmonad, интерактивную среду разработки Leksah, генератор статических веб-сайтов Hakyll, torrent-клиент СombinaTorrent, веб-браузер Hbro, распределенную систему управления версиями Darcs, компилятор языка программирования Perl 6 под заглавием Pugs и (!) операционную систему House.

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

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

Дополнение: Книжку «Learn You a Haskell for Great Good!» в российском переводе уже можно заказать в интернет-магазинах. Также на просторах веба была найдена пригодная книжка Антона Холомьёва «Учебник по Haskell».

Похожие статьи

Теги: ос
Рейтинг: +7 Голосов: 234 1858 просмотров
Комментарии (0)

Нет комментариев. Ваш будет первым!

Найти на сайте: параметры поиска

Windows 7

Среда Windows 7 на первых порах кажется весьма непривычной для многих.

Windows 8

Если резюмировать все выступления Microsoft на конференции Build 2013.

Windows XP

Если Windows не может корректно завершить работу, в большинстве случаев это

Windows Vista

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