Привет!

Конференция FPConf уже в эту субботу, нас аж 160 и еще не поздно заскочить в последний вагон. Регистрация — тут.

А накануне мы решили задать нашим спикерам один довольно неоднозначный вопрос. Публикуем ответы, ваши варианты в комментах люто приветствуются!

image

В объектно-ориентированных языках есть широко известный список паттернов проектирования (design patterns) от «Банды четырех» (Gang of Four). В функциональных языках такого известного списка не существует. С вашей точки зрения, почему так?
Подобные паттерны не нужны при программировании на функциональных языках или просто их канонический список еще не сложился?




Антон Холомьев, Haskell
Для Haskell такой список есть, но он по-другому называется и там другие паттерны,
в стиле ФП. Тут


Сергей Лобин, Scala
На мой взгляд, паттерны — это принципы FP, которыми можно пользоваться как кубиками, собирая прекрасные программы :)


Сергей Тихон, F#
В вопросе паттернов для языка программирования я солидарен с Peter Norvig который сказал что «Design patterns are bug reports against your programming language.». Большая часть проблем которые решали классические паттерны от Gang of Four уже решена в функциональных языках в том или ином виде. “Функциональные паттерны\абстракции” есть и будут появляться (например Монады), как набор общих подходов для управления сложностью разрабатываемых приложений. Возможно со временем появится один общий список который мы все признаем как канонический, но это будет вызвано заметный увеличение сложности задач которые позволят решать функциональные языки.


Александр Гранин, Haskell
Вопрос очень коррелирует с темой моего доклада. И я его несколько раз поднимал на наших встречах LambdaNsk.

Паттерны проектирования в ООП — это искусственные конструкции, решающие ту или иную техническую проблему, которую нельзя решить естественным путем — синтаксисом языка, верхнеуровневыми идиомами или концепциями. Значительная часть паттернов (если не все) оперирует ООП-понятиями «наследование», «полиморфизм», «абстракция», в то время как ни эти понятия, ни какая иная конкретная синтаксическая конструкция ООП-языка не решают проблему напрямую. В то же время в функциональных языках эти паттерны либо вырождаются (Visitor), либо становятся просто не нужны (Command) — в виду того, что в самом ФП есть концепции, которые непосредственно могут решить проблему: ФВП, лямбды, сопоставление с образцом, первоклассные функции, иммутабельность, композиция, ленивость и многое другое. Например, Visitor легко заменяется сопоставлением с образцом и ФВП, а Command — просто первоклассными функциями. В ООП же паттерны — это сложные конструкции, не присущие самому ООП-языку. То есть, чтобы решить проблему, ООП-языка и его элементов недостаточно: нужно составить из этих элементов тот или иной механизм. А много похожих механизмов в итоге и обобщаются в ООП-паттерны. Напротив, чтобы в ФП решить проблему, достаточно только языковых конструкций, и зачастую хватает просто составить тип функции. Если есть тип — реализация функции будет уже простой.

В то же время, в ФП существуют свои «паттерны», хотя я предпочитаю называть их «идиомами». Вы о них слышали: разные монады, комонады, функторы, стрелки, аппликативные функторы, комбинаторы, трансдьюсеры. Кроме того, в ФП есть такие паттерны как FRP, STM, линзы. В чем, по моему мнению, отличие ООП-паттернов от ФП-идиом?

ООП-паттерн — это решение проблемы «снаружи» императивным подходом. ООП-паттерн адресует к сущностям и их мутабельному взаимодействию. ООП-паттерн описывает, «как работает» система. Бывают ООП-паттерны, которые нужны, чтобы лишь реализовать недостающую в языке функциональную идиому (Visitor — это сопоставление с образцом и ФВП).

ФП-идиома — это решение проблемы «изнутри» функционально-декларативным подходом. ФП-идиома адресует к свойствам и их немутабельной трансформации; ФП-идиома описывает, «чем является» сущность, какие у нее есть неотделимые свойства.

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

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

Ссылки по теме
* Заменяет ли ФП GOF-паттерны?
* Паттерны проектирования в Haskell
* Паттерны проектирования в ФП
* Идиоматичный функциональный код (презентация с моего выступления на LambdaNsk)
* Идиомы в Haskell и ФП: 1 и 2

Денис Редозубов, Haskell
+1 к предыдущему ответу.


Николай Рыжиков, Clojure
Не все паттерны GOF одинаковы. Некоторые слишком низкоуровневые — например, Iterator. В ФП есть свой (более выразительный) арсенал для подобных задач: map, reduce/fold, walker. А также ряд сугубо функциональных низкоуровневых шаблонов: monads, zippers, transducers.

Однако, значительная часть шаблонов — это полезные и понятные опытным программистам высокоуровневые конструкции, позволяющие структурировать и описывать программу in the large. И они вполне могут использоваться в ФП, иногда с несколько отличной от оригинала реализацией.

Например, Chain of Responsibility может быть выражен через HOF как декорация функций (используем js для доступности:):

function handler1(next_handler) {
  return function(args){
    if (exp){    // some logic
     next_handler(args); //pipe to next handler
    } else {
     return some_responce; //intercept
    }
  }
}

var stack = handler1(handler2(handler3)) // build chain (stack)
stack(args); // process chain


Поэтому думаю надо просто перевыпустить Шаблоны Проектирования Для ФП. Возможно, вы тот, кто напишет эту книгу :)


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

Поэтому я бы не стал заходить так далеко, чтобы приписывать ФП-языкам некие особые свойства, аннулирующие паттерны. Паттерн — это просто классификатор часто встречающихся «форм кода». Конкретные ООП-паттерны из GoF не нужны, потому что нет объектов. Но ФП-паттерны нужны. Как-то ведь ФП код пишут, и он не то чтобы у каждого человека уникальный.

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

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


Михаил Лиманский, Scala
Функциональщикам паттерны не нужны, потому что мы люди творческие, а не ремесленники.

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

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


Арсений Жижелев, Scala
Банда четырёх «застолбила» список паттернов, существовавших на тот момент, и казавшихся универсальными и широко применимыми. Со временем появились новые паттерны, а в старых обнаружились изъяны (например, dependency injection пришёл на смену factory).

Как уже сказали выше, в функциональных языках тоже есть наборы хороших практик, только ни один из наборов ещё не стал общеизвестным и популярным. Это открывает возможность включения в такой список новых подходов и библиотек их реализующих. Об одном из интересных способов использования функциональных языков пойдёт речь в моём докладе про библиотеку SynapseGrid, реализующую подход Functional Reactive Programming на Scala.

UPD. Утром получили ответ от Эдварда и решили опубликовать его отдельным постом

Апдейты от остальных участников и спикеров конференции — в комментариях. До встречи на FPConf!

Комментарии (10)


  1. erlyvideo
    13.08.2015 22:43
    +1

    как-то дискуссия разделилась на приватную в гмыле и публичную тут


    1. elena_voronina
      13.08.2015 23:21
      +1

      Ты, кстати, еще не ответил. Ни в мыле ни тут :)


  1. HDDimon
    13.08.2015 23:27
    +1

    а можно подробнее про приватную?


    1. elena_voronina
      13.08.2015 23:36
      +1

      :) Да, давайте я сюда перекидаю реплики.


      Александр Гранин
      Николай, и вам спасибо, я вас услышал. И правда у меня такие мысли вертятся, и я к тому иду. Скажем, серию статей «Дизайн и архитектура в ФП» я буду продолжать писать с прицелом на книгу. Вероятно, вернусь к статьям в сентябре.

      Но вообще, эту книгу можно писать коллективно, при том что материала в сети уже очень, очень много. Там и про функциональные паттерны в Scala, и про паттерны JS, и про Haskell — так или иначе говорилось. Пришло время систематизировать информацию.

      Вот, например, книга "Scala Design Patterns: Patterns for Practical Reuse and Design". Уважаемые скалисты, если вы читали, — о чем там? Я не читал еще.
      Есть и статьи, вот: Design Patterns in Scala.

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

      Антон Холомьев
      Некоторые из паттернов переосмысливаются в контексте ФП.
      Как например Observer. В ФП предлагается вместо него использовать ФРП (реактивное программирование).
      Подробнее можно почитать в статье: Deprecating the Observer Pattern with Scala.React.


  1. elena_voronina
    14.08.2015 09:35

    Получили ответ от Эдварда и опубликовали отдельным постом — тут.


  1. FiresShadow
    14.08.2015 09:52
    +1

    Паттерны проектирования подсказывают, как можно удачно сгруппировать данные и функции, чтобы код был более читаемым, модифицируемым и удобным в использовании. Например, паттерн NULL-object заключается в группировке вместе методов, реализующих поведение сущности, когда основополагающий признак сущности не определён. Flyweight рассказывает, как сохранить качество кода при оптимизации расхода памяти.
    Необходимость в паттернах проектирования возникла, потому что люди, чётко понимающие принцип ООП, затруднялись эффективно применять его на практике. Часто в одном процессе участвуют несколько объектов, и непонятно, в какой класс следует поместить метод. И тут на помощь приходит медиатор, инкапсулирующий внутри себя взаимодействие группы объектов в рамках какого-то процесса.
    В ФП основная единица — функция, и тут смысл проектирования сводится к грамотному распределению кода по функциям. И если поиск сходств и различий назначений методов в ООП (для грамотного их распределения по классам) вызывает сложности даже у опытных программистов, то дробление функции на несколько более простых вызывает сложности лишь у новичков. Вполне возможно, что большинство людей, кристально ясно понимающих принцип ФП, не будут периодически сталкиваться с проблемами реализации принципа ФП на практике по причине сложности самого принципа. Соответственно, не будет сложностей при реализации — не будет и паттернов. Однако будут сложности или нет — это как гадание на кофейной гуще. Сейчас ФП не получило широкого распространения среди решений сложных промышленных задач, поэтому рано делать какие-то выводы.


  1. mynameisdaniil
    14.08.2015 10:19
    +1

    Оффтопик
    Очень жаль, что первоначальное обьявление прошло незамеченным. С удовольствием бы присоединился, если бы узнал раньше. Надеюсь, это не последняя конференция и в следующий раз будет чуть больше напоминаний и обьявлений. Возможно, так же стоило попросить своих спикеров разместить обьявления (хотя бы и у себя в жж). Если бы об этом написал Максим или Лев то, наверняка, я бы заметил.


    1. erlyvideo
      14.08.2015 10:59
      +1

      Да, вы правы, стоило побольше написать.


  1. FiresShadow
    14.08.2015 12:26
    +2

    «Design patterns are bug reports against your programming language.». Большая часть проблем которые решали классические паттерны от Gang of Four уже решена в функциональных языках в том или ином виде.
    Как сказал один человек, ООП — это уменьшение сложности предметной области через декомпозицию её на абстракции. ФП — это уменьшение сложности предметной области через композицию (выделение) однотипных операций. В моём понимании, решение проблемы — это когда достижение цели не вызывает сложностей. Ваша точка зрения заключается в «нет декомпозиции на абстракции — нет проблем, связанных с декомпозицией на абстракции», или, иными словами — «нет действия — нет проблем». Эта точка зрения может быть верна лишь если мы добиваемся тех же целей другими действиями. Однако не факт, что композиция однотипных операций всегда и везде борется со сложностью столь же эффективно, как и разбиение на абстракции, имеющие состояние.


    1. zw0rk
      14.08.2015 14:13
      +3

      С маленькой поправкой:

      «ФП — это уменьшение сложности предметной области через композицию (выделение) однотипных операций над выделенными абстракциями»