Pull to refresh

Comments 151

Ох, ленивые.
The real benefit of lazy evaluation still has to be evaluated out.
Прямо как на лекции по функциональному программированию:
— Сегодня я вам расскажу о ленивых вычислениях. Есть вопросы?
А я осилил до середины, там про историю интересно, дальше в «Почитать»
Бессонница. ФП. Тугие паруса.
Статью на хабре я прочёл до середины.
Рабочий день. ФП. В оконце свет.
Вторую часть прочту в обед.
А я прочел…
Многое встало на свои места.
Автору и переводчику +100500.
Занимательно, что строки в Java не изменяются. Интересно было бы выяснить причину такого вероломства, но не будем отвлекаться.

Для перформанса памяти стринговые переменны лежат в пуле стрингов поэтому все переменные класса String с одинаковым значением могут ссылатья на один объект в памяти (при том что единственная другая связь между ними это единый JVM). Так как для програмиста это неочевидно и отследить это проблематично то объект должен быть неизменным чтоб не возникло казусов что кто то привязал к твоему «Preved» своегое «Medved». Поэтому же String final чтоб от него не отнаследовали
В дотнете строки тоже immutable.
Увы, от злоумышленного reflection'а не спасает.
Сори, отправил раньше времени.
Поиском в Google site:rsdn.ru "defmacro.org" нашёл ссылки на другие статьи автора, но на эту — нет.
Таки да. Вы правы.
И что теперь делать с моим постом? Удалять?
ИМХО. Поставь ссылку. Удалять не надо: ибо вдруг будет интересная дискуссия?
Зачем ссылку? Есть ссылка на автора — зачем ссылка на другие переводы?
В переводе на RSDN есть довольно занимательные комментарии переводчика, который в ФП разбирается получше меня.
Ну с такой точки зрения — да, интересно.
Зачем? Может быть кто-то не читает RSDN, я констатировал факт. Пожалуй, перечитаю эту статью.
Ни в коем случае. Вы перевели, а не скопипастили перевод, потому пускай будет
Не надо удалять. Я бы и не узнал про статью если бы не ваш перевод. ) Отличная статья, и перевод тоже очень качественный, прочитал с удовольствием.
Добавил в шапку ссылку на RSDN вариант перевода
Классный текст! Правда я так и не понял когда имеет смысл использовать ФП, а когда — нет. В статье очень много слов посвящено тому, как на ФП можно программировать в смысле ООП. А все преимущества описаны как возможный потенциал: возможность распараллеливание, возможность оптимизации без участия человека.

Хотелось бы конкретных примеров:
1. Распараллеливание — берем известный алгоритм, пишем программу на ФП и на ООП так, как нам удобно и сравниваем.
2. Оптимизация — берем известный алгоритм, пишем программу на ФП и на ООП так, как нам удобно и сравниваем.
Я так понимаю, что на ФП код должен получиться существенно короче и понятней, скорость примерно одинаковой, так?

Относительно ФП у меня сложилось впечатление, что его преимущества проявляются при решении для больших задач. Для решения простых кусочков большой проблемы код пишется на ООП, а вот чтобы составить из этих кусочков решение всей проблемы стОит применять ФП.
UFO landed and left these words here
А как в этом случае с переполнением стека и нагрузкой на каналы передачи данных? Не становится ли она сильно избыточной?
UFO landed and left these words here
Когда у нас переменные не изменяются, то фактически это означает, что память одноразовая. И если бы работаем со сложной структурой, о которой думаем как о чем-то неделимом, то мы не можем изменить какой-то ее кусочек, мы обязаны менять ее целиком. Т.е. заводить новую и туда копировать данные. Это не создает чрезмерную нагрузку на память?
В случае использования списков, такой проблемы не возникает
UFO landed and left these words here
А копирование памяти — операция довольно быстрая.

тем более если используется механизм copy-on-write
> Если мы работаем со сложной структурой, о которой думаем как о чем-то неделимом — то она должна быть неделимой вне зависимости от подхода.

Я неточно выразился. Возможно лучше s/неделимой/цельной/.

> А копирование памяти — операция довольно быстрая.

Все относительно. Но избыточного копирования больших объемов данных стараются избегать.

Впрочем, это не важно. Кажется я понял как сформулировать то, что меня напрягает в ФП: в ФП нет привычных структур данных, привычных в том смысле, что мы можем их схематично нарисовать на бумаге, и их хранение в памяти компьютера будет примерно таким же, как наша рукописная схема.

Пример: как в ФП будет хранится матрица целых чисел? Будет ли это непрерывный массив ячеек памяти длинных n^2? Если да, то на сколько эффективна будет работа с ним, когда нам потребуется изменять только несколько ячеек? Если нет, то насколько эффективна будет работа с ним, когда нам потребуется работать со всеми ячейками?

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

Matrix.select ( sub {
… // выбираем нужные индексы матрицы
$cc->( $result );
},
sub {
… // изменяем полученные индексы как нам надо
return $newMatrix;
} );

Можно. Но скорость работы с ней при будет удручающей.
Функциональные языки не предназначены для числодробилок — для этого есть фортран. Они лучше подходят для сложных, требующих высокого уровня абстракции задач, где скорость разработки важнее скорости работы.
По моему опыту с Clojure для этого случая лучше писать код с сайд-эффектами (в императивном стиле), но изолировать всю работу с состоянием внутри функции, которая будет чистой. Т.е. зависеть только от входа и не менять глобального состояния.

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

1. На бумаге датаориентированный подход виде трубы, которая видоизменяет данные намного проще нарисовать, как и понять, чем императивную блок-схему. На самом деле большая часть документации намного более функциональна, чем код, который она описывает.
2. Когдая вижу x_new = x_old + 1 я знаю, что в любом месте я могу сделать мысленную подстановку. Когда я вижу x = x + 1, мне нужно мысленного разделить код на две части — до присваивания и после. Это настолько очевидно, что сейчас в любом стилевом гайде любого языка говорится о том, что переиспользовать переменные нельзя. В этом случае в императивном языке присваиваний становится намного меньше, чем кажется.
Пример: как в ФП будет хранится матрица целых чисел? Будет ли это непрерывный массив ячеек памяти длинных n^2? Если да, то на сколько эффективна будет работа с ним, когда нам потребуется изменять только несколько ячеек?

Матрица в ФП должна храниться как тип данных, у которого операции типа *, /, +, -, слайсов вызывают соответствующие функции библиотеки линейной алгебры, написанной на С.
На этом уровне абстракции требоваться изменять несколько ячеек придется очень редко, если матрица — действительно подходящая структура данных, а, например, не (key, key)->value хранилище на основе дерева, которое в случае персистентности достаточно эффективно. Плюс оптимизация, в коде x_new = x_old + 1 x_new могут занимать и одну область памяти, но это будет разруливать компилятор, избавив программиста от мыслей о состоянии.
Ой, да многие структуры данных имеют свою функциональную реализацию, эффективно работающую в условиях, когда необходимо получать новую копию. Организуются они так, чтобы любое изменение требовало как можно меньше копирований уже имеющихся данных.
Статья в тему на Wikipedia. В конце дана ссылка на книгу Okasaki; в ней должно быть много полезного по поводу неизменяемых структур данных и их практическому применению.
>… функциональную реализацию, эффективно работающую…

Относительно эффективно.

По моим экспериментам с Clojure — для бизнес логики в самый раз, а вот число-дробилки (number crunching) быстрее выполняются в императивном стиле.
UFO landed and left these words here
А может быть потому что простые вещи надо решать просто? Если, например, я пишу библиотеку для линейной алгебры, то зачем мне ФП? Лучше я реализую все алгоритмы с учетом функционирования аппаратной части и привяжу их к структурам данных, это ООП.
UFO landed and left these words here
UFO landed and left these words here
А есть ли в ФП разделение на Алгоритмы и Структуры данных? Особенно если для с одной структурой мы хотим работать на разных уровнях абстракции? Те же матрицы — порой нам нужно работать с отдельными элементами, а порой со все матрицей в целом. Как это будет реализовано?
В ФП есть туплы и списки. А из них можно построить что угодно.
Не зачем. Только библиотеки для линейной алгебры уже написаны, на них потрачены человеко-годы и нехрен велосипедить. А где использовать библиотеку уже большей частью все равно.
Лучше я реализую все алгоритмы с учетом функционирования аппаратной части и привяжу их к структурам данных, это ООП.

ООП — это наследование, которое в случае линейной алгебры нафиг не нужно там ничего, кроме голой структурщины.
Первая, имхо, человеческая статья по ФП, теперь я его не так сильно боюсь )
Хотелось бы продолжения в том же ключе.
String reverse(String arg) {
    if(arg.length == 0) {
        return arg;
    }
    else {
        return reverse(arg.substring(1, arg.length)) + arg.substring(0, 1);
    }
}


«бесконечная» рекурсия
Наверное это шутка, раз бесконечная в кавычках
Пардоньте, неправильно распарсил скобки :)
UFO landed and left these words here
Ну да, и мне кажется бесконечная, т.к. мы всегда возвращаем строку той же длины (только с переставленным символом), то условие arg.length == 0 никогда не выполнится, а значит выхода нет
Я считаю героями тех, кто может до конца за раз прочитать таки статьи.
А по-моему эта статья как-раз написано настолько хорошо, что глаз не оторвать. Возможно потому, что я искренне люблю ФП. Но и оригинал, и перевод, читаются на ура.
Я с ФП не знаком, но статью прочитал запоем, наверно сказалось мат образование)
Правда, некоторые вещи так до конца не осознал.
В статье много наврано в пользу «прекрасного ФП».

Unit тестирование

В реальности состояние есть (база данных, интерфейс пользователя), просто в этом случае ФП-программа будет выступать в качестве функции, которая берёт состояние мира в прошлой итерации (например, секунду назад) и возвращать новое состояние мира. Так что все зависимости сохранятся, просто их надо будет тащить в аргументы.

Отладка

См. выше. Функция, преобразующая мир, может выполнить 1000 итераций, а на 1001-й отвалиться. Кстати, по моему горькому опыту, как раз-таки функциональные программы тяжелее отлаживать.

Многопоточность

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

Развёртывание по горячему

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

Доказательные вычисления и оптимизация

Это всё можно прекрасно делать с императивными языками. И делается. Тем более, что по сути, императивные программы преобразуются в граф, haskell тоже свои программы компилирует в граф редукции. Но это только haskell, потому что он академический и чисто ленивый, а вот многие другие ФЯ перед оптимизацией и генерацией машинного кода получают представление, слабо отличающееся от того же байткода Java.

Функции высшего порядка

Этим давно уже никого не удивишь в мире императивного программирования. Благо, есть Python, Ruby и C#. Java тоже подтягивается.

Ленивые вычисления

Это не является прерогативой одних только ФЯ. Во-первых, далеко не все ФЯ ленивые. Во-вторых, организовать ленивость в ИЯ при необходимости несложно.

Pattern matching

А что мешает делать PM в императивных языках?

Замыкания

Этим сейчас тоже никого не удивишь. Даже в Java есть довольно давно
> В реальности состояние есть (база данных, интерфейс пользователя)

БД и интерфейс в юнит-тестах? Что-что, простите?

>… много всего с упоминанием слова «мир»…

SiCP в руки и учиться декомпозиции задачи.
БД и интерфейс в юнит-тестах? Что-что, простите?

Отлично, можно сказать «автоматические тесты», чтобы быть формально корректнее. Ну вот что делать, если код зависит от БД? Конечно, к БД обращаемся через интерфейс и в тестах даём тестируемому функционалу заглушки и mock'и. Но всё равно, даже такая непрямая зависимость от БД порождает зависимость от состояния.
SiCP в руки и учиться декомпозиции задачи.

SiCP читали, декомпозицию умеем, монаду IO знаем. И чо? Как не было серебряной пули, магическим образом решающей обозначенные в статье проблемы, так и нет. ФЯ лучше ИЯ только в определённом круге задач (а в определённом вообще позорно сливает), хоть обчитайся SiCP.
Корректнее тогда уж «интеграционные». Про каковые в статье ни слова.
А там где есть I/O, у вас есть соответствующая монада. Хотя я придерживаюсь мысли, что идеальный подход — гибридный.

Далее по пунктам:

Отладка


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

Многопоточность


Map/reduce. Да, да, в императивных тоже можно. А откуда оно пришло в ГИБРИДНЫЕ языки ruby, python, С№ итд?

Функции высшего порядка


Ленивые вычисления

Pattern matching

Замыкания

И откуда оно в гибридных языках?

Развёртывание по горячему
Что-то мешает преобразовать? Каррирование на что?
Корректнее тогда уж «интеграционные». Про каковые в статье ни слова.

Ну если говорить про чистые юниты, то они и в ИЯ не вызывают проблем. Интеграционные тесты тоже можно автоматизировать, да. Потому я про автоматические тесты сказал.
А там где есть I/O, у вас есть соответствующая монада. Хотя я придерживаюсь мысли, что идеальный подход — гибридный.

А можно и без монад, если это не Haskell, а гибридный язык. Монада — это костыль для чистых ленивых ФЯ для обработки состояний. Такой же, какие бывают в ИЯ для организации ленивости и т.п.
Функции высшего порядка
Ленивые вычисления
Pattern matching
Замыкания

Учитывая, что оно прекрасно прижилось в ИЯ, стоит ли эти фичи называть монопольными фичами ФЯ? По сути, что такое ФЯ? Это язык, где мы напрямую не оперируем состоянием? Где функции не дают побочных эффектов?
Развёртывание по горячему
Что-то мешает преобразовать? Каррирование на что?

Тут без конкретики сложно о чём-то говорить. Например, вот есть сервлет на Java. При старте в контейнере у него ORM обходит классы модели и компилирует шаблоны. На это тратится некоторое время. Кто мешает контейнеру быть чуть умнее и запускать второе окружение для сервлета, а когда он полностью запустится, выключать первое окружение со старым сервлетом и все запросы начать направлять ко второму? Как бы проблема решилась, если бы мы использовали haskell?
Согласен, что монады — костыль.

> Учитывая, что оно прекрасно прижилось в ИЯ, стоит ли эти фичи называть монопольными фичами ФЯ? > По сути, что такое ФЯ? Это язык, где мы напрямую не оперируем состоянием? Где функции не дают
> побочных эффектов?

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

Про горячие развертывание:
Честно говоря не знаю как в хаскелле, но в эрланге примерно так и происходит. Только это все инкапсулировано от разработчика и происходит «по волшебству».
функции высших порядков

В Python, Ruby и C# есть ФВП. Вопрос: почему мы Haskell, Erlang или Ocaml, где они тоже есть, считаем функциональными, а другие языки — не считаем? Уж не из-за декларативного ли стиля? Я так скажу, что благодря fluent-интерфейсу на C# определённые вещи пишут очень и очень декларативно.

декларативный стиль программирования

Я так понимаю, что декларативный стиль от императивного отличаются тем, что в первом случае мы сказали «хочу в Тамбов» и нас интерпретатор повёз, а в императивном мы рулим-рулим, и внезапно туда попадаем. Я понимаю, что первое удобнее и быстрее, однако ФЯ такого не дают. Я пишу быструю сортировку на haskell-е в одну строчку и он мне сортирует мееедленно. Если хочется быстро, мне надо писать какое-то там слияние. Т.е. я рулю сам. Что я делаю не так? Нет, ну т.е. я прекрасно понимаю, чем ИЯ от ФЯ отличается, но даёт ли это какое-то реальное преимущество одному перед другим?

в эрланге примерно так и происходит. Только это все инкапсулировано от разработчика и происходит «по волшебству».

Отлично. Кто указанное поведение мешает реализовать в контейнерах сервлетов? При этом оно тоже будет совсем инкапсулировано от разработчика.
> Я так понимаю, что декларативный стиль от императивного отличаются тем, что в первом случае мы сказали «хочу в Тамбов»…

Нет. Такое понимание приходит от кривоватых определений в статьях.
Декларативный стиль, это когда вы задаете набор определений от самый простейших (в ФЯ определения задаются в виде функций, поэтому они и ФЯ), а интерпретатор их уже рекурсивно крутит.
Т. е. в вашем примере:
«Тамбов — это город»
«Поезда ездят из города в город»
«Я сижу в поезде не в Тамбове, а поезд едет в Тамбов.»
итд.
IO тоже можно без монад. Монада к IO имеет весьма посредственное отношение и вовсе не костыль, коль уж имеются монады Exception, Maybe, State, Async (async/await), Cont (call/cc) и другие.
Эдак можно сказать, что async/await — костыль для языков, где нет нормальных монад.

Хотя можно придерживаться т.з., что объекты — замыкания для бедных, а замыкания — объекты для бедных.
Тяжело будет делать IO без монад вообще. Монада != спец. синтаксис для неё в haskell. И про остальные монады я знаю. Я не говорил, что монада как таковая костыль для haskell. Конкретно монада IO + сахар в виде do — это и есть костыль для состояния.
Сахар в виде do — он для всех монад.

Ну, а Async-monad — костыль для асинхронный действий?
А Cont-monad, надо полагать, костыль для call/cc?

В чём костыльность-то проявляется?
Вот на примере Java. Там не сделали нормального вывода типов, потому надо писать:
Map<String, Integer> someMap = new HashMap<String, Integer>();

Добрые люди пишут различные библиотеки, которые позволяют сделать покороче:
Map<String, Integer> someMap = Collections.newMap();

Это костыль. Теперь насчёт Haskell. Его природа обделила императивными конструкциями. Поэтому работу с состояниями надо делать в монаде IO. Вроде бы это можно делать, но возникает ряд сложностей. Например, новичку надо разобраться, почему в одних случаях можно написать
do
   a <- someFoo

а в других
do
   a <- return someFoo

Или почему в одних случаях нужен map, а в других — mapM. Ну и т.п. В Java, чтобы вызвать императивный метод или организовать императивный цикл, не нужно знать тонкостей реализации JVM, потому что в Java императивность есть нативно. А вот в Haskell — костыль.
> новичку надо разобраться
То, что новичку что-то там не понятно, не тянет на аргумент о костыльности в системе.
Может, и теория групп в математике тогда костыли?

map от mapM отличаются не только тем, что там монада, а и тем, что для map выполняются функторные законы, для mapM — нет.
Тот же mapM можно написать как минимум двумя способами.

В хаскеле не нужно знать «тонкости», надо понимать, что такое чистота. Новичку и впрямь непривычно, но на костыльность это не указывает.
Отлично. Тогда договоримся, что мы будем считать критерием костыльности?
Случайно отправил, недописав.

В случае языков ситуация, когда недостающая функциональность обходится другими средствами языка, при этом крайне неудобно. Как лямбды в C++03, например (boost::bind + boost::lambda).

Так вот чистота и ленивость — это фундаментально в Haskell, и сделано намеренно. И различие между IO/State/whatever и чистыми функциями тоже сделано намеренно. Это меняет подход, это неудобно новичкам, но это не костыль.

У сколь-нибудь опытных программистов на Haskell с этим проблем нет.
Задам встречный риторический вопрос.

В Haskell STM (Software Transactional Memory) реализовано в виде библиотеки. Благодаря тем самым «костыльным» монадам для состояния, у нас есть гарантия того, что всё STM изолировано и мы не можем внутри обращаться к «левым» ссылкам.
Как дать такие гарантии там, где потенциально всё — IO?

Также есть монада ST, мы можем локально заводить мутабельные переменные, но так как они изолированы, итоговое вычисление гарантированно чистое. Оборачиваем это в runST и получаем чистое значение, реализованное императивным способом. Это аналогично pure из D, но без дополнительного ключевого слова и какого-то специального анализа компилятора.
Вопрос тот же, как это сделать в языке, где нет таких «костылей»? В D пришлось ввести новое ключевое слово.
В Java можно менять байт-код как душе угодно. На этом и основаны некоторые библиотеки, реализующие STM в Java.
Если внутри STM-кода я меняю внешнюю переменную, то что будет?
Ой, ну что вы въелись так? Не приходилось мне использовать STM, потому тонкостей я не знаю. Ну не мой это конёк. Могу предположить, что «а если я себе выстрелю в ногу» — примерно из той же оперы вопрос. В крайнем случае, всегда можно написать утилиту, которая проанализирует байт-код и поругается на места, где можно поломать STM, и даже свой SuppressWarning для неё предусмотреть, мол, я знаю, чего хочу. Причём, это может быть даже не утилита, а плагин к одному из множества анализаторов кода для Java. Или плагин к встроенному анализатору кода IDE (для Netbeans и IDEA такое точно можно сделать, для Eclipse не приходилось, но тоже, может быть, возможно).

Лучше уж я задам вопрос по тому, что я знаю. Вот если есть у нас какая-то логика на веб-сервере, и её нужно реализовать один-в-один в браузере на JS (например, логика валидации или парсер языка разметки для превью в блоге), то как такое могут решить средства Haskell (не считая написания компилятора самого Haskell)?
SiCP читали, декомпозицию умеем, монаду IO знаем. И чо?

Чо чо, «все в IO», — это не ФП, а то, во что превращается код, втупую перенесенный с императивного языка. ФП — это выкинуть из IO все, что можно, оставив легко отлаживаемый ссылочно-прозрачный минимум.
Отлично. Что делать, если состояние всё-таки есть? Как я уже говорил в комментах к другому топику о функциональных языка, я с этим столкнулся при написании компилятора. Ну вот есть множество входных символов, положение в тексте (номер строки/номер столбца), накопленные ошибки. Если таскать всё это а аргументах — можно убиться. И вообще, я не пишу о том, что надо везде использовать IO. Я просто хотел показать, как все перечисленные достоинства ФЯ исчезают при встрече с реальным миром, в котором есть состояние.
Странно, Agda, GHC и куча других компиляторов написано на Haskell как раз потому, что он для этого удобен.
Я пишу на нём уже несколько лет, писал WebSocket-сервер, mp3tags-декодер (ибо текущие реализации на тот момент на любом языке многие не поддерживали даже сжатия, не говоря о шифровании), и почему-то пока не столкнулся с ситуацией, где «все перечисленные достоинства» исчезали бы.
Когда критикуешь что-то, важно вовремя остановиться, чтобы не скатиться в критику всего подряд, даже если критиковать нечего.
Это всё можно прекрасно делать с императивными языками
Этим давно уже никого не удивишь
А что мешает делать PM в императивных языках?
Этим сейчас тоже никого не удивишь
Прошу прощения, отправил раньше времени.
Так вот, цель статьи не в том, чтобы кого-то удивлять «уникальными фичами», а в том, чтобы дать представление об основных инструментах ФП. И если всё это «можно», то почему в императивных языках всё это так непопулярно? Не потому ли, что в ФП такие лучше интегрируются и смотрятся естественнее?
Ну насчёт лучше интегрируются и смотрятся естественнее — это вопрос спорный. А в каких императивных языках это непопулярно?
Дочитал. Спасибище огромное, как уже заметили выше — очень годная статья для новичков в ФП :) И да, читал вдумчиво и не торопясь выпив кружки три кофе, без малого полтора часа заняло :)
Занимательная статья и хороший перевод. Но с отождествлением каррирования и шаблона Адаптер автор, кажется, переборщил. Каррирование — только один из сценариев использования адаптера, а ведь есть и другие.
Хорошая, интересная статья.

Однако, на мой взгляд, автор давно (или вообще?) не работал с web-яп в духе Python, Ruby, PHP, так как в них многое, что в статье описывается как «невозможное в императивных языках» является вполне себе возможным и даже не считается магией.
>Python, Ruby

их никак нельзя называть «web-яп» и поставить в один ряд с PHP. Даже Ruby, который для меня привычнее всего воспринимается как "...on Rails" :) Это general-purpose ЯП, и питон в огромном числе своих применений не имеет к вебу никакого отношения.
Хм, РНР вообще-то тоже без вэба можно использовать, но вы же поняли о чём я?
Большое спасибо за перевод. Оригинал бы тоже осилил, но нашёл бы лет через 100 — хабр как-то чаще читается :-)
Очень надеялся здесь же прочитать про монады, но в конце увидел жуткий облом.
Скажите, автор уже написал на английском, или это «свежачок»? Потому что мне кажется, что понимание монад — это именно то, чего мне не хватает, чтобы представить, что все эти чисто функциональные программы вообще могут работать. И почему без монад вообще никак?
Там же вначале написано:
«Понедельник, 19 июня, 2006»
Так что, думаю, автор уже написал:)
Смотрел другие его публикации — нигде нет намёка на продолжение. Отправился в самостоятельное копание за что получил по шапке по причине непроизводственного использования рабочего времени :-)
Наконец-то в википедии нашёл замечание, способное пролить свет на суть монад:
«Although a function cannot directly cause a side effect, it can construct a value describing a desired side effect, that the caller should apply at a convenient time.»

Если я правильно понял, монады используются для того, чтобы, вызвав какую-ту функцию с желательным сайд-эффектом, они вернули описание этого сайд-эффекта, которое можно куда-то применить. Самый непонятный момент — «применить». Реально ли написать ОС на чисто функциональном языке?
Я что-то слышал про ковыряние ОСи на Хаскелле. Основная проблема — чем ниже уровень, тем больше возникает императивного кода с острейшими требованиями по скорости исполнения.

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

Наверное, если попытаться спроектировать железо так, чтобы можно было комфортно обрабатывать прерывания, управлять скедулером и т.д., получится лучше…
ИМХО без специально заточенного под это дело железа смысла писать ось нет. У меня просто другое когнитивное затруднение — может быть, чтобы такая система в принципе могла быть написана на чисто функциональном языке, она требует специально заточенный под неё мир? Чисто функциональный… :-)
Насчёт мира не знаю. Интерфейсы — может быть.
UFO landed and left these words here
Ну это просто феерия какая-то. Разумеется, паравиртуализированная система может быть написана на чём угодно. Причина очень простая — паравиртуализированный домен физически не имеет права исполнять привилегированные инструкции. За него всё делает dom0 (а там линукс, ага, со своими мерзкими императивными драйверами к чему попало) и xen (который разруливает прерывания и управляет памятью).

Это всё равно, что показывать куличик в песочнице и говорить про возможность строительства небоскрёбов на песчаном грунте.
UFO landed and left these words here
Я про это и говорю. Паравиртуализированный домен (если в него не делегируется PCI-шина) находится от железа на большем расстоянии, чем userspace-программа обычного домена.

Все паравиртуализированные драйвера (blkfront, xen-netfront) проектировались так, чтобы всё самое сложное было в -back половинке, а front был бы примитивным и простым в реализации (для упрощения портирования ОС под Xen).

Другими словами, наличие чего-либо под xen означает лишь способность делать гипервызовы (которые от syscall отличаются лишь номером прерывания), да способность прочитать свою start page. Кроме этого, машина в домене ничем от обычного ELF-приложения не отличается.
Это не суть монад, это частный случай State.
Возьмите код для maybe, list, writer и state (в таком порядке, сложность будет возрастать) и раскройте скобки, превратив do-нотацию во вложенные bind'ы, а потом раскрыв бинды в вызовы функций. Там все видно будет, что общее, а что частное. И каким образом state (и IO) тащит состояние.
Мысли в процессе чтения:
1. Pattern matching — широкое использование в CSS, XSLT.

2. Функция-наследник — это коллбеки в современных языках и 3-адресная система команд в машинных языках.

3. Есть какое-то противоречивое противопоставление возврата значений и передачи результата. Передача результата — это возврат. Как можно говорить, что первое не нужно, потому что есть второе?
Это всё всегда красиво выглядит, пока речь не заходит о том, что нужно работать с байтами и битами. Что нужно СНАЧАЛА записать в порт 0xFFFE байт 0xF3, _ПОТОМ_ через 0.3-0.35нс прочитать из порта 0xFFE значение, _ПОТОМ_ начать через указанное число НС и читать массив значений с частотой 10000000 шт в секунду из порта 0xFFFA с допуском не более ± 0.1мс.
Это все от того, что существующее железо императивно. Все-таки ФП тянет абстракции «вверх» а не «вниз».
Попробуйте описать простенький интерфейс к шаговому двигателю без интеллекта в функциональных терминах.

Дано: на ногу поступает напряжение, двигатель делает поворот на 1 градус. Даётся напряжение на другую ногу — в другую сторону. Длительность импульса — не менее 10мс, можно дольше, но не сильно, ибо греется (например, не более 50мс). Заметим, никакой логики и интеллекта. На ноге А 1 — ползём влево на шаг. На Ноге Б — вправо на шаг. Для двух шагов нужно повторить цикл появления исчезновения напряжения с паузой не менее 3мс.

Попробуйте предложить этому функциональную обвёртку. Кстати, хорошая задача для любителей ФЯП.
FRP — список (время, действие) превращается в список (время, напряжение на ногах). Этот список хавается единственной грязной функцией, которая просто устанавливает нужные напряжения в нужный момент времени.
Дальше для любого типа двигателя нужно просто представить набор функций, который мало будет отличаться от таблички в документации. Хороший код на С будет выглядить примерно так же, но этому еще и научиться нужно, скорее всего этот код будет одноразово захардкожен.
Стоп. Мы с вами в реальном мире живём. Что значит «в нужный момент времени»? Какого времени? wallclock или таймера? Кто таймеры обрабатывает и отсчитывает?
Мне кажется, или мы обсуждаем как это всё писать на ФЯПе?

Кроме того, 10мс вы получите очень неточно (если мы про современное железо и обычный sleep), куда точнее использовать высокоточные таймеры.… Или считать тики процессора (спинлоки). Я с трудом понимаю, как это всё можно уложить в парадигму красивости.

… А главное, чем ближе к железу, тем меньше там алгоритмов высокого уровня и больше именно таких «прочитать, поспать, записать, вернуть, изменить бит» и т.д.
Мне кажется, или мы обсуждаем как это всё писать на ФЯПе?

Я объяснил, как это писать на ФЯПе. Хотите считать тики — считайте в Sleep тики, или используйте РТОС, что бы вы на С делали, то и тут делайте в общем. Это будет одной грязной функцией из двух. Вторая — та, которая состояние на ногах меняет.
А главное, чем ближе к железу, тем меньше там алгоритмов высокого уровня и больше именно таких «прочитать, поспать, записать, вернуть, изменить бит» и т.д.

Смотря к какому железу. Те же видеокарты намного проще в терминах map и reduce программировать. С VHDL тоже не все так неоднозначно, там по сути два языка, императивный и декларативный, и декларативно обычно проще описывать. Хотя я не особый знаток VHDL.
Если речь о контроллерах и С, то скрипты типа «прочитать, поспать, записать, вернуть, изменить бит» низкоуровневы, но не особо грязны. Есть входное состояние, выходное состояние, скрипт между ними. Шаговый ли это двигатель или протокол обмена, набор реально низкоуровневых примитивов мал. Для шагового двигателя их два, например.
Запись в порты — это IO в любом случае. Можно сделать на продолжениях.
В нечистых ФЯ реализуется ровно так же как в императивных. Т.е проблемы в принципе нет, как и особой «задачи для любителей ФП»
Оказывается кроме «статью_не_читай@комментарий_пиши», есть еще «комментарии_не_читай@комментарий_пиши».
<< действительно работающей машиной Тьюринга
Вот прям так? Машина Тьюринга в реальном мире? С бесконечной лентой? Основа машины тьюринга именно эта бесконечность.
В реальном мире можно использовать:

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

«потенциальную бесконечность» — когда головка доходит до конца ленты, машина останавливается, специально обученный человек наращивает ленту, машина продолжает работать.
Спасибо за статью. Историческая часть была интересной, но примеры на Джаве смотрелись странно.
Автор оригинальной статьи ставил себе целью объяснить ФП для любителей императивных языков программирования. Видимо он решил, что Java будет понятней большинству.
Ленивые языки позволяют создавать бесконечные структуры данных, создание которых в строгих языках гораздо сложнее [пер. — только не в Python].


Python сложно отнести к «строгим» языкам — он мультипарадигменный и содержит достаточно много функциональных элементов. В частности, генераторы бесконечных последовательностей с помощью ключивого слова «yield» :).
Я как раз и вспомнил про yeild и генераторы, когда увидел «бесконечные структуры данных».
очень крутая статья, давно здесь таких не было.
ловите плюс.
Так, про ФП на понятном языке прочитал, спасибо, теперь надо найти что-нибудь подобное про лямбда-исчисление.
UFO landed and left these words here
Именно 'Real World Haskell' я сейчас и читаю. А теория мне скоро понадобится для реферата.
> В императивном мире это невозможно [пер. в Smalltalk-е очень даже возможно]. Представьте себе выгрузку Java класса на лету и перезагрузка новой версии. Если бы мы так сделали, то все экземпляры класса стали бы нерабочими, потому что потерялось бы состояние, которое они хранили.

В Common Lisp тоже возможно. С перезагрузкой определений классов. (Нет, он не функциональный, дальше даже о этом сказано)
750 добавило в избранное, рейтинг +150… Вы чего человеку в карму не плюсуете?!
да просто потому что в избранное может добавить кто угодно, а оценки доступны очень мало кому
Отличная статья, теперь мне как заядлому ООПшнику (C++, Delphi, C#) функциональщина не кажется таким WTFом как раньше.

Впрочем, как всегда, одни плюсы и никаких серьезных минусов парадигмы не указано. А так не бывает. Это настораживает.

Опять же на вопрос почему функциональщина не шагает по планете, приводятся примеры в общем-то специфического телекоммуникационного софта. А как насчет более традиционных приложений? Корпоративный сегмент например? Работа с БД?
UFO landed and left these words here
Спасибо за обзор инструментов, но меня больше интересует опыт. Опыт разработки корпоративных приложений с использованием ФП. Какие плюсы/минусы.

Кстати отдельный вопрос, как насчет GUI? Десктопные GUI-приложения чисто на ФП пишут ли?
UFO landed and left these words here
Спасибо за тёплый приём. Были интересно читать комментарии.
Удивительное дело, Я почти неделю занимался переводом. В сумме четыре раза перечитывал весь пост с включённой проверкой орфографии, и всё равно в текст проникли опечатки и ошибки. Спасибо всем, кто писал мне на почту замечания. По состоянию на момент написания этого комментария в тексте было исправлено 10 опечаток. Это так, для статистики :)
Вы выбрали прекрасную статью для перевода!
Спасибо за отличный, качественный перевод, полностью осилил и многие вещи стали понятные! Очень хочется продолжея в таком же ключе и такого же качества!
Я никогда не работал с фонкциональными языками, но когда я встречаю что-то, чего не знаю, я смотрю — а сколько людей с такими навыками требуется? Смотрю обычно на монстре в штатах — самый большой рынок.

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

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

> В данной статье мы поработаем с продолжениями, а монады и однозначную типизацию отложим до следующего раза

следующий раз был/будет?
Очень интересная статья. Прям завораживает, действительно раньше функциональное программирование пугало, а после этой статьи почитал про него еще. Сидя в своей уютной visual studio с уютным c#, разумеется, начал копать в сторону языка f#. И вот что я понял, поправьте меня, если ошибаюсь: являясь дотнетовским языком у f# остался лишь «лаконичный синтаксис». То есть, один из озвученных плюсов — что легко распараллеливать код, на самом деле, один и тот же код на разных языках, c# await/async и f#, со своим подходом, компилируется в один и тот же промежуточный IL. То есть какого-то выигрыша производительности нет и разница только в синтаксисе? Другими словами, f# не является чистым функциональным языком, поэтому в нём нет никаких других преимуществ или плюсов?
UFO landed and left these words here
Да что же вы все уперлись в эту параллелность.
Это вообще десятое дело, вопринимайте ее просто как приятный побочный бонус.
Вся эта функциональная чистота нужна исключительно для человека. Банально потому, что он не может оперировать 0 и 1 для составления сложных систем. Ему нужны абстракции для написания программ. Если бы программы писали роботы, то они прекрасно оперировали 0 и 1. И никогда не ошибались при этом)
Все эти навроты нужны только для того, чтобы программист ошибался меньше и легче понимал что написали другие программисты.
Поэтому абсолютно не важно, что c# await/async и f#, со своим подходом, компилируется в один и тот же промежуточный IL. Начхать с высокой колокольни. Главное тут удобство и корректность для разработчика. А у F# этого больше чем у C#.
UFO landed and left these words here
> Почему Вас это так сильно раздражает?
Раздражает? ))) Меня не раздражает, а удивляет дремучесть людей, которые из всего многообразия возможнотей, выбирают в качестве основного аргумента фактически побочный эффект. Очень приятный и полезный безусловно. Но все же не являющийся ключевой особенностью.

>Так почему об этом нельзя говорить?
Почему же нельзя говорить? Можно конечно. Только говорить надо корректно)
Выражения типа «на самом деле, один и тот же код на разных языках, c# await/async и f#, со своим подходом, компилируется в один и тот же промежуточный IL. То есть какого-то выигрыша производительности нет и разница только в синтаксисе? Другими словами, f# не является чистым функциональным языком, поэтому в нём нет никаких других преимуществ или плюсов?», просто показывают дремучесть человека, который не понимает о чем вообще идет речь.
И вот в таком ключе говорить лучше не надо. Потому что многих, прочитавших такие высказывания это введет в заблуждение. Они будут думать, что все преимущество того же f#, должно быть в каком-то особенном байткоде. А это очевидная чушь.
Повторюсь, вся эта функциональщина, все эти навороты, нужны исключительно человеку, а не компьютеру.
UFO landed and left these words here
Only those users with full accounts are able to leave comments. Log in, please.