Я долго (несколько лет) не решался составить окончательное мнение о Haskell'e: слишком противоречивы были мысли. И вот, наконец, благодаря этой записи о разборе программки определения двудольности графа я могу это сделать :)
Я понял, что Haskell-программисты — в основном, нужно сказать, хобби-программисты — это те, кто программирует не решение задачи, алгоритм, систему, а Haskell! [1] Посмотрите, какой простой алгоритм описан в заметке, а сколько вокруг него нагромождено языковых конструкций, объяснений и дискуссий. Чтоб доказать, что он очень простой, привожу пример кода на Lisp'е, который решает ту же задачу без никаких монадных трансформеров и т.п. (не обращайте внимание на большой docstring):
(defun test-bipartite (graph)
"Test for bipartiteness a GRAPH, that is given as a list of pairs
vertix - list of immediately connected vertices, like:
'((0 . (1)) (1 . (0 2 8))
(2 . (1 3)) (3 . (2 6))
(4 . (5 7)) (5 . (4))
(6 . (3)) (7 . (4 8))
(8 . (1 7)))
-- bipartite one
'((0 . (1 2)) (1 . (0 2 8))
(2 . (1 3)) (3 . (2 6))
(4 . (5 7)) (5 . (4))
(6 . (3)) (7 . (4 8))
(8 . (1 7)))
-- not bipartite one
'((0 . (1 2)) (1 . (0 2 8))
(2 . (1 3)) (3 . (2 6))
(4 . (5 7)) (5 . (4))
(6 . (3)) (7 . (4 8))
(8 . (1 7)) (9 . ()))
-- not connected one"
(let ((map (make-hash-table))
(visited '()))
(labels ((con1 (v)
"vertices immediately connected to V"
(tail (assoc v graph)))
(paint (v level)
"paint the graph from vertix V, return all
visited so far (in subsequent calls of PAINT) vertices"
(pushnew v visited)
(setf (gethash v map) level)
(mapc (lambda (v) (paint v (1+ level)))
(remove-if (lambda (v) (member v visited))
(con1 v)))
visited))
;; first we paint and check, that all vertices are visited
(unless (set-exclusive-or (paint 1 0)
(mapcar #'first graph))
(every (lambda (entry)
(every (lambda (v)
(oddp (+ (gethash (first entry) map)
(gethash v map))))
(tail entry)))
graph)))))
Мне хорошо знакомо это умонастроение — когда в погоне за максимальным использованием мощи языка забываешь о самой задаче,— поскольку в Lisp-мире оно тоже часто встречается: есть языки, которые способны действительно увлечь. И выражение "это взорвало мне мозг" часто звучат и по поводу Lisp'а, и по поводу Haskell'а. Но это же — фигня! Конечно, не может не радовать узнать что-то новое, но не нужно же радоваться этому, как ребенок новой игрушке. Хороший язык программирования должен быть максимально понятен и прост, должен давать человеку свободу самовыражения. Честно говоря, именно этому я обрадовался, когда открыл для себя Lisp: что нашел то, что искал. А не тому, что увидел какую-то конструкцию или изворот, который не доводилось встречать раньше.
Так что же, вывод: всем программировать на Lisp? Я, конечно, за, но вывод тут другой: Haskell — очень интересный язык, у которого есть как плюсы, так и минусы. Плюсы: это интересная семантика и сильная теоретическая база, хорошая скорость выполнения современных компиляторов. Минусы: ужасно нерегулярный синтаксис [2], искусственная ограниченность, которая приводит к необходимости задействовать сложные подоходы там, где отлично справятся и простые. И им просто обязательно стоит заниматься, если вас интересует тема языков программирования как таковых, их развития и исследований. Из Haskell берут многое другие более практичные языки: яркий пример тому Clojure. Но он не для написания больших систем и даже не для исследования алгоритмов в общем случае. У языков программирования кроме синтаксиса и семантики есть еще третий аспект, пожалуй даже важнейший, о котором часто забывают — прагматика. То, как язык используется, для чего он предназначается, чем живет сообщество его разработчиков и пользователей. Прагматика Haskell'а заключается в том, что он существует прежде всго для исследования... Haskell'а.
[1] Есть, конечно, исключительные, прекраснейшие Haskell-программисты, написавшие на нем много полезного кода для реального мира, но это, как говорится в нелюбимом мной афоризме, только подтверждает правило.
[2] Для современного языка нерегулярный синтаксис — это неуважение к своим пользователям. Ведь никто в современном мультиязыковом мире не программирует на одном языке, поэтому нельзя требовать от человека держать в голове идиосинкразии каждого. И этих общеупотребимых языков будет все больше и больше, а количество legacy кода уменьшаться не будет. Я сейчас имею дело с Lisp, Python, Php, C, JavaScript, Shell, Java. И это ведь не самый яркий пример.