it-roy-ru.com

Что за шум вокруг Хаскелла?

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

Может кто-нибудь привести несколько примеров на Haskell, которые показывают, почему он такой элегантный/превосходный?

107
Frank

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

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

130
Edward Z. Yang

Это пример, который убедил меня изучить Haskell (и я рад, что сделал это).

-- program to copy a file --
import System.Environment

main = do
         --read command-line arguments
         [file1, file2] <- getArgs

         --copy file contents
         str <- readFile file1
         writeFile file2 str

Хорошо, это короткая, читаемая программа. В этом смысле это лучше, чем программа на Си. Но чем это так отличается от (скажем) программы Python с очень похожей структурой?

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

Хаскель "ленив". Он не вычисляет вещи до тех пор, пока ему это не нужно, и, соответственно, не не вычисляет вещи, которые ему никогда не нужны. Например, если бы вы удалили строку writeFile, Haskell не стал бы вообще ничего читать из файла.

На самом деле, Haskell понимает, что writeFile зависит от readFile, и поэтому может оптимизировать этот путь данных.

Хотя результаты зависят от компилятора, обычно при запуске вышеуказанной программы происходит следующее: программа читает блок (скажем, 8 КБ) первого файла, затем записывает его во второй файл, затем читает другой блок из первого файл, и записывает его во второй файл, и так далее. (Попробуйте запустить strace на нем!)

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

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

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

  1. Лучший дизайн программы. Снижение сложности приводит к меньшему количеству логических ошибок.

  2. Компактный код. Меньше строк для ошибок.

  3. Ошибки компиляции. Просто много ошибок не являются допустимыми Haskell .

Хаскелл не для всех. Но каждый должен попробовать.

133
Artelius

Вы как бы задаете не тот вопрос.

Хаскель - это не язык, на котором вы смотрите, смотрите несколько интересных примеров и говорите: "Ага, теперь я вижу, вот что делает это хорошо!"

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

  • Ленивая оценка
  • Никаких побочных эффектов (все чисто, IO/etc происходит через монады)
  • Невероятно выразительная статическая система типов

а также некоторые другие аспекты, которые отличаются от многих основных языков (но разделяются некоторыми):

  • функциональная
  • значительные пробелы
  • предполагаемый тип

Как ответили некоторые другие авторы, комбинация всех этих функций означает, что вы думаете о программировании совершенно по-другому. И поэтому трудно придумать пример (или набор примеров), который адекватно сообщает об этом Joe-mainstream-programmer. Это экспериментальная вещь. (Чтобы провести аналогию, я могу показать вам фотографии моей поездки в Китай в 1970 году, но, посмотрев фотографии, вы все равно не узнаете, каково это было жить там в то время. Точно так же я могу показать вам Haskell "Быстрая сортировка", но вы все равно не будете знать, что значит быть Хаскеллером.)

65
Brian

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

Когда дело доходит до написания программы для реального использования, вы можете обнаружить, что Haskell не хватает практического подхода, но ваше окончательное решение будет лучше, если вы знаете Haskell с самого начала. Я определенно еще не там, но пока изучение Хаскелла было намного более поучительным, чем, скажем, LISP учился в колледже.

25
gtd

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

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

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

22
Don Stewart

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

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

c1 `mappend` c2 `mappend` c3

где c1 и т. д. возвращают LT, EQ или GT, c1, возвращая EQ, чтобы выражение продолжалось, вычисляя c2; если c2 возвращает LT или GT, это значение целого, и c3 не оценивается. Подобные вещи становятся значительно более сложными и сложными в таких вещах, как генераторы монадических сообщений и парсеры, где я могу переносить различные типы состояний, иметь различные условия прерывания или иметь возможность принимать решение для любого конкретного вызова, действительно ли прерывание означает "нет дальнейшей обработки" или означает "вернуть ошибку в конце, но продолжить обработку для сбора дальнейших сообщений об ошибках".

Это все, что требует некоторого времени и, возможно, немало усилий для изучения, и, таким образом, может быть трудно привести убедительные аргументы для тех, кто еще не знает этих методов. Я думаю, что учебник All About Monads дает довольно впечатляющую демонстрацию одного аспекта этого, но я не ожидаю, что кто-то, не знакомый с материалом, уже "получит его" с первого раза, или даже третье, внимательное чтение.

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

18
Curt J. Sampson

Software Transactional Memory - это довольно крутой способ работы с параллелизмом. Это гораздо более гибко, чем передача сообщений, и не подвержено взаимной блокировке, как мьютексы. GHC's реализация STM считается одной из лучших.

17
bmdhacks

Интересный пример вы можете посмотреть по адресу: http://en.literateprograms.org/Quicksort_ (Haskell)

Что интересно, так это посмотреть на реализацию на разных языках.

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

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

11
James Black

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

7
maxaposteriori

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

Например, если вы хотите вычислить все простые числа, вы можете использовать

primes = sieve [2..]
    where sieve (p:xs) = p : sieve [x | x<-xs, x `mod` p /= 0]

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

foo = sum $ takeWhile (<100) primes

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

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

7
waxwing

Я согласен с другими, что увидеть несколько небольших примеров - не лучший способ показать себя Хаскеллом. Но я все равно дам немного. Вот молниеносное решение задачи проекта Эйлера 18 и 67 , которое попросит вас найти путь с максимальной суммой от основания до вершины треугольника:

bottomUp :: (Ord a, Num a) => [[a]] -> a
bottomUp = head . bu
  where bu [bottom]     = bottom
        bu (row : base) = merge row $ bu base
        merge [] [_] = []
        merge (x:xs) (y1:y2:ys) = x + max y1 y2 : merge xs (y2:ys)

Вот полная, многократно используемая реализация алгоритма BubbleSearch Леша и Митценмахера. Я использовал его для упаковки больших медиа-файлов для архивного хранения на DVD без потерь:

data BubbleResult i o = BubbleResult { bestResult :: o
                                     , result :: o
                                     , leftoverRandoms :: [Double]
                                     }
bubbleSearch :: (Ord result) =>
                ([a] -> result) ->       -- greedy search algorithm
                Double ->                -- probability
                [a] ->                   -- list of items to be searched
                [Double] ->              -- list of random numbers
                [BubbleResult a result]  -- monotone list of results
bubbleSearch search p startOrder rs = bubble startOrder rs
    where bubble order rs = BubbleResult answer answer rs : walk tries
            where answer = search order
                  tries  = perturbations p order rs
                  walk ((order, rs) : rest) =
                      if result > answer then bubble order rs
                      else BubbleResult answer result rs : walk rest
                    where result = search order

perturbations :: Double -> [a] -> [Double] -> [([a], [Double])]
perturbations p xs rs = xr' : perturbations p xs (snd xr')
    where xr' = perturb xs rs
          perturb :: [a] -> [Double] -> ([a], [Double])
          perturb xs rs = shift_all p [] xs rs

shift_all p new' [] rs = (reverse new', rs)
shift_all p new' old rs = shift_one new' old rs (shift_all p)
  where shift_one :: [a] -> [a] -> [Double] -> ([a]->[a]->[Double]->b) -> b
        shift_one new' xs rs k = shift new' [] xs rs
          where shift new' prev' [x] rs = k (x:new') (reverse prev') rs
                shift new' prev' (x:xs) (r:rs) 
                    | r <= p    = k (x:new') (prev' `revApp` xs) rs
                    | otherwise = shift new' (x:prev') xs rs
                revApp xs ys = foldl (flip (:)) ys xs

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

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

6
Norman Ramsey

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

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

5
rpg

Я считаю, что для определенных задач я невероятно продуктивен с Haskell.

Причина кроется в лаконичном синтаксисе и простоте тестирования.

Вот как выглядит синтаксис объявления функции:

фу а = а + 5

Это самый простой способ придумать определение функции.

Если я напишу обратное

inverseFoo a = a - 5

Я могу проверить, что это обратный для любого случайного ввода, написав

prop_IsInverse :: Double -> Bool
prop_IsInverse a = a == (inverseFoo $ foo a)

И звонит из командной строки

jonny @ ubuntu: runhaskell quickCheck + имена fooFileName.hs

Который проверит, что все свойства в моем файле сохранены, путем случайной проверки входов в сто раз.

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

5
Jonathan Fischoff

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

3
Dr. Watson

у него нет циклических конструкций. не многие языки имеют эту черту.

2
Badri

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

1
Jacob