Как стать автором
Обновить

Комментарии 162

Спасибо за статью. Хотя быть может стоило пожалеть мозги неподготовленных читателей…
Вы только что сломали мой мозг. Хотя я уже был немного подготовлен. Буду изучать.
Видимо перестарался, ну так если что непонятно, вы говорите, постараюсь исправиться.
Да просто синтаксис нечитабельный совершенно. Трудно разобраться, куда точки, куда строчки.
Скорее он непривычный, особенно после Си-подобных языков.
Восхитительно, особенно преобразования!
отлично написано: просто и по сути
всё очень доступно изложено.
спасибо!
проблема тут вот в чём

типичному программисту на пых-пыхе непонятно, к чему всё это

списки, lazy eval, ссылочная прозрачность, ФВП, GADT, карринг, typeclasses, Y-комбинатор, изъебства с I/O, континуации?

вот я беру, что там у нас… переменную, цикл, класс, метод, ассоциативный массив, print/echo и… творю… мне ни к чему эти ваши флюгергехаймеры

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

какие из этого выводы?

вероятно, конкретно Haskell тут вообще ни при чём — надо заходить издалека, с теории, подкреплённой хорошими, рабочими аналогиями…

типа, calculus 101
я уже не говорю про синтаксис ML-подобных, мотивы и истоки которого совсем непонятны людям, которые по работе сталкиваются в основном с foo(bar) { }
Вот это я и хочу показать на примере реализации чата. Можно будет оценить, что стало проще, что сложнее.
В целом всё это даёт более короткую запись, а строгая типизация позволяет практически избежать ошибок.
Это уже большой плюс. Конечно, те же бесконечные списки можно запрограммировать итераторами, но Haskell потому и стоит учить, что он позволяет по-другому решать задачу, хотя потом её можно будет записать хоть на Си++.
А зачем всё это типичному программисту на пыхпыхе? Как говорится, «нам тут лишние люди не нужны!».
Ну знаете, иногда приятно отойти от догм. Расширяет кругозор, я вот начал учить японский потому, что он в корне другой. Расширение кругозора не поможет сделать очередной чат, но поможет сделать что-то совершенно новое. Мне это интересно.
Учите сами или посещаете курсы? сорри за оффтопик))
Сам. Но оказалось много людей, кто может помочь. Сегодня только на работе парня из Владика распрашивал.
хороший программист должен уметь писать алгоритмы, а не только код.
У нас в МИЭМ, кстати, сформировался небольшой кружок Хаскеля
ммм, а почему про ленивость ничего не написано?
и еще кое-что… при программировании на Haskell (и многих других функциональных языках, например, Lisp) очень удобно пользоваться редакторами поддерживающими REPL (Read-eval-print loop). Например, emacs'ом (c haskell-mode) или патченым (sic!) vim'ом…
Посоветуйте, что можно отдельно написать про ленивость?
То, что бесконечный список не вычисляется до его вывода на экран, я показал.
Или вы о нюансах?
Ну, на мой взгляд важно акцентировать внимание на том, что функция в реальности не вычисляется до того момента, когда понадобится…

Как это написать, я с наскоку не скажу…

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

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

ты просто пишешь то, что думаешь, а constraint (в моём случае — проверка на циклы) накладывается сверху, уже потом

это так же просто, как take 5 [1..]

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

Потому как написание чата на Хаскеле, оно, конечно, весьма интересно, но вряд ли имеет практическую ценность сильно бОльшую, чем написание того же чата на Brainfuck.
Поддержу!
Лингвистические абстракции — это, конечно, хорошая гимнастика для ума, но нам, людям приземленным и конкретным (переменные, циклы, классы — это наше всё) хотелось бы понять в чём суть подхода на задачах, которые он (подход) призван решить.
Он призван сократить количество кода, а это работает практически на любых задачах. Я, честно говоря, не знаю, есть ли какая-то конкретная задача, где можно оценить функциональный язык во всей красе. Ну, т.е. по-моему любая задача подходит :)
Обработка деревьев и списков, конечно, хорошо, но это обычно составная какой-то более сложной задачи, а не сама программа.
Фунциональный подход очень удобен при генерации XML, например. Простейший пример на PHP:

function e($name, $value='')
{
    return "<$name>$value</$name>";
}

print e('root', e('one', 1).e('two', 2));


Этот пример позволяет легко понять и объяснить самые базовые вещи самым «упёртым» пэхапешникам.
Не особо интересно, если честно.
Подобный способ создания древовидных структур во-первых, достаточно очевиден, и во-вторых, используется давно и многими безо всяких специальных функциональных языков.
Вы удивитесь, но для кучи людей он совершенно не очевиден и воспринимается с трудом. Если человек не способен это понять, то можно на него не тратить усилия по обучению ФП.
Я ответил чуть выше.
В общем-то мне самому интересно в этом разбираться, так как на практике я Haskell не использовал (а хочу), так что если есть интересные задачи, я готов попробовать.
Но выделить задачу, в которой ФЯ был бы удобнее, — сложно. Он везде удобнее, правда, за это приходится платить.
То, что это чат, — не принципиально. Там есть и многопоточность (Chan, MVar, forkIO), и GUI, и бесконечные списки. В принципе это же самое можно показать на примере любой другой программы.
«Везде удобнее» — это ни о чем. Это лозунг в стиле «за все хорошее против всего плохого», никакого отношения к действительности не имеющий.

Функциональный подход используют многие программисты и весьма регулярно. Выше приведен типичный пример создания иерархической структуры, применявшийся еще черт знает когда (я первый раз с ним столкнулся, осваивая Turbo Vision в мохнатом году). Я сам его регулярно применяю (в тех случаях, где он мне кажется уместным) — на Delphi и PHP.

И потому скажу свое мнение человека, 15 лет занимающегося структурным и объектно-ориентированным программированием — интересно посмотреть прежде всего на решение практических задач. Потому как еще нет инструмента, который был бы лучше «везде» и «всегда». А вот где и как именно — очень интересно.
после функционального языка приходишь в старую добрую Java

и это выглядит как-то так

Neo You: I know Kung Fu!
Java: Show me

и тут начинается депрессия…
Дебаггеры под это дело есть? В 20 строчках этого языка можно запутаться на неделю…
там не нужны дебаггеры
Тут фактически сам язык — дебаггер.
Вообще говоря вроде есть, но я никогда не пользовался.
REPL'а вполне достаточно. Можно любую функцию определить и тут же её прогнать.
два разных подхода к программированию: Python/Ruby/JavaScript и Haskell

программист на Python:

1. пишет функцию
2. проверяет в интерпретаторе — функция не работает как надо
3. исправляет баги
4. проверяет в интерпретаторе — опять не работает
5. ...?
6. profit! спустя месяц в функции обнаруживается непойманный баг

программист на Haskell:

1. пишет функцию
2. проверяет в интерпретаторе — функция не компилируется, выдает жуткого вида информацию об ошибке несоответствия типов
3. сидит, долго думает
4. исправляет функцию так, чтобы система типов заткнулась — функция работает как часы
5. profit!
иногда вообще бывает достаточно сразу правильно выписать тип функции, остальное вытекает из него
да ладно вам сказки рассказывать… компилируется значит работает? старо предание, да верится с трудом… вот я намедни можно сказать починял баг в хаскелевом пакете zip-archive.

так знаете как я его локализовывал? расставляя функции error в кишках сего пакета. я, конечно, привычный и видел более жесткие случаи, но не скажу, что это доставило мне большое удовольствие…
Можно было использовать Debug.Trace
хм, спасибо.

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

я сам стараюсь «ловить баги думаньем» (я еще называю это термином «psychic debugging», подсмотренным у Раймонда Чена), но иногда отладчик всё таки удобнее чем все остальные костыли…
у нас в конторе используется что-то вроде визуального ФЯ

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

вот это реально крутая отладка… как в тех роликах с youtube про четвёртое измерение, просто смотришь на всю программу сверху, даже не останавливая её
Это какой-то собственный софт?
нет, вполне коммерчески доступный — Quest3D, Virtools

это не ФЯ ни разу (там грязный stateful код)

а вот Quest3D, в частности — имеет lazy evaluation схему
да ладно вам сказки рассказывать… компилируется значит работает? старо предание, да верится с трудом…

Нас спасут dependent types, когда выйдут из подполья :)
не спасут, баги бывают в логике…

и вообще, неразрешимость проблемы остановки как бы намекает нам на то, что мы никогда не будем полностью спасены.
«Проблема всех языков программирования в том, что они делают то, что он написал, а не то, что имел в виду»
От ошибки алгоритма никуда не деться. Но если в Хаскеле ещё можно написать head [] и получить ошибку во время исполнения, то dependent types это пресекут.
По поводу проблемы остановки. Есть Total FP. Например Coq, он не Тьюринг-полный, однако на нём верифицировали (доказали корректность) урезанного компилятора из C в PowerPC. Возможности таких языков велики. Из этого следует, что гарантированно завершнимое подмножество языка может быть как-то выделено, на системе типов или ещё как-то.
Тогда если используешь потенциально незавершимый алгоритм, это будет отражено так же, как сейчас отражаются операции ввода-вывода в Хаскеле.
Ссылочку на дадите на верификацию компилятора?

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

Coq же это вообще не язык программирования, как я понимаю, а язык/среда для описания доказательств… А-ля старый добрый Larch…
Ну там и опухли. Если я не ошибаюсь, 40к строк кода.
compcert.inria.fr/doc/index.html

В общем-то да, язык для описания доказательств, но чем это не язык программирования? Разве что тулзу на нём не написать, но речь была не об этом, а о возможностях total fp.
О, ну я так и предполагал…

Разве что тулзу на нём не написать, но речь была не об этом, а о возможностях total fp.


Нет, речь идёт как раз о практической возможности применять языки, для которых проблема остановки разрешима.
Ибо какой толк от языка, для которого легко автоматически ловить ошибки, если обычный программист не может его применять?
Я привёл пример, чтобы показать, что многие достаточно сложные задачи решаемы и на Total FP. Т.е. имеет смысл выделить это в языке особенным образом (как выделены «грязные» функции).
Хотите — используете всю мощь, но теряете уверенность в том, что алгоритм завершится.
Тогда возникает второй вопрос: насколько сложные задачи (причем практические, а не теоретические) возможно решить на «завершающемся» подмножестве? Насколько понятно выглядит получающийся код?

Как прекрасно сказал Pierce
Do we want languages where a PhD (and two PhDs for Haskell) is required to understand the library documentation?
Is it better for Jane Programmer to write ~20 more or less correct lines of code / day or ~0 perfect ones?

а вот насчет проблемы останова я бы поспорил, это всё-таки тот ещё сферический конь в вакууме

можно писать на ограниченных языках, которые разрешимы

вот тут Нейл пишет про «Unfailing Haskell» — PDF
Давайте поспорим =)
Вопрос стоит простой: достаточно ли выразительны языки, для которых проблема останова разрешима? Вот скажем простое типизированное лямбда исчисление, для него проблема останова разрешима (= проблема существования нормальной формы)… Но! Y-комбинатор уже не выражается… Как же быть?

Спасибо, диссер интересный, сохранил на почитать… Но диссер про ошибки сопоставления с образцом, а не про завершимость (если я правильно понял заключение).

к примеру, можно как-то изолировать рекурсию (запретить её?)
я с трудом себе представляю осмысленные программы без рекурсии или циклов… вообщем нужны примеры =)
Достаточно потребовать, чтобы функция вызывала себя только с редуцированными аргументами. В таком случае fact завершается, так как вызывает себя с аргументом на 1 меньше, а при 0 (или 1) значение определено.
Можно даже так: один аргумент может оставаться тем же, если при этом уменьшается другой, но если второй даже увеличивается, то первый должен уменьшиться, тогда второй обязательно дойдёт до некоторого минимума, после чего уменьшится первый и в итоге всё завершится.
Всё зависит от способностей total checker'а.

Насчёт того, насколько мощным будет это подмножество — не знаю, но сходу очевидны такие примитивы как map/fold/filter и прочие списочные функции, а значит и все их комбинации.

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


и тут у меня возникает вопрос… а причем здесь вообще FP? =)
Разве вы не видите, что все те же рассуждения можно привести и для императивных языков программирования?

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

Честно говоря, не совсем вижу. Функции обязаны быть чистыми, static переменные нельзя, циклы с изменяемым счётчиком нельзя. Там от императивности ничего и не останется. Поправьте, если я не прав.
ну разумеется императивное с ограничениями…
и что от него останется «императивного»?

не забывайте, что императивные языки — несут в себе отпечаток машины Тьюринга, той самой, с лентой и прибамбасами

то бишь, память, mutable state, глобальный

никакой theorem prover не сможет проанализировать типичный императивный mess, где память рандомно пишется-читается из десятков потоков

это абсолютный анриал
а в чистых функциональных «ленту и прибамбасы» приходится приделывать обратно (монады там всяческие, unsafePerformIO), разве нет?

никакой theorem prover не сможет проанализировать типичный императивный mess, где память рандомно пишется-читается из десятков потоков


аналогичное, как мы уже поняли, верно и для FP. Никакой анализатор не справится с полноценным языком… Эта истина, которую я высказал в самом начале трэда…

Осталось лишь понять, насколько практически полезны и удобны «неполноценные» языки…
ага, а вот с монадами не всё так просто!

когда я пишу код с монадическим I/O — я спинным мозгом чувствую, что императивный код и моя чистая функциональная логика — надежно изолированы друг от друга

это как раз то, о чём я говорил — изоляция, divide and conquer

типичный императивный код — mess

код на Haskell — разделён так, что подавляющую часть программы можно верифицировать формальными методами
код на Haskell — разделён так, что подавляющую часть программы можно верифицировать формальными методами


у вас есть доказательство сего факта?
мне помогает капитан Очевидность, т.е. это просто gut feeling

на потребительском уровне это объясняется так: Haskell-программа служит мета-программой для I/O-кода, формирует его flow

вот он — подвержен всем тем flaws, каким подвержены сотнитыщ императивных языков

а мета-программа на Haskell — нет
Не, ну сама по себе МТ такая же детерминированная, как и функциональные языки. Это вот ее реализация в компьютере «грязная».
все говорят «я не могу» — «а ты возьми и купи слона!» ©

никто же не говорит, что их не должно быть

я думаю так

они допустимы только там, где можно доказать их завершимость

если доказать нельзя — рефакторим код так, чтобы доказывалось

у Нейла, похоже, так и сделано — его totality checker не пытается доказать недоказуемое, просто фейлится, если у него возникают затруднения
все говорят «я не могу» — «а ты возьми и купи слона!» ©


никто же не говорит, что их не должно быть

я думаю так

они допустимы только там, где можно доказать их завершимост
пардон глюкануло, чего-то

все говорят «я не могу» — «а ты возьми и купи слона!» ©


не-не-не! вы же говорите «могу». Вы говорите «Haskell — панацея». Вы говорите «profit и работает как часы». Я не просто так вцепился…

они допустимы только там, где можно доказать их завершимость

если доказать нельзя — рефакторим код так, чтобы доказывалось


В чем отличие от императивного кода? Для него тоже самое верно… Можно рефакторить, пока prover не прожуёт… Можно выделять подможества, для которых разрешима задача проверки корректности… Всё можно.

В чем же реальный, практический profit FP?

он проще
субъективное утверждение требует хотя бы статистического доказательства…

Вот кстати в соседнем посте человек привёл достаточно интересный факт о том, что GHCшники отказались от darcs, о котором я не знал… Если бы он был проще, так darcs давно бы взяли бы и допилили бы… А так они взяли и пересели на git, который суть C + bash (и никакой функциональщины). Получается продукт написанный на «небезопасном и отсталом» языке качественней чем проект написанный на «безопасном языке будущего»? В чем причина?
я не о том, проще ли он для понимания

в математическом смысле, проще

и, кроме того, я верю, что он проще для понимания человеку без какого-либо background в программировании, чем Java или C

неспроста раньше в курсе SICP языком был Scheme (почему сейчас не так — это отдельная история)
в математическом смысле, проще


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

Например, у нас в ИСИ СО РАН есть лаборатория, в которой весьма успешно занимаются верификацией подмножества C#.

неспроста раньше в курсе SICP языком был Scheme (почему сейчас не так — это отдельная история)


Однако, Scheme не чист, не ленив и не статически типизирован =)
это я прекрасно знаю

тем не менее, там успешно применяются списки, рекурсия и map/reduce, вполне себе FP flavor
ну это много где нынче успешно применяется… собственно мой point в том и состоит, что вот такие гибриды (функциональщина + императивщина) таки являются оптимумом с практической точки зрения…
вы же не будете спорить с тем, что человеку, начинавшему со Схемы намного проще понять Haskell, чем человеку, который начинал с PHP?
нет, не буду

причина в том, что Git тупо лучше

не потому, что он написан на какой-то не такой хуите, на какой написан darcs

как продукт — лучше, как питательная среда для разработчиков самого Git — лучше

язык это всего лишь инструмент, важно лишь то, в чьих он руках
НЛО прилетело и опубликовало эту надпись здесь
к примеру, thesz.livejournal.com/

человек занимается моделированием аппаратуры на Haskell
НЛО прилетело и опубликовало эту надпись здесь
вы спрашивали про прямые руки, а не про большие проекты — я привёл пример

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

«реальный» функциональный язык — это O'Caml

caml.inria.fr/about/successes.en.html

в частности, на O'Caml был написан софт, который верифицировал управляющую логику самолётов Airbus A-340 (44k строк кода)
НЛО прилетело и опубликовало эту надпись здесь
>> Очень сомневаюсь, что Хаскель — хороший академический язык

он лучший — я гарантирую это ©

это практически эссенция FP (чистота, строгая типизация, ленивость), без компромиссов

к примеру, если бы Haskell был не ленивым — это был бы компромисс (в угоду предсказуемости производительности), и это изменило бы семантику Haskell-программ, пришлось бы писать код совсем по-другому, более многословно

если бы он был не чистым — это был бы компромисс

был бы слабо/динамически типизирован — опять компромисс (уже для разработчиков языка, т.к. лень/сложно реализовывать type inference / unification)

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

Важна декларативность чистоты и нечистоты. И когда это выражается на системе типов — выглядит естественно. Хотя можно и ключевое слово ввести.

всяких классов типов

Ну я бы не говорил «всякие» :)
Помощнее интерфейсов-то будут.
НЛО прилетело и опубликовало эту надпись здесь
тогда в чём академическая ценность?

пишите на C#, там всё есть
НЛО прилетело и опубликовало эту надпись здесь
тут выигрывает C#

только неясно, при чём тут FP

FP == Haskell
>> Почти все функциональные языки ленивы.

по-моему, тут вы что-то путаете, т.к. из ФЯ ленив по умолчанию только Haskell

>> Вывод типов в Haskell, имхо, менее удобен, чем утиная типизация.

у меня большой опыт разработки на динамических языках (JavaScript, Python) — и могу сказать вот что:

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

со временем понимаешь, что это никакое не преимущество, а огромное зло — заранее неизвестно, упадёт программа или нет

и единственный способ узнать — сделать юнит-тесты, но даже с ними неизвестен code coverage, а он должен быть 100%… приходится использовать сложнейший софт для анализа code coverage, и хорошо ещё, если он доступен для выбранного языка (а для JavaScript такого точно нет)

«мыши плакали, кололись, но продолжали жрать кактус»

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

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

оттуда и растут такие «имхо», что duck typing это хорошо
НЛО прилетело и опубликовало эту надпись здесь
caml статически строго типизирован, как и Haskell
НЛО прилетело и опубликовало эту надпись здесь
Можно пояснить, что вы понимаете под статической утиной типизацией в OCaml? На ум приходят полиморфные варианты и функторы. Но у первых область применения ограничена, а функторы — более крупноблочное и синтаксически тяжелое средство. Кому как, а я от выразительных возможностей классов типов в OCaml не отказался бы.
по-моему, тут вы что-то путаете, т.к. из ФЯ ленив по умолчанию только Haskell


еще Миранда =)
Ну, и наконец, Clean :) (странно, что о нем все забыли)
> А так они взяли и пересели на git, который суть C + bash (и никакой функциональщины). Получается продукт написанный на «небезопасном и отсталом» языке качественней чем проект написанный на «безопасном языке будущего»? В чем причина?

У GHC большой codebase, и darcs просто не справлялся с нагрузкой (он в сторону увеличения количества кода плохо масштабировался тогда, вроде с тех пор ускорился, точно не знаю). В этом вся причина.
Ответ не подходит к вопросу.
Я не спрашивал, почему пересели.
Я спрашивал, почему менее качественный.
Вы часто сравниваете яблоки с апельсинами?

Git и Darcs для разных вещей придуманы: один для очень больших репозитариев (и быстрых операций), а второй для небольших проектов на Хаскеле.

Darcs отличная VCS, как и Git.
НЛО прилетело и опубликовало эту надпись здесь
> А что, Git на небольших проектах хуже Darcs?

Кто тебе такое сказал? Неужели я? %)

> Тогда уж не яблоки с апельсинами, а отвёртки с универсальными отвёртками.

LOLWUT? О чем спор?
НЛО прилетело и опубликовало эту надпись здесь
«Haskell не спас»… я плакалъ

darcs написан человеком, а не Haskell'ем
НЛО прилетело и опубликовало эту надпись здесь
darcs провалился потому что разработчику просто надоело его писать в отсутствие мотивации

Git намного старше darcs, а из забвения вылез благодаря github и популярности среди ruby-разработчиков

это совсем разные истории
Не надо доходить до фанатизма — первый релиз git — 2005, а darcs — 2003.
да, тут я ошибся

однако, без github'a и рубистов он так и остался бы уделом Торвальдса и пары линукс-гиков
Если вас интересует, есть ли вероятность, что darcs провалился из-за ЯП, то да, есть.
Если вы хотите доказать какую-то точку зрения, я бы хотел увидеть как формулировку точки зрения, так и оценку вероятности.
Иначе это выглядит как переливание из пустого в порожнее. Верным может оказаться любое мнение.
НЛО прилетело и опубликовало эту надпись здесь
Мне хватает того факта, что вероятность есть, и она выше, например, вероятности угадать 5-ти символьный пароль с первого раза

Именно, что данный факт ни о чём не говорит, поэтому и выводов никаких сделать нельзя.
Дело не в языке. Darcs основан на теории патчей, когда любой патч не зависит от предыдущего — т.е. мы можем работать с каждым патчем отдельно (у git придётся звать rebase в этом случае), cherry picking опять же поэтому удобнее — но это субъективное. Минусы — скорость, но, повторю, это следствие не языка, а теории патчей, на которой darcs построен. В целом я предпочитаю git (знаю людей, которые предпочитают darcs). Но язык здесь совершенно не при чём, честно.
а про завершимость там есть немного

4.4 Termination Checking

The initial research has pushed in the direction of pattern match errors, and termination has been deliberately ignored. I suspect that the same framework can be used in the termination checking. Implementing a simple termination checker that accepts only primitive recursion, and ignores laziness entirely, should be an easily achievable first step.

т.е. у автора есть «gut feeling», что похожим образом можно анализировать завершимость в Haskell

ведь анализ на полноту паттернов для Haskell — это тоже неразрешимая проблема, как и termination analysis

автор сам вот что пишет:

As totality is undecidable, so my tool must err on the side of caution – only claiming totality when a complete proof can be produced.
это зависит от того, как софт писать

вообще говоря, в GHC, который использовал я был один фатальный недостаток — он не умел в compile-time обнаруживать неполные паттерны

это единственное, из-за чего программа на чистом Haskell могла упасть в run-time :((((

Нейл Митчел написал case totality checker для Haskell, уж не знаю, интегрировали его в GHC или нет — www-users.cs.york.ac.uk/~ndm/catch/

думайте что хотите, но это case totality check + система типов Haskell это реальная мощь, с помощью которой 99% багов (которые я каждый день вижу в своём коде и коде коллег на динамических языках) — просто нельзя допустить

баги, вызванные корявым unsafePerformIO и уж тем более баги в алгоритмике программы — тут, извините, язык ни при чём
вообще говоря, в GHC, который использовал я был один фатальный недостаток — он не умел в compile-time обнаруживать неполные паттерны

-fwarn-incomplete-patterns
Или это не оно?
а я вот не знаю

думаю, не оно — это скорее всего наивный чекер, который не проверяет всю программу
Тогда не оно. Анализ всей программы это круто.
ну, типа, Total FP
и уж тем более баги в алгоритмике программы — тут, извините, язык ни при чём


вот, вот. т.е. от всех багов не застрахует и Haskell… иными словами фразу

функция работает как часы


в вашем предыдущем комментарии следует читать так

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


Сделали fromJust где не надо, и опа-хопа аналог NPE в лоб…
Кстати личное наблюдение — хаскель засчет вывода типов очень располагает к test-driven девелопменту, почти как питон и прочие динамические друзья (конечно, в хаскеле тестов надо заметно меньше).

Это в отличие от C#/Java где в тестах получается дофига boilerplate кода, и в результате писать их там не очень приятно.
кстати, тесты — при наличии мощного средства для формального анализа — становятся контрактами и позволяют выявлять ошибки даже без прогона тестов (еще при компиляции)

но тут success stories мне, увы, не известны
НЛО прилетело и опубликовало эту надпись здесь
Ну «всё» — это, конечно, преувеличение, но ощущение волшебства, когда запускаешь программу, прошедшую контроль типов и она сразу работает, у меня было только в Haskell. Об этом ощущении я слышал от многих людей. Неудивительно, изоморфизм Карри-Ховарда (Говарда?) помогает. Часто я даже пишу «от типов». Ещё можно поглядеть на theorem for free у Вадлера или презентацию доклада Дениса Москвина на SpbHUG.
спасибо, хорошее введение
ghci> let max2 x = max 2 x
ghci> :t max2
max2 :: (Ord t, Num t) -> t -> t


Можете пояснить, что значит такая запись типа?
Данная запись означает:
«Для любого типа t такого, что он принадлежит к классу Ord и Num, функция max2 принимает один аргумент типа t и возвращает результат типа t»
Про классы я ещё расскажу. Вкратце, суть в том, что если тип принадлежит к некоему классу, то над ним определены некоторые функции, и если max2 можно выразить только через эти функции, то сам тип уже не важен. Это может быть и Int, и Double, и даже Ratio (целочисленная дробь)
Спасибо. Жду продолжения.
На всякий случай уточню, чтобы небыло недопонимания. Класс здесь не связан с таковым в ООП. Это скорее в математическом смысле. Т.е. все числа принадлежат к классу Num, а все упорядочиваемые — к классу Ord (сокр. от ordering)
а по-мойму удобно мыслить type classes, как интерфейсы
Интересующимся Haskell для изучения очень рекомендую «Programming in Haskell» моего лектора, вкладчика в развитие языка, Graham Hutton при University of Nottingham — www.cs.nott.ac.uk/~gmh/book.html.
Как по мне, то плотность изложения оптимальна. С нетерпением жду части, когда дойдет дела до имплементации «мяса» движка чата.
Спасибо, очень хорошая и понятная статья. Обязательно почитаю.
Сразу видно — человек трудился.
Низкий поклон тебе.
Все-таки самое интересное в ФП, на мой взгляд — это стыковка идеального мира функций с переменными, юзерским вводом и прочими изменяемыми значениями. Расскажите пожалуйста, как это реализовано.
Я пока Haskell только учу, и семантика монад мне не до конца понятна=) Однако я более-менее читал SICP, так вот в Scheme это реализуется через «потоки», в общем аналог ленивых списков Haskell. Напомню, что в таком списке head — текущее значение, а tail — обещание вычислить хвост (который есть ленивый список). Естественно можно создать поток чего-то не чистого, например пользовательского ввода. Далее мы работаем с этим потоком как и со всеми другими. Получаем функциональную чистоту и поведение, сходное с поведением императивного языка. Я думаю монады имеют схожую семантику.

И самое главное, трам-пам-пам, барабанная дробь: все эти имитаторы императивщины ведут себя так же как и императивные языки, что порой сводит на «нет» все прелести ФП. Справедливости ради стоит отметить, что по крайней мере опасные не чистые участки кода локализованы. Что не отменяет того факта, что характер панацеии ФП такой же рекламный ход как и ООП. Нет в жизни счастья, есть прямые руки и кропотливая работа.
Спасибо, действительно полезный сайт.

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

P.S: сам по работе в основном пишу на PHP
Этот гайд чем-то напомнил мне Why's Poignant Guide to Ruby, который тоже очень, очень крут.

Вообще, все учебники, которые интересно читать, автоматически круты.

Насколько я знаю, создатель learnhaskellforgood вдохновился этим руководством по ruby.
А Вы уверены, что foldr работает именно так, как Вы описали?
Сорри, недописал
Prelude> foldr (/) 1 [2,3]
0.6666666666666666
Prelude> (/) 3 ((/) 2 1)
1.5
huh?
Да, я там что-то напутал, сейчас исправлю, спасибо.
Теперь лучше, только поменяйте описания для foldr и foldl местами
А, не, не надо, это уже моя невыспанность
Ничего непонятно, синтаксис запутан до ужаса.
тоесть, получается, что выражение: fun arg
на самом деле означает не применение функции fun к arg,
а карринг функции fun по первому аргументу.
и если повезёт, и у fun больше нет аргументов, то результат будет скаляром,
а если не повезёт — то будет функцией с n-1 аргументами.

обозначение:
ghci> :t max
max :: (Ord a) => a -> a -> a
при таком раскладе выглядит логичнее, чем «принимает два аргумента»

и как?
Я, честно говоря, не понял Вашего вопроса
я вобщем-то тоже :)
поэтому изучать хаскель пойду таки по учебникам :)
означает, ещё как. то есть Вы мыслите правильно, но неправильно понимаете что такое аппликация в смысле лямбда-исчисления :) варианта «если не повезёт» быть не может — тайпчекер не пропустит некорректное выражение

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

>Она принимает функцию из t1 в t2, из t в t1, а возвращает функцию из t в t2.
А вот это хоть и согласовано, но свежему человеку непонятно напрочь (угадал все буквы, не смог назвать слово) не могли бы вы подробнее написать, откуда куда принимается функция?
«Она принимает две функции: из t1 в t2, и из t в t1, а возвращает функцию из t в t2».
у кого-нибудь есть аккаунт на хабре? вот тут:

habrahabr.ru/blogs/Haskell/50310/#habracut

неплохая вводная статья, но с ошибкой:

>Бесконечный список можно определить и через рекурсию:

>ghci> let ones = 1: ones

>ones — это список, в голове которого находится 1, а в хвосте — сам ones, т.е. это бесконечный список единиц.

это не рекурсия, а корекурсия:

en.wikipedia.org/wiki/Corecursion

существенный момент с точки зрения ленивости языка (ну и определения рекурсии тоже), а никто не поправил :( оставьте там кто-нибудь комментарий, пожалуйста. заранее спасибо
jtootf * (*) (01.02.2009 16:10:49)

//привет с лора
Спасибо, поправил
вообще интересная идея серии статей с real-world примером, с удовольствием почитаю до конца
а, ну ещё, просто как комментарий — раз уж пример корекурсии всё равно дан, да и fold фигурирует,- почему бы не упомянуть и unfold заодно?

ну или убрать бесконечные списки вообще :) а то как-то получается, что бесконечные структуры данных упоминаются в отрыве и от unfold'ов, и от специфики ленивого программирования
ghci> :t max
max :: (Ord a) => a -> a -> a

Таким образом, max определён для любого a (принадлежащего к классу Ord), принимает два аргумента одного типа и возвращает результат того же типа.

Можете пояснить что означает запись "(Ord a) => a -> a -> a"? Не очень обозначения "=>" и "->". "=>" означает, на сколько я понял, «max определён для любого a (принадлежащего к классу Ord)», но "->", судя по всему имеет не всегда один и тот же смысл. В случае «a -> a» это означает берёт аргумент типа и возвращает результат того же типа, но вот с «a -> a -> a» уже непонятно. Если бы было написано, например, «a a -> a» или «a, a -> a» или что-то ворде этого, то вопроса не возникло бы :)

Но тип функции можно представить и так:

max :: (Ord a) => a -> (a -> a)

И тут мы встречаем currying. Получается, что max принимает один аргумент и возвращает функцию типа (a -> a).
Любую функцию можно представить, как принимающую один аргумент и возвращающую другую функцию.

Что означает запись «max :: (Ord a) => a -> (a -> a)»? Это вывод интерпретатора или это вы вбили в интерпретатор для выполнения или…?
Перед => указываются constraints, в данном примере принадлежность типа a к классу Ord, а после — сам тип.
a -> b — тип функции, принимающий a, и возвращающий b. Стоит просто помнить, что -> — левоассоциативен, и запись a -> b -> c означает ровно то же, что и a -> (b -> c). Т.е. функция принимает a, а возвращает другую функцию с типом b -> c.
Это действительно так, вы можете написать max 10, и это будет функция из числа в число.
mymax :: (Num a) => a -> a
-- здесь уже нужен Num, потому что в определении мы используем литерал 10, а он имеет тип (Num a) => a
mymax = max 10

test = mymax 0 -- 10
test2 = mymax 20 -- 20


Что означает запись «max :: (Ord a) => a -> (a -> a)»?

Это декларация функции, которую можно вбить в исходном коде:
max :: (Ord a) => a -> (a -> a)
-- полностью эквивалентно max :: Ord a => a -> a -> a
1. Я правильно понимаю, что " a -> a -> a" означает:
Берём x типа a, выполняем f(x) и получаем функцию y -> z, т.е. f(y)=z,
затем, собственно, берём f(y) и получаем z.

2. В таком случае «a -> a -> a-> a» будет тоже самое, что и «a -> (a -> (a-> a))»?
Т.е. из функции f(x1) получаем f(x2), из неё f(x3), а из неё результат, верно?
Да, только f разные.
Всё понял, спасибо большое!
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Изменить настройки темы

Истории