Comments 162
Спасибо за статью. Хотя быть может стоило пожалеть мозги неподготовленных читателей…
Вы только что сломали мой мозг. Хотя я уже был немного подготовлен. Буду изучать.
Восхитительно, особенно преобразования!
отлично написано: просто и по сути
всё очень доступно изложено.
спасибо!
спасибо!
проблема тут вот в чём
типичному программисту на пых-пыхе непонятно, к чему всё это
списки, lazy eval, ссылочная прозрачность, ФВП, GADT, карринг, typeclasses, Y-комбинатор, изъебства с I/O, континуации?
вот я беру, что там у нас… переменную, цикл, класс, метод, ассоциативный массив, print/echo и… творю… мне ни к чему эти ваши флюгергехаймеры
я хочу сказать, что большинство втыкателей живет в другом мире, оперирует в голове другими категориями, другая система понятий
какие из этого выводы?
вероятно, конкретно Haskell тут вообще ни при чём — надо заходить издалека, с теории, подкреплённой хорошими, рабочими аналогиями…
типа, calculus 101
типичному программисту на пых-пыхе непонятно, к чему всё это
списки, lazy eval, ссылочная прозрачность, ФВП, GADT, карринг, typeclasses, Y-комбинатор, изъебства с I/O, континуации?
вот я беру, что там у нас… переменную, цикл, класс, метод, ассоциативный массив, print/echo и… творю… мне ни к чему эти ваши флюгергехаймеры
я хочу сказать, что большинство втыкателей живет в другом мире, оперирует в голове другими категориями, другая система понятий
какие из этого выводы?
вероятно, конкретно Haskell тут вообще ни при чём — надо заходить издалека, с теории, подкреплённой хорошими, рабочими аналогиями…
типа, calculus 101
я уже не говорю про синтаксис ML-подобных, мотивы и истоки которого совсем непонятны людям, которые по работе сталкиваются в основном с foo(bar) { }
Вот это я и хочу показать на примере реализации чата. Можно будет оценить, что стало проще, что сложнее.
В целом всё это даёт более короткую запись, а строгая типизация позволяет практически избежать ошибок.
Это уже большой плюс. Конечно, те же бесконечные списки можно запрограммировать итераторами, но Haskell потому и стоит учить, что он позволяет по-другому решать задачу, хотя потом её можно будет записать хоть на Си++.
В целом всё это даёт более короткую запись, а строгая типизация позволяет практически избежать ошибок.
Это уже большой плюс. Конечно, те же бесконечные списки можно запрограммировать итераторами, но Haskell потому и стоит учить, что он позволяет по-другому решать задачу, хотя потом её можно будет записать хоть на Си++.
А зачем всё это типичному программисту на пыхпыхе? Как говорится, «нам тут лишние люди не нужны!».
Ну знаете, иногда приятно отойти от догм. Расширяет кругозор, я вот начал учить японский потому, что он в корне другой. Расширение кругозора не поможет сделать очередной чат, но поможет сделать что-то совершенно новое. Мне это интересно.
хороший программист должен уметь писать алгоритмы, а не только код.
У нас в МИЭМ, кстати, сформировался небольшой кружок Хаскеля
ммм, а почему про ленивость ничего не написано?
и еще кое-что… при программировании на Haskell (и многих других функциональных языках, например, Lisp) очень удобно пользоваться редакторами поддерживающими REPL (Read-eval-print loop). Например, emacs'ом (c haskell-mode) или патченым (sic!) vim'ом…
Посоветуйте, что можно отдельно написать про ленивость?
То, что бесконечный список не вычисляется до его вывода на экран, я показал.
Или вы о нюансах?
То, что бесконечный список не вычисляется до его вывода на экран, я показал.
Или вы о нюансах?
Ну, на мой взгляд важно акцентировать внимание на том, что функция в реальности не вычисляется до того момента, когда понадобится…
Как это написать, я с наскоку не скажу…
Просто вы употребили такие слова как «чистота», «ленивость», «строгость», «полиморфный», «класс» но расшифровки их в тексте нет. Это может стать причиной непонимания.
Как это написать, я с наскоку не скажу…
Просто вы употребили такие слова как «чистота», «ленивость», «строгость», «полиморфный», «класс» но расшифровки их в тексте нет. Это может стать причиной непонимания.
UFO just landed and posted this here
когда я писал на Haskell приложение для работы с графами, то для меня стало большим открытием, что можно разбить сложный алгоритм трансформации графа на много маленьких простых
и не париться в каждом из них о том, есть в графе циклы или нет, завершается каждый отдельно взятый кусок трансформации или нет
ты просто пишешь то, что думаешь, а constraint (в моём случае — проверка на циклы) накладывается сверху, уже потом
это так же просто, как take 5 [1..]
но в большом масштабе это действительно сложно осознать
и не париться в каждом из них о том, есть в графе циклы или нет, завершается каждый отдельно взятый кусок трансформации или нет
ты просто пишешь то, что думаешь, а constraint (в моём случае — проверка на циклы) накладывается сверху, уже потом
это так же просто, как take 5 [1..]
но в большом масштабе это действительно сложно осознать
Может, лучше привести примеры тех задач, где функциональный язык действительно удобнее?
Потому как написание чата на Хаскеле, оно, конечно, весьма интересно, но вряд ли имеет практическую ценность сильно бОльшую, чем написание того же чата на Brainfuck.
Потому как написание чата на Хаскеле, оно, конечно, весьма интересно, но вряд ли имеет практическую ценность сильно бОльшую, чем написание того же чата на 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, и бесконечные списки. В принципе это же самое можно показать на примере любой другой программы.
В общем-то мне самому интересно в этом разбираться, так как на практике я Haskell не использовал (а хочу), так что если есть интересные задачи, я готов попробовать.
Но выделить задачу, в которой ФЯ был бы удобнее, — сложно. Он везде удобнее, правда, за это приходится платить.
То, что это чат, — не принципиально. Там есть и многопоточность (Chan, MVar, forkIO), и GUI, и бесконечные списки. В принципе это же самое можно показать на примере любой другой программы.
«Везде удобнее» — это ни о чем. Это лозунг в стиле «за все хорошее против всего плохого», никакого отношения к действительности не имеющий.
Функциональный подход используют многие программисты и весьма регулярно. Выше приведен типичный пример создания иерархической структуры, применявшийся еще черт знает когда (я первый раз с ним столкнулся, осваивая Turbo Vision в мохнатом году). Я сам его регулярно применяю (в тех случаях, где он мне кажется уместным) — на Delphi и PHP.
И потому скажу свое мнение человека, 15 лет занимающегося структурным и объектно-ориентированным программированием — интересно посмотреть прежде всего на решение практических задач. Потому как еще нет инструмента, который был бы лучше «везде» и «всегда». А вот где и как именно — очень интересно.
Функциональный подход используют многие программисты и весьма регулярно. Выше приведен типичный пример создания иерархической структуры, применявшийся еще черт знает когда (я первый раз с ним столкнулся, осваивая Turbo Vision в мохнатом году). Я сам его регулярно применяю (в тех случаях, где он мне кажется уместным) — на Delphi и PHP.
И потому скажу свое мнение человека, 15 лет занимающегося структурным и объектно-ориентированным программированием — интересно посмотреть прежде всего на решение практических задач. Потому как еще нет инструмента, который был бы лучше «везде» и «всегда». А вот где и как именно — очень интересно.
после функционального языка приходишь в старую добрую Java
и это выглядит как-то так
Neo You: I know Kung Fu!
Java: Show me
и тут начинается депрессия…
и это выглядит как-то так
Java: Show me
и тут начинается депрессия…
Дебаггеры под это дело есть? В 20 строчках этого языка можно запутаться на неделю…
там не нужны дебаггеры
Тут фактически сам язык — дебаггер.
Вообще говоря вроде есть, но я никогда не пользовался.
REPL'а вполне достаточно. Можно любую функцию определить и тут же её прогнать.
REPL'а вполне достаточно. Можно любую функцию определить и тут же её прогнать.
два разных подхода к программированию: Python/Ruby/JavaScript и Haskell
программист на Python:
1. пишет функцию
2. проверяет в интерпретаторе — функция не работает как надо
3. исправляет баги
4. проверяет в интерпретаторе — опять не работает
5. ...?
6.profit! спустя месяц в функции обнаруживается непойманный баг
программист на Haskell:
1. пишет функцию
2. проверяет в интерпретаторе — функция не компилируется, выдает жуткого вида информацию об ошибке несоответствия типов
3. сидит, долго думает
4. исправляет функцию так, чтобы система типов заткнулась — функция работает как часы
5. profit!
программист на Python:
1. пишет функцию
2. проверяет в интерпретаторе — функция не работает как надо
3. исправляет баги
4. проверяет в интерпретаторе — опять не работает
5. ...?
6.
программист на Haskell:
1. пишет функцию
2. проверяет в интерпретаторе — функция не компилируется, выдает жуткого вида информацию об ошибке несоответствия типов
3. сидит, долго думает
4. исправляет функцию так, чтобы система типов заткнулась — функция работает как часы
5. profit!
иногда вообще бывает достаточно сразу правильно выписать тип функции, остальное вытекает из него
да ладно вам сказки рассказывать… компилируется значит работает? старо предание, да верится с трудом… вот я намедни можно сказать починял баг в хаскелевом пакете zip-archive.
так знаете как я его локализовывал? расставляя функции error в кишках сего пакета. я, конечно, привычный и видел более жесткие случаи, но не скажу, что это доставило мне большое удовольствие…
так знаете как я его локализовывал? расставляя функции error в кишках сего пакета. я, конечно, привычный и видел более жесткие случаи, но не скажу, что это доставило мне большое удовольствие…
Можно было использовать Debug.Trace
хм, спасибо.
можно было, но суть от этого не изменилась бы: отладочная печать, она и в африке отладочная печать… и я бы не назвал это «вершиной отладочных технологий»
я сам стараюсь «ловить баги думаньем» (я еще называю это термином «psychic debugging», подсмотренным у Раймонда Чена), но иногда отладчик всё таки удобнее чем все остальные костыли…
можно было, но суть от этого не изменилась бы: отладочная печать, она и в африке отладочная печать… и я бы не назвал это «вершиной отладочных технологий»
я сам стараюсь «ловить баги думаньем» (я еще называю это термином «psychic debugging», подсмотренным у Раймонда Чена), но иногда отладчик всё таки удобнее чем все остальные костыли…
у нас в конторе используется что-то вроде визуального ФЯ
там функции и их композиции — рисуются как граф, который в run-time можно смотреть (какие ветки выполняются, какие значения пробегают по графу при вычислении и т.п.)
вот это реально крутая отладка… как в тех роликах с youtube про четвёртое измерение, просто смотришь на всю программу сверху, даже не останавливая её
там функции и их композиции — рисуются как граф, который в run-time можно смотреть (какие ветки выполняются, какие значения пробегают по графу при вычислении и т.п.)
вот это реально крутая отладка… как в тех роликах с youtube про четвёртое измерение, просто смотришь на всю программу сверху, даже не останавливая её
да ладно вам сказки рассказывать… компилируется значит работает? старо предание, да верится с трудом…
Нас спасут dependent types, когда выйдут из подполья :)
не спасут, баги бывают в логике…
и вообще, неразрешимость проблемы остановки как бы намекает нам на то, что мы никогда не будем полностью спасены.
и вообще, неразрешимость проблемы остановки как бы намекает нам на то, что мы никогда не будем полностью спасены.
«Проблема всех языков программирования в том, что они делают то, что он написал, а не то, что имел в виду»
От ошибки алгоритма никуда не деться. Но если в Хаскеле ещё можно написать
По поводу проблемы остановки. Есть Total FP. Например Coq, он не Тьюринг-полный, однако на нём верифицировали (доказали корректность) урезанного компилятора из C в PowerPC. Возможности таких языков велики. Из этого следует, что гарантированно завершнимое подмножество языка может быть как-то выделено, на системе типов или ещё как-то.
Тогда если используешь потенциально незавершимый алгоритм, это будет отражено так же, как сейчас отражаются операции ввода-вывода в Хаскеле.
От ошибки алгоритма никуда не деться. Но если в Хаскеле ещё можно написать
head []
и получить ошибку во время исполнения, то dependent types это пресекут.По поводу проблемы остановки. Есть Total FP. Например Coq, он не Тьюринг-полный, однако на нём верифицировали (доказали корректность) урезанного компилятора из C в PowerPC. Возможности таких языков велики. Из этого следует, что гарантированно завершнимое подмножество языка может быть как-то выделено, на системе типов или ещё как-то.
Тогда если используешь потенциально незавершимый алгоритм, это будет отражено так же, как сейчас отражаются операции ввода-вывода в Хаскеле.
Ссылочку на дадите на верификацию компилятора?
У меня есть сильное подозрение (вытекающее из моего опыта в compiler construction & formal methods), что верифицировать любой нетривиальный компилятор (особенно если он еще и связан с нетривиальной средой исполнения) можно опухнуть.
Coq же это вообще не язык программирования, как я понимаю, а язык/среда для описания доказательств… А-ля старый добрый Larch…
У меня есть сильное подозрение (вытекающее из моего опыта в compiler construction & formal methods), что верифицировать любой нетривиальный компилятор (особенно если он еще и связан с нетривиальной средой исполнения) можно опухнуть.
Coq же это вообще не язык программирования, как я понимаю, а язык/среда для описания доказательств… А-ля старый добрый Larch…
Ну там и опухли. Если я не ошибаюсь, 40к строк кода.
compcert.inria.fr/doc/index.html
В общем-то да, язык для описания доказательств, но чем это не язык программирования? Разве что тулзу на нём не написать, но речь была не об этом, а о возможностях total fp.
compcert.inria.fr/doc/index.html
В общем-то да, язык для описания доказательств, но чем это не язык программирования? Разве что тулзу на нём не написать, но речь была не об этом, а о возможностях total fp.
О, ну я так и предполагал…
Разве что тулзу на нём не написать, но речь была не об этом, а о возможностях total fp.
Нет, речь идёт как раз о практической возможности применять языки, для которых проблема остановки разрешима.
Ибо какой толк от языка, для которого легко автоматически ловить ошибки, если обычный программист не может его применять?
Я привёл пример, чтобы показать, что многие достаточно сложные задачи решаемы и на Total FP. Т.е. имеет смысл выделить это в языке особенным образом (как выделены «грязные» функции).
Хотите — используете всю мощь, но теряете уверенность в том, что алгоритм завершится.
Хотите — используете всю мощь, но теряете уверенность в том, что алгоритм завершится.
Тогда возникает второй вопрос: насколько сложные задачи (причем практические, а не теоретические) возможно решить на «завершающемся» подмножестве? Насколько понятно выглядит получающийся код?
Как прекрасно сказал Pierce
Как прекрасно сказал 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
можно писать на ограниченных языках, которые разрешимы
вот тут Нейл пишет про «Unfailing Haskell» — PDF
Давайте поспорим =)
Вопрос стоит простой: достаточно ли выразительны языки, для которых проблема останова разрешима? Вот скажем простое типизированное лямбда исчисление, для него проблема останова разрешима (= проблема существования нормальной формы)… Но! Y-комбинатор уже не выражается… Как же быть?
Спасибо, диссер интересный, сохранил на почитать… Но диссер про ошибки сопоставления с образцом, а не про завершимость (если я правильно понял заключение).
Вопрос стоит простой: достаточно ли выразительны языки, для которых проблема останова разрешима? Вот скажем простое типизированное лямбда исчисление, для него проблема останова разрешима (= проблема существования нормальной формы)… Но! Y-комбинатор уже не выражается… Как же быть?
Спасибо, диссер интересный, сохранил на почитать… Но диссер про ошибки сопоставления с образцом, а не про завершимость (если я правильно понял заключение).
к примеру, можно как-то изолировать рекурсию (запретить её?)
я с трудом себе представляю осмысленные программы без рекурсии или циклов… вообщем нужны примеры =)
Достаточно потребовать, чтобы функция вызывала себя только с редуцированными аргументами. В таком случае fact завершается, так как вызывает себя с аргументом на 1 меньше, а при 0 (или 1) значение определено.
Можно даже так: один аргумент может оставаться тем же, если при этом уменьшается другой, но если второй даже увеличивается, то первый должен уменьшиться, тогда второй обязательно дойдёт до некоторого минимума, после чего уменьшится первый и в итоге всё завершится.
Всё зависит от способностей total checker'а.
Насчёт того, насколько мощным будет это подмножество — не знаю, но сходу очевидны такие примитивы как map/fold/filter и прочие списочные функции, а значит и все их комбинации.
Понятно, что вся программа не будет таковой, но если так удастся написать хотя бы часть функций, это уже хорошо.
Можно даже так: один аргумент может оставаться тем же, если при этом уменьшается другой, но если второй даже увеличивается, то первый должен уменьшиться, тогда второй обязательно дойдёт до некоторого минимума, после чего уменьшится первый и в итоге всё завершится.
Всё зависит от способностей total checker'а.
Насчёт того, насколько мощным будет это подмножество — не знаю, но сходу очевидны такие примитивы как map/fold/filter и прочие списочные функции, а значит и все их комбинации.
Понятно, что вся программа не будет таковой, но если так удастся написать хотя бы часть функций, это уже хорошо.
Всё зависит от способностей total checker'а.
и тут у меня возникает вопрос… а причем здесь вообще FP? =)
Разве вы не видите, что все те же рассуждения можно привести и для императивных языков программирования?
Вообщем, я не вижу убедительных доказательств в пользу того факта, что Haskell как бы снижает стоимость разработки/поддержки и при этом не привносит дополнительных издержек.
Честно говоря, не совсем вижу. Функции обязаны быть чистыми, static переменные нельзя, циклы с изменяемым счётчиком нельзя. Там от императивности ничего и не останется. Поправьте, если я не прав.
ну разумеется императивное с ограничениями…
и что от него останется «императивного»?
не забывайте, что императивные языки — несут в себе отпечаток машины Тьюринга, той самой, с лентой и прибамбасами
то бишь, память, mutable state, глобальный
никакой theorem prover не сможет проанализировать типичный императивный mess, где память рандомно пишется-читается из десятков потоков
это абсолютный анриал
не забывайте, что императивные языки — несут в себе отпечаток машины Тьюринга, той самой, с лентой и прибамбасами
то бишь, память, mutable state, глобальный
никакой theorem prover не сможет проанализировать типичный императивный mess, где память рандомно пишется-читается из десятков потоков
это абсолютный анриал
а в чистых функциональных «ленту и прибамбасы» приходится приделывать обратно (монады там всяческие, unsafePerformIO), разве нет?
аналогичное, как мы уже поняли, верно и для FP. Никакой анализатор не справится с полноценным языком… Эта истина, которую я высказал в самом начале трэда…
Осталось лишь понять, насколько практически полезны и удобны «неполноценные» языки…
никакой theorem prover не сможет проанализировать типичный императивный mess, где память рандомно пишется-читается из десятков потоков
аналогичное, как мы уже поняли, верно и для FP. Никакой анализатор не справится с полноценным языком… Эта истина, которую я высказал в самом начале трэда…
Осталось лишь понять, насколько практически полезны и удобны «неполноценные» языки…
ага, а вот с монадами не всё так просто!
когда я пишу код с монадическим I/O — я спинным мозгом чувствую, что императивный код и моя чистая функциональная логика — надежно изолированы друг от друга
это как раз то, о чём я говорил — изоляция, divide and conquer
типичный императивный код — mess
код на Haskell — разделён так, что подавляющую часть программы можно верифицировать формальными методами
когда я пишу код с монадическим I/O — я спинным мозгом чувствую, что императивный код и моя чистая функциональная логика — надежно изолированы друг от друга
это как раз то, о чём я говорил — изоляция, divide and conquer
типичный императивный код — mess
код на Haskell — разделён так, что подавляющую часть программы можно верифицировать формальными методами
код на Haskell — разделён так, что подавляющую часть программы можно верифицировать формальными методами
у вас есть доказательство сего факта?
Не, ну сама по себе МТ такая же детерминированная, как и функциональные языки. Это вот ее реализация в компьютере «грязная».
все говорят «я не могу» — «а ты возьми и купи слона!» ©
никто же не говорит, что их не должно быть
я думаю так
они допустимы только там, где можно доказать их завершимость
если доказать нельзя — рефакторим код так, чтобы доказывалось
у Нейла, похоже, так и сделано — его totality checker не пытается доказать недоказуемое, просто фейлится, если у него возникают затруднения
никто же не говорит, что их не должно быть
я думаю так
они допустимы только там, где можно доказать их завершимость
если доказать нельзя — рефакторим код так, чтобы доказывалось
у Нейла, похоже, так и сделано — его totality checker не пытается доказать недоказуемое, просто фейлится, если у него возникают затруднения
все говорят «я не могу» — «а ты возьми и купи слона!» ©
никто же не говорит, что их не должно быть
я думаю так
они допустимы только там, где можно доказать их завершимост
пардон глюкануло, чего-то
не-не-не! вы же говорите «могу». Вы говорите «Haskell — панацея». Вы говорите «profit и работает как часы». Я не просто так вцепился…
В чем отличие от императивного кода? Для него тоже самое верно… Можно рефакторить, пока prover не прожуёт… Можно выделять подможества, для которых разрешима задача проверки корректности… Всё можно.
В чем же реальный, практический profit FP?
все говорят «я не могу» — «а ты возьми и купи слона!» ©
не-не-не! вы же говорите «могу». Вы говорите «Haskell — панацея». Вы говорите «profit и работает как часы». Я не просто так вцепился…
они допустимы только там, где можно доказать их завершимость
если доказать нельзя — рефакторим код так, чтобы доказывалось
В чем отличие от императивного кода? Для него тоже самое верно… Можно рефакторить, пока prover не прожуёт… Можно выделять подможества, для которых разрешима задача проверки корректности… Всё можно.
В чем же реальный, практический profit FP?
он проще
субъективное утверждение требует хотя бы статистического доказательства…
Вот кстати в соседнем посте человек привёл достаточно интересный факт о том, что GHCшники отказались от darcs, о котором я не знал… Если бы он был проще, так darcs давно бы взяли бы и допилили бы… А так они взяли и пересели на git, который суть C + bash (и никакой функциональщины). Получается продукт написанный на «небезопасном и отсталом» языке качественней чем проект написанный на «безопасном языке будущего»? В чем причина?
Вот кстати в соседнем посте человек привёл достаточно интересный факт о том, что GHCшники отказались от darcs, о котором я не знал… Если бы он был проще, так darcs давно бы взяли бы и допилили бы… А так они взяли и пересели на git, который суть C + bash (и никакой функциональщины). Получается продукт написанный на «небезопасном и отсталом» языке качественней чем проект написанный на «безопасном языке будущего»? В чем причина?
я не о том, проще ли он для понимания
в математическом смысле, проще
и, кроме того, я верю, что он проще для понимания человеку без какого-либо background в программировании, чем Java или C
неспроста раньше в курсе SICP языком был Scheme (почему сейчас не так — это отдельная история)
в математическом смысле, проще
и, кроме того, я верю, что он проще для понимания человеку без какого-либо background в программировании, чем Java или C
неспроста раньше в курсе SICP языком был Scheme (почему сейчас не так — это отдельная история)
в математическом смысле, проще
Ну некоторое чистое подмножество лучше поддаётся анализу, этого я и не отрицаю.
Только ведь и в императивных языках такие подмножество выделять можно.
Например, у нас в ИСИ СО РАН есть лаборатория, в которой весьма успешно занимаются верификацией подмножества C#.
неспроста раньше в курсе SICP языком был Scheme (почему сейчас не так — это отдельная история)
Однако, Scheme не чист, не ленив и не статически типизирован =)
это я прекрасно знаю
тем не менее, там успешно применяются списки, рекурсия и map/reduce, вполне себе FP flavor
тем не менее, там успешно применяются списки, рекурсия и map/reduce, вполне себе FP flavor
вы же не будете спорить с тем, что человеку, начинавшему со Схемы намного проще понять Haskell, чем человеку, который начинал с PHP?
причина в том, что Git тупо лучше
не потому, что он написан на какой-то не такой хуите, на какой написан darcs
как продукт — лучше, как питательная среда для разработчиков самого Git — лучше
язык это всего лишь инструмент, важно лишь то, в чьих он руках
не потому, что он написан на какой-то не такой хуите, на какой написан darcs
как продукт — лучше, как питательная среда для разработчиков самого Git — лучше
язык это всего лишь инструмент, важно лишь то, в чьих он руках
UFO just landed and posted this here
UFO just landed and posted this here
вы спрашивали про прямые руки, а не про большие проекты — я привёл пример
а большие проекты на Haskell не пишут, потому что это академический язык, а не индустриальный
«реальный» функциональный язык — это O'Caml
caml.inria.fr/about/successes.en.html
в частности, на O'Caml был написан софт, который верифицировал управляющую логику самолётов Airbus A-340 (44k строк кода)
а большие проекты на Haskell не пишут, потому что это академический язык, а не индустриальный
«реальный» функциональный язык — это O'Caml
caml.inria.fr/about/successes.en.html
в частности, на O'Caml был написан софт, который верифицировал управляющую логику самолётов Airbus A-340 (44k строк кода)
UFO just landed and posted this here
>> Очень сомневаюсь, что Хаскель — хороший академический язык
он лучший — я гарантирую это ©
это практически эссенция FP (чистота, строгая типизация, ленивость), без компромиссов
к примеру, если бы Haskell был не ленивым — это был бы компромисс (в угоду предсказуемости производительности), и это изменило бы семантику Haskell-программ, пришлось бы писать код совсем по-другому, более многословно
если бы он был не чистым — это был бы компромисс
был бы слабо/динамически типизирован — опять компромисс (уже для разработчиков языка, т.к. лень/сложно реализовывать type inference / unification)
языки, в которых есть все эти компромиссы — лежат по другую чашу весов от Haskell
он лучший — я гарантирую это ©
это практически эссенция FP (чистота, строгая типизация, ленивость), без компромиссов
к примеру, если бы Haskell был не ленивым — это был бы компромисс (в угоду предсказуемости производительности), и это изменило бы семантику Haskell-программ, пришлось бы писать код совсем по-другому, более многословно
если бы он был не чистым — это был бы компромисс
был бы слабо/динамически типизирован — опять компромисс (уже для разработчиков языка, т.к. лень/сложно реализовывать type inference / unification)
языки, в которых есть все эти компромиссы — лежат по другую чашу весов от Haskell
UFO just landed and posted this here
Чистота — можно не пользоваться императивными возможностями языков, которые их предоставляеют.
Важна декларативность чистоты и нечистоты. И когда это выражается на системе типов — выглядит естественно. Хотя можно и ключевое слово ввести.
всяких классов типов
Ну я бы не говорил «всякие» :)
Помощнее интерфейсов-то будут.
>> Почти все функциональные языки ленивы.
по-моему, тут вы что-то путаете, т.к. из ФЯ ленив по умолчанию только Haskell
>> Вывод типов в Haskell, имхо, менее удобен, чем утиная типизация.
у меня большой опыт разработки на динамических языках (JavaScript, Python) — и могу сказать вот что:
динамические языки поначалу дают эйфорию: «вау, я еще ничего не написал, а программа уже компилируется и запускается», и это с неопытности можно легко принять за преимущество
со временем понимаешь, что это никакое не преимущество, а огромное зло — заранее неизвестно, упадёт программа или нет
и единственный способ узнать — сделать юнит-тесты, но даже с ними неизвестен code coverage, а он должен быть 100%… приходится использовать сложнейший софт для анализа code coverage, и хорошо ещё, если он доступен для выбранного языка (а для JavaScript такого точно нет)
«мыши плакали, кололись, но продолжали жрать кактус»
со строгой статической типизацией таких проблем нет, но нет и эйфории — приходится сначала сделать так, чтобы программа работала, а потом уже её запускать
тут уж конечно, кому понравится рутина по вычищению багов просто для того, чтобы программа скомпилировалась…
оттуда и растут такие «имхо», что duck typing это хорошо
по-моему, тут вы что-то путаете, т.к. из ФЯ ленив по умолчанию только Haskell
>> Вывод типов в Haskell, имхо, менее удобен, чем утиная типизация.
у меня большой опыт разработки на динамических языках (JavaScript, Python) — и могу сказать вот что:
динамические языки поначалу дают эйфорию: «вау, я еще ничего не написал, а программа уже компилируется и запускается», и это с неопытности можно легко принять за преимущество
со временем понимаешь, что это никакое не преимущество, а огромное зло — заранее неизвестно, упадёт программа или нет
и единственный способ узнать — сделать юнит-тесты, но даже с ними неизвестен code coverage, а он должен быть 100%… приходится использовать сложнейший софт для анализа code coverage, и хорошо ещё, если он доступен для выбранного языка (а для JavaScript такого точно нет)
«мыши плакали, кололись, но продолжали жрать кактус»
со строгой статической типизацией таких проблем нет, но нет и эйфории — приходится сначала сделать так, чтобы программа работала, а потом уже её запускать
тут уж конечно, кому понравится рутина по вычищению багов просто для того, чтобы программа скомпилировалась…
оттуда и растут такие «имхо», что duck typing это хорошо
UFO just landed and posted this here
caml статически строго типизирован, как и Haskell
Можно пояснить, что вы понимаете под статической утиной типизацией в OCaml? На ум приходят полиморфные варианты и функторы. Но у первых область применения ограничена, а функторы — более крупноблочное и синтаксически тяжелое средство. Кому как, а я от выразительных возможностей классов типов в OCaml не отказался бы.
по-моему, тут вы что-то путаете, т.к. из ФЯ ленив по умолчанию только Haskell
еще Миранда =)
> А так они взяли и пересели на git, который суть C + bash (и никакой функциональщины). Получается продукт написанный на «небезопасном и отсталом» языке качественней чем проект написанный на «безопасном языке будущего»? В чем причина?
У GHC большой codebase, и darcs просто не справлялся с нагрузкой (он в сторону увеличения количества кода плохо масштабировался тогда, вроде с тех пор ускорился, точно не знаю). В этом вся причина.
У GHC большой codebase, и darcs просто не справлялся с нагрузкой (он в сторону увеличения количества кода плохо масштабировался тогда, вроде с тех пор ускорился, точно не знаю). В этом вся причина.
Ответ не подходит к вопросу.
Я не спрашивал, почему пересели.
Я спрашивал, почему менее качественный.
Я не спрашивал, почему пересели.
Я спрашивал, почему менее качественный.
Вы часто сравниваете яблоки с апельсинами?
Git и Darcs для разных вещей придуманы: один для очень больших репозитариев (и быстрых операций), а второй для небольших проектов на Хаскеле.
Darcs отличная VCS, как и Git.
Git и Darcs для разных вещей придуманы: один для очень больших репозитариев (и быстрых операций), а второй для небольших проектов на Хаскеле.
Darcs отличная VCS, как и Git.
UFO just landed and posted this here
> А что, Git на небольших проектах хуже Darcs?
Кто тебе такое сказал? Неужели я? %)
> Тогда уж не яблоки с апельсинами, а отвёртки с универсальными отвёртками.
LOLWUT? О чем спор?
Кто тебе такое сказал? Неужели я? %)
> Тогда уж не яблоки с апельсинами, а отвёртки с универсальными отвёртками.
LOLWUT? О чем спор?
UFO just landed and posted this here
«Haskell не спас»… я плакалъ
darcs написан человеком, а не Haskell'ем
darcs написан человеком, а не Haskell'ем
UFO just landed and posted this here
darcs провалился потому что разработчику просто надоело его писать в отсутствие мотивации
Git намного старше darcs, а из забвения вылез благодаря github и популярности среди ruby-разработчиков
это совсем разные истории
Git намного старше darcs, а из забвения вылез благодаря github и популярности среди ruby-разработчиков
это совсем разные истории
Если вас интересует, есть ли вероятность, что darcs провалился из-за ЯП, то да, есть.
Если вы хотите доказать какую-то точку зрения, я бы хотел увидеть как формулировку точки зрения, так и оценку вероятности.
Иначе это выглядит как переливание из пустого в порожнее. Верным может оказаться любое мнение.
Если вы хотите доказать какую-то точку зрения, я бы хотел увидеть как формулировку точки зрения, так и оценку вероятности.
Иначе это выглядит как переливание из пустого в порожнее. Верным может оказаться любое мнение.
UFO just landed and posted this here
Мне хватает того факта, что вероятность есть, и она выше, например, вероятности угадать 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.
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 обнаруживать неполные паттерны
это единственное, из-за чего программа на чистом 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
Или это не оно?
и уж тем более баги в алгоритмике программы — тут, извините, язык ни при чём
вот, вот. т.е. от всех багов не застрахует и Haskell… иными словами фразу
функция работает как часы
в вашем предыдущем комментарии следует читать так
функция работает как часы, но эти часы вполне могут показывать неправильное время
Сделали fromJust где не надо, и опа-хопа аналог NPE в лоб…
Кстати личное наблюдение — хаскель засчет вывода типов очень располагает к test-driven девелопменту, почти как питон и прочие динамические друзья (конечно, в хаскеле тестов надо заметно меньше).
Это в отличие от C#/Java где в тестах получается дофига boilerplate кода, и в результате писать их там не очень приятно.
Это в отличие от C#/Java где в тестах получается дофига boilerplate кода, и в результате писать их там не очень приятно.
UFO just landed and posted this here
Ну «всё» — это, конечно, преувеличение, но ощущение волшебства, когда запускаешь программу, прошедшую контроль типов и она сразу работает, у меня было только в 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 (целочисленная дробь)
«Для любого типа t такого, что он принадлежит к классу Ord и Num, функция max2 принимает один аргумент типа t и возвращает результат типа t»
Про классы я ещё расскажу. Вкратце, суть в том, что если тип принадлежит к некоему классу, то над ним определены некоторые функции, и если max2 можно выразить только через эти функции, то сам тип уже не важен. Это может быть и Int, и Double, и даже Ratio (целочисленная дробь)
Интересующимся Haskell для изучения очень рекомендую «Programming in Haskell» моего лектора, вкладчика в развитие языка, Graham Hutton при University of Nottingham — www.cs.nott.ac.uk/~gmh/book.html.
Как по мне, то плотность изложения оптимальна. С нетерпением жду части, когда дойдет дела до имплементации «мяса» движка чата.
Спасибо, очень хорошая и понятная статья. Обязательно почитаю.
Сразу видно — человек трудился.
Низкий поклон тебе.
Сразу видно — человек трудился.
Низкий поклон тебе.
Все-таки самое интересное в ФП, на мой взгляд — это стыковка идеального мира функций с переменными, юзерским вводом и прочими изменяемыми значениями. Расскажите пожалуйста, как это реализовано.
От себя рекомендую прекрасный Learn You a Haskell for Great Good!
Спасибо, действительно полезный сайт.
Всем php-материалистам рекомендую хотя бы изредка отвлекаться на технологии, развивающие иной образ мышления, чем имеющийся у Вас в голове. Если этого не делать, то мозг рано или поздно превратится в зефир.
P.S: сам по работе в основном пишу на PHP
Всем php-материалистам рекомендую хотя бы изредка отвлекаться на технологии, развивающие иной образ мышления, чем имеющийся у Вас в голове. Если этого не делать, то мозг рано или поздно превратится в зефир.
P.S: сам по работе в основном пишу на PHP
Этот гайд чем-то напомнил мне Why's Poignant Guide to Ruby, который тоже очень, очень крут.
Вообще, все учебники, которые интересно читать, автоматически круты.
Вообще, все учебники, которые интересно читать, автоматически круты.
А Вы уверены, что foldr работает именно так, как Вы описали?
Ничего непонятно, синтаксис запутан до ужаса.
тоесть, получается, что выражение: fun arg
на самом деле означает не применение функции fun к arg,
а карринг функции fun по первому аргументу.
и если повезёт, и у fun больше нет аргументов, то результат будет скаляром,
а если не повезёт — то будет функцией с n-1 аргументами.
обозначение:
ghci> :t max
max :: (Ord a) => a -> a -> a
при таком раскладе выглядит логичнее, чем «принимает два аргумента»
и как?
на самом деле означает не применение функции 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)
//привет с лора
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'ов, и от специфики ленивого программирования
ну или убрать бесконечные списки вообще :) а то как-то получается, что бесконечные структуры данных упоминаются в отрыве и от 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, и это будет функция из числа в число.
Это декларация функции, которую можно вбить в исходном коде:
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), а из неё результат, верно?
Берём 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), а из неё результат, верно?
Sign up to leave a comment.
Основы