вторник, 13 октября 2009 г.

[comp.prog.thoughts] Причины шума вокруг функционального программирования

Начинал писать этот текст как комментарий. Но потом решил вынести в отдельную заметку, уж очень понравилось самому как написано ;)

По поводу шума вокруг ФП у меня в последнее время вот какая теория.

Когда-то давно, больше двадцати лет назад, в мейнстриме было структурное и модульное программирование. Которое подразумевало обычную функциональную декомпозицию задачи. Для ряда актуальных на тот момент задач (GUI, например) функциональная декомпозиция была не очень удобна. Соответственно, народ искал альтернативы, поскольку трудоемкость решений зашкаливала. И тут подворачивается что-то новое и неизведанное под названием ООП. Которое наглядно демонстрирует, что вот это и вот это в ООП решается гораздо проще и быстрее, чем в структурном программировании.

Ура! Все толпой в ООП!

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

Однако время шло. Выросло новое поколение программистов, которое начинало учиться сразу на ООП. Что такое структурное программирование и функциональная декомпозиция сейчас мало кто знает и/или помнит. Везде ООП – и где нужно, и где не нужно. Как обойтись без ООП знают не многие. Даже на C программируют с использованием ОО идей о полиморфизме и инкапсуляции (сам видел примеры такого кода в OpenSSL и gSOAP).

Соответственно, писать в ОО стиле некоторые задачи сложно, трудоемкость решений зашкаливает. А тут подворачивается что-то “новое и неизведанное” под названием ФП. Которое наглядно демонстрирует, что вот это и вот это в ФП решается гораздо проще и быстрее, чем в ООП. Ну и плюс шумиха вокруг multi-/manycores и concurrency: “Как! Вы все еще кипятите выискиваете баги в shared memory из-за side effects?! Тогда мы идем к вам!”

Ура! Все толпой в ФП!


Естественно, что все это утрирование и передергивание. Современное ФП – это далеко не структурное программирование середины 80-х годов. Но в плане декомпозиции задачи я честно не вижу принципиальной разницы между ФП и структурным программированием. А различия между декомпозицией в функциональном и объектном программировании были хорошо описаны в книге Бертрана Мейера “Объектно-ориентированное конструирование программных систем”.

18 комментариев:

Rustam комментирует...

Я уже с Владом поругался на тему что функциональщина ничего нового не приносит в плане декомпозиции:)

По моему это не так. Те же параметризованные модули из ML (правда это не только в функциональщине есть, например у ады тоже, но все равно это новье по сравнеие с "классическим" СП) и/или монады уже сильно меняют структуру кода. Да и ФВП тоже сильно смещает акценты, также и алгебраические типы.

eao197 комментирует...

Я читал ту тему. Там был один хороший вопрос с просьбой демонстрации этой функциональной декомпозиции. Но ее не было, демонстрации-то. Игра в шашки точно так же решалась бы на какой-нибудь Modula-2 в 1980-м.

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

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

Но для многих задач (те же вычисления или синтаксический анализ) это гораздо удобнее ООП.

Quaker комментирует...

Почитав пару номеров журнала "Практика функционального программирования" (http://fprog.ru/2009/), я понимаю, что в функциональных языках ничего сверхнового нет. Идеи есть интересные (борьба с изменяемым состоянием), но уж слишком это все заматематизировано (это отчетливо видно на примере статей Душкина). Лучше почитать книги по дискретной математике и уже упомянутую книгу Б. Мейера.

Rustam комментирует...

По моему это только схожесть, проектирование совершенно разное, хотя допускаю что я уже успел позабыть структурное :)

Хотя может решает мощность. Например в структурных языках, просто невозможно также легко манипулировать функциями как в ФЯ это и ФВП из которых строится все как из кирпичиков (кстати как и в ООП) и замыкания которые во многом локально заменяют объекты.

По моему наоборот ява интерфейсы с одним методом это жалкая пародия на замыкания :)

Rustam комментирует...

Я читал ту тему. Там был один хороший вопрос с просьбой демонстрации этой функциональной декомпозиции. Но ее не было, демонстрации-то. Игра в шашки точно так же решалась бы на какой-нибудь Modula-2 в 1980-м.

С демонстрацией теже проблемы что с ООП на маленьких примерах ничего ни увидишь. Как пример вполне кстати подходит, например решение задачи К на rsdn http://www.rsdn.ru/forum/decl/2720396.flat.aspx#2720396 кстати из него видно что функциональная декомпозиция скорее ближе к ОО чем к структурной :)

eao197 комментирует...

2Rustam: С демонстрацией теже проблемы что с ООП на маленьких примерах ничего ни увидишь.

Может быть.

По моему наоборот ява интерфейсы с одним методом это жалкая пародия на замыкания :)

Мне кажется, что наши формулировки не противоречат друг другу :) На уровне реализации моя, имхо, точнее :))

Skynin комментирует...

Ура! Все толпой в ООП!
Было, было :)
Причем им отвечали:
Да тоже самое можно ж писать и в процедурном стиле :)

Выросло новое поколение программистов, которое начинало учиться сразу на ООП.
Должно было бы вырости :)
Но поражаюсь, как не зная что такое процедурный стиль умудряются на нем писать.

Тогда еще, говорили - не-е-е, на С++ ООП не поймешь. Нужно углубиться в Smalltalk, вот тогда ты поймешь, что такое ООП, а не будешь лепить структуры с указателями на функции.

Но как бы там ни было - ООП прижилось, потому что во многом в нем можно усмотреть продолжение процедурного стиля.

А вот с ФП... не так :) Это иное программирование.

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

Как обойтись без ООП знают не многие.
Может это мне так много лет "везет", но из своего опыта - ООП знают немногие :) Поэтому скорей:
Без ООП обходятся и доныне - многие.

Konstantin комментирует...

Зря вы так :)

С тем же успехом можно и ООП на структурное нятянуть:
"Структуры-состояния передаются в процедуры-методы. А виртуальная диспетчеризация для выбора ветки обработки -- все это для меня чистой воды программирование в стиле Виртовского Паскаля."

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

eao197 комментирует...

2Konstantine:
Зря вы так :)

Я предупреждал, что это передергивание ;) Но действительно, от мифов о волшебных свойствах ФП уже подташнивает.

С тем же успехом можно и ООП на структурное нятянуть:

Нет, не с тем же. Наследование плохо на структурное программирование ложится. И полиморфизм (что видно по исходникам OpenSSL и gSOAP). Да и инкапсуляция тоже (хотя модули Modula-2 и пакеты Ada здесь сильно в тему).

Сами же видите что и ФВП в вашу систему не укладываются.

Не особо. Указатели на функции были и в Паскале, и в С. И использовались похожим образом (C-шный qsort тому пример). Кроме того, ряд языков для структурного программирования (Паскаль, С, Ada) не имели сборки мусора. Поэтому ФВП там быть не могло по физическим, а не идеологическим причинам. Но в том же Oberon-е, AFAIK, локальные функции вполне себе используются как ФВП.

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

А пример можно увидеть?

Miroslav комментирует...

главный вопрос не "вы все еще кипятите?" а "как вы кипятите? вы же еще живы". Правда вопрос риторический.

По фп есть желание цинично подискутировать :) правда я не спец - у меня только пожелания есть :) По принципу - что от него надо чтобы мы ( EDA индустрия ) его начали юзать ( кроме массовых расстрелов ессно )

eao197 комментирует...

2Rubanets Myroslav: не знаю, что такое EDA индустрия, но могу предположить, что для массового использования ФП нужно максимально снизить порог вхождения. По сути -- интегрировать возможности ФП в мейнстримовые языки (что сейчас видно на примерах C# и D).

Имхо, все это оставляет языки типа OCaml и Haskell для маргиналов. Особенно OCaml. Haskell, наверное, будет областью исследований в области языков программирования.

Кстати, по поводу C#, Scala и D. Имхо, D в этом смысле самый интересный вариант, т.к. только в нем есть поддержка иммутабельности и константности для любых объектов. И даже C++ в этом смысле к ФП ближе, чем C# и Scala.

Miroslav комментирует...

2Евгений Охотников:
eda - electronic design automation. Софт для тех кто лепит микросхемы/чипы. Здесь по прежнему нужно контролировать размеры в битах основных типов данных. Я охотно верю что gc и прозрачные от машины типы рулят много где, но за лишние 8 байт в прямоугольнике ... То есть компилятор должен а)давать рулить памятью б)проверять чего нарулили. Нужны массивы (медицинский факт). И тп.
То есть почитав RWH я честно говоря особых проблем чтобы из H вырос язык пригодный для нас не вижу. Разве что авторы будут продолжать "avoid success at all cost" :) Могу развернуть наверное.

Miroslav комментирует...

ну и главное - за проверку и контроль сайд эффектов я готов отдать много чего. Долой клятый дебаггер, и даже IDE впридачу. У меня полосами дебаг чужого багла идет - и сейчас черная. А когда начальство проснется насчет "many core utilization" то придет полная и беспросветная ...

eao197 комментирует...

2Rubanets Myroslav:

Могу развернуть наверное.

С удовольствием почитал бы. И, думаю, не только я.

ну и главное - за проверку и контроль сайд эффектов я готов отдать много чего.

Эх, в очередной раз с грустью вспоминается долгострой под названием D...

Miroslav комментирует...

ну ди тут никаким боком - все сайд эффекты на месте. Скорее это будет помесь RapidMind'овского детища с хаскелем.
*)Смысл в том чтобы ругалось на невозможность оптимизировать инплейс изменение массива изза наличия другой ссылки на него и тп.
*)Чтобы нельзя было промазать мимо массива индексом ибо чтобы заткнуть компилер во всех путях исполнения должна быть гарантия.
*)Чтобы нельзя было плодить уродцев с вагонами постоянно меняющегося состояния. Чтобы Степановские регулярные функции были по дефолту и транзитивно проверялась регулярность через весть проект. Чтобы нерегулярные нужно было писать специально и с трудом :). Убрать глобальные вообще.
Вот тогда на вопрос "вы еще кипятите ?" будет простой ответ

eao197 комментирует...

*)Чтобы нельзя было промазать мимо массива индексом ибо чтобы заткнуть компилер во всех путях исполнения должна быть гарантия.

А разве это в общем случае решается? Разве что заведением типа индекса, как в Паскале:

type MyArrayDim: 1..25;
float my_array[ MyArrayDim ];
for( MyArrayDim i = 1; ... ) {
my_array[ i ] = ...;
}

Но и здесь, я думаю, будут какие-то runtime-проверки при изменения значения i.

*)Чтобы нельзя было плодить уродцев с вагонами постоянно меняющегося состояния. Чтобы Степановские регулярные функции были по дефолту и транзитивно проверялась регулярность через весть проект.

Как раз D к этому ближе всяких C++/C#/Java/Scala подобрался. В D2 появились immutable-данные и pure-функции. И все это в привычной для C++ника упаковке.

Konstantin комментирует...

Но действительно, от мифов о волшебных свойствах ФП уже подташнивает.

Немного странно что вы основываетесь не на собственном опыте применения ФП, а на том что опоненты были не достаточно убедительны.

Не так давно вы восхищались VoidSafety в Эйфеле. А алегбраические типы которые дают тоже самое и много другое - это для вас виртовский Паскаль. Как-то не логично не находите?

Наследование плохо на структурное программирование ложится. И полиморфизм (что видно по исходникам OpenSSL и gSOAP). Да и инкапсуляция тоже...

Замечательно наследование ложится. Особенно наследование интефейсов. Всего-то надо в класс-структуру добавить указатели на функции.
Вся существующая инкапсуляция - всего лишь вариации на тему opaque pointer.

Указатели на функции были и в Паскале, и в С.
С ФВП можно делать композицию, карринг и другие классные штуки. Сможете вы такое сделать с указателями?

А пример можно увидеть?

--
data Base = Derived1 | Derived2

foo Derived1 = "Derived1"

foo Derived2 = "Derived2"

createSomeBase::() -> Base

main = putStrLn (foo createSomeBase)
--
Вызывается тот или иной вариант foo в зависимости от типа переданного параметра.

eao197 комментирует...

2Konstantin:

Немного странно что вы основываетесь не на собственном опыте применения ФП, а на том что опоненты были не достаточно убедительны.

Мой собственный опыт основан на изучении OCaml и Scala пару лет назад. В результате которого стало понятно, что на моих текущих задачах ни один из этих языков достойной заменой C++у не станет. Может я, конечно, не прозрел, но каких-то фундаментальных преимуществ ФП перед ООП я не увидел.

Что до оппонентов, то вот пример. Очень много в разговорах об ФП говорится о хорошей поддержке распараллеливания. Но где она? Автоматического распаралленивания нет, в программе нужно самостоятельно выделять места и для параллельной обработки и использовать соответствующие структуры данных. Все то же самое получается и не в ФП подходах.

Не так давно вы восхищались VoidSafety в Эйфеле.

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

Указатели на функции были и в Паскале, и в С.
С ФВП можно делать композицию, карринг и другие классные штуки. Сможете вы такое сделать с указателями?


При наличии GC и обобщенного программирования в языке -- не вижу проблем. В том же D это возможно, хотя он и не ФП язык.

Вызывается тот или иной вариант foo в зависимости от типа переданного параметра.

И если со временем нам нужно будет добавить Derived3, то нам придется перекомпилировать места обращения к foo.