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

«Паттерны» функционального программирования

Время на прочтение10 мин
Количество просмотров72K
Всего голосов 61: ↑56 и ↓5+51
Комментарии361

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

Это все звучит очень круто. Но вам не кажется, что за стремлением к высокому искусству несколько теряется смысл программирования?


Когда-то изобрели процедурный подход. Жить стало круче, стали плодить процедуры из всего, чего можно, где можно было обойтись прямым кодом: вместо a = a + 2 стали писать Add(a, 2). Что улучшает читаемость и поддерживаемость кода.


Потом стали работать с ООП, и все завертелось ещё круче — все должно быть объектами! Теперь мы можем писать a.Add(2), что ещё лучше улучшает читаемость и поддерживаемость!


Но это мало, далее пошла мода на интерфейсы. Стоит ведь предусмотреть, что 2 — это не совсем два, а добавить — это не всегда сложить. Реализуем интерфейс IAdd! Что улучшит читаемость и поддерживаемость кода, само собой, а также сделает его ну ОЧЕНЬ гибким.


Тут уже наворотили столько, что чтобы не изобретать велосипед, улучшить читаемость и поддерживаемость, надо воспользоваться соответствующим паттерном — желательно с древнеримским названием типа MVXXML. Каждому интерфейсу по контроллеру, каждому контроллеру по интерфейсу! Теперь код мало того, что необходимо поддерживать — это ещё должна делать толпа джуниоров, пара сениоров и главный архитектор.


Но это мало. Это, знаете ли, не Bleeding Edge! Заверните, пожалуйста, в функциональное программирование, нарежьте монадами по 100гр кусочек и подайте под мелко нашинкованными лямбдами. Выглядит вкусно? Что, добавить 2 к переменной a? Это прошлый век! Как вы можете оскорблять высокую кухню самой постановкой такой задачи!


Идите к этим, как их, низкоуровневым! Пусть выдадут вам
add ax, 2! А у нас — искусство!

ООП не о том, что мы прибавляем 2, а о том, зачем мы это делаем. (Поэтому никакого .add там не будет).

Там будет CQRS и Add Action
НЛО прилетело и опубликовало эту надпись здесь

Ну вот вы шутите, а многие за чистую монету такое принимают. И даже так пишут :-)


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


Или еще можно сказать, что инструментами ООП удобно описывать "что надо сделать", а инструментами ФП — "как мы это делаем".

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


А вот "как" описывается, в основном, структурным программированием, которое не относится ни к ООП, ни к ФП.

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


Императивный подход:


    IEnumerable<int> SomeMethod(IEnumerable<Foo> foos)
    {
        foreach (var foo in foos)
        {
            if (foo.x > 42)
            {
                foreach (var bar in foo.Bars)
                {
                    yield return foo.y + bar.z;
                }
            }
        }
    }

Декларативный подход:


    IEnumerable<int> SomeMethod(IEnumerable<Foo> foos) =>
        from foo in foos
        where foo.x > 42
        from bar in foo.Bars
        select foo.y + bar.z;

Эквивалентный код:


    IEnumerable<int> SomeMethod(IEnumerable<Foo> foos) => foos
        .Where(foo => foo.x > 42)
        .SelectMany(foo => foo.Bars.Select(bar => foo.y + bar.z));

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

А, ну это смотря что считать ООП.


Я вот не считаю, что если я написал class ImmutableCollection<T> с методами map и filter, то это ООП. :-)

Если это простой класс не привязанный ни к какой иерархии — то тут конечно же от ООП будет только обертка. А вот если ваш ImmutableCollection реализует хотя бы интерфейс IEnumerable (C#) или Iterable (Java) — то это уже типичный механизм ООП.

С чего бы это наличие интерфейса свидетельствовало об ООП? Интерфейс — это просто описание набора операций, доступных для данного типа.

С того, что тут задействуются два кита ООП — наследование и полиморфизм (ну, второе еще не задействуется — но я все же предполагаю что написанный код кто-то использует).


Кроме того, тут используется паттерн из мира ООП ("итератор"), который запрещен в мире функционального программирования, поскольку построен вокруг изменяемого состояния — так что как только вы его реализовали, ваш код перестает быть чистым функциональным.

> Кроме того, тут используется паттерн из мира ООП («итератор»)

Это как вы определили, что там используется итератор? Из кода это никак не следует.

> С того, что тут задействуются два кита ООП — наследование и полиморфизм

Интерфейсы не наследуются, а реализуются. И если интерфейс в стиле тайпклассов хаскеля, не предполагает полиморфизма подтипов (по крайней мере вне костылей из existential types)? Будет уже не ООП?
Это как вы определили, что там используется итератор? Из кода это никак не следует.

Следует. Интерфейсы IEnumerable и Iterable — это стандартные реализации паттерна "итератор".


Интерфейсы не наследуются, а реализуются.

Это всего лишь особенность конкретного языка.


И если интерфейс в стиле тайпклассов хаскеля, не предполагает полиморфизма подтипов (по крайней мере вне костылей из existential types)? Будет уже не ООП?

Мы все еще говорим о C# и Java или обсуждаем какой-то неопределенный язык программирования?


Разумеется, в том же Хаскеле классы не являются признаком ООП по построению. Но Хаскель — чистый функциональный язык, а я говорю про мультипарадигменный C#.

> Следует. Интерфейсы IEnumerable и Iterable — это стандартные реализации паттерна «итератор».

То есть если я поменяю IEnumerable на List, код чудесным образом перестанет быть ООП?

> Это всего лишь особенность конкретного языка.

Это особенность интерфейсов. Интерфейсы нельзя наследовать, потому что интерфейс — это контракт. Что значит «наследовать контракт»?

> Мы все еще говорим о C# и Java или обсуждаем какой-то неопределенный язык программирования?

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

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

Когда придумывались классические "три кита" ООП, интерфейсов/контрактов не было, отсюда и некоторое несоответствие в определениях. Технически интерфейс мало отличается от абстрактного класса без полей.


Применительно к интерфейсам в C#, при реализации интерфейса в некотором смысле наследуются все методы-расширения, определенные для этого интерфейса. Например, любой класс, реализующий IEnumerable<T>, автоматически получает методы-расширения Where, Select, SelectMany, Join и прочие.




То есть если я поменяю IEnumerable на List, код чудесным образом перестанет быть ООП?

Мои ответы надо рассматривать в контексте тех комментариев, которые я писал ранее и тех вопросов, на которые я отвечал.


Напомню, началась эта ветка с моего ответа на вот этот комментарий:


Я вот не считаю, что если я написал class ImmutableCollection<T> с методами map и filter, то это ООП.

Если вы считаете что слово class автоматически означает ООП — то лучше расскажите об этом symbix, а не мне. Если вы так не считаете — то я не понимаю какой тезис вы пытаетесь отстоять в этом споре.

> Применительно к интерфейсам в C#, при реализации интерфейса в некотором смысле наследуются все методы-расширения, определенные для этого интерфейса. Например, любой класс, реализующий IEnumerable, автоматически получает методы-расширения Where, Select, SelectMany, Join и прочие.

И? Это вы к чему? Как это меняет тот факт, что интерфейс — это контракт?

> Если вы так не считаете — то я не понимаю какой тезис вы пытаетесь отстоять в этом споре.

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

Вы опять путаете общее понятие и элемент языка.


Интерфейс как элемент языка очень похож на абстрактный класс без полей.

> Интерфейс как элемент языка очень похож на абстрактный класс без полей.

Давайте по порядку. Есть интерфейсы, интерфейсы — это контракты (абстрактные классы, конечно же, тоже). Идея накладывать контракты на программные сущности — не является чем-то специфичным для ООП. Это моя точка зрения. Можете свою полностью сформулировать, потому что мне совершенно непонятно, к чему вы ведете.
Моя точка зрения в том, что реализация интерфейса классом в языке C# с точки зрения парадигмы ООП является частным случаем наследования как общего понятия, а потому код на C#, который использует реализацию нетривиального интерфейса классом, можно считать написанным в парадигме ООП (не исключая возможность присутствия и других парадигм).
Мне кажется, что в рамках подобной логики абсолютно любой код на языке, поддерживающем ООП, будет считаться написанным в ООП-стиле. По крайней мере, мне не удалось сейчас придумать какой-то контр-пример.
«Подобной логики» — это какой?

Я видел программы на C#, в которых все методы были статические и находились в классе Program, а остальные классы были без конструкторов и с публичными полями. Это было в чистом виде структурное программирование на C#.

Но если не брать такие крайние случаи — то да, почти любая программа на C# использует парадигму ООП. В этом нет ничего удивительного — все же это основная парадигма языка.
> Я видел программы на C#, в которых все методы были статические и находились в классе Program

Но ведь _класс_, да еще и статический — это со всей очевидностью ООП-шная конструкция. Как и методы. Ровно в той же степени, как интерфейсы или абстрактные классы. Разве из этого не следует сразу сделать вывод, что это код в ООП-стиле? И не-ООП код, получается, на c# вообще не написать?
Пожалуйста, перечитайте мои комментарии еще раз. Там есть ответ на ваш вопрос. А спорить ради спора я не собираюсь.
Не понимаю, зачем тут спорить. Всё зависит от использования интерфейса.

Когда мы приводим объект к типу `IEnumerable`, например, `void Foo(IEnumerable obj)`, то интерфейс неотличим от абстрактного класса.

Когда же мы накладываем ограничение на тип, например `void Foo(T obj) where T: IEnumerable`, интерфейс является контрактом.

Всё остальное типа особенности множественного наследования здесь не имеет значения.
Что значит «наследовать контракт»?

Можно сделать более специфичный контракт который унаследует поддерживаемые операции у другого контракта и добавит свои.


Кстати, каждому классу концептуально соотвествует свой наиболее специфичный контракт. Типа "Поток-в-памяти это такой поток вообще, который обязуется еще и хранить то, что в него запихали в ОЗУ и предоставлять потом по требованию"

НЛО прилетело и опубликовало эту надпись здесь
> И почему existentials сразу костыли?

Костыли — в качестве эмуляции подтипирования. Потому что подтипирование в хаскеле считается ненужным.

> Что вы называете подтипами?

Не совсем понимаю вопроса. То же, что и все? Есть системы типов с подтипированием (например, lambda<:), у них есть семантика.
НЛО прилетело и опубликовало эту надпись здесь
> Но как на хаскеле existentials помогают эмулировать подтипы?

data IShow = forall a. (Show a) => IShow a

тогда любой инстанс IShow ведет себя так же, как в ООП ведет себя класс, реализующий интерфейс IShow. Отличие только в наличии обертки, но на самом деле чисто с формальной точки зрения запись data IShow = forall a. (Show a) => a тоже валидна, просто в хаскеле так нельзя (вроде бы нельзя, по крайней мере, без каких-то хитрых расширений).
> тогда любой инстанс IShow

Любой инстанс Show, конечно же.
НЛО прилетело и опубликовало эту надпись здесь
> объект, про который вы только знаете, что вы его можете отображать, и всё.

Но ведь сабтайпинг именно так и работает: «перед нами объект, про который мы можем сказать, что мы с ним можем делать все, что и с объектом, подтипом которого он является». В данном случае любой инстанс Show ведет себя как подтип IShow (если без боксинга).
НЛО прилетело и опубликовало эту надпись здесь
> Да и чем тогда это отличается от a в функции foo :: Show a => a -> Smth?

Тем, что foo можно применять только к одному конкретному типу a, а foo :: IShow => smth к любому «подтипу» IShow (т.к. а там нет).

> Но вы с ним не можете делать ничего другого.

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

Ну уж нет. Итератор в ООП — это общий паттерн для организации обхода произвольных коллекций, а head/tail-список — это конкретная реализация.


Аналогом паттерна "Итератор" в Хаскеле можно назвать классы Traversable и Foldable, но никак не список.

НЛО прилетело и опубликовало эту надпись здесь

Но первична-то именно операция foldr, а toList выражается через нее.

НЛО прилетело и опубликовало эту надпись здесь

… вот мы и пришли к вопросу "что же такое ООП".

«ООП — это миф, или как на обманывают тыжпрограмисты», смотрите на РЕНТВ сегодня в 26.00.

Я считаю, что опечатку "как на обманывают" надо расшифровывать "как, нах, обманывают".

Ну, от того, что реализован интерфейс, тоже пока еще ничего не случится. ООП появится там, где будет someMethod(Iterable foo). :-)

НЛО прилетело и опубликовало эту надпись здесь

Это не алгоритмы. Алгоритм — это по определению последовательность действий, что для функциональных языков едва ли не запретное слово :-)


Функциональное выражение алгоритма на деревьях — это скорее схема такого алгоритма чем сам алгоритм.

НЛО прилетело и опубликовало эту надпись здесь

Ничем не хуже. Просто take 5 . sortBy (comparing fst) — декларативное выражение того что мы хотим получить в результате, а приведенный вами фрагмент кода на C++ — императивный алгоритм получения.

НЛО прилетело и опубликовало эту надпись здесь

Код перестает быть императивным когда из него пропадают явные указания на промежуточные состояния процесса решения задачи.


Так, в вашем примере после исполнения std::sort контейнер vec оказывается в состоянии "отсортирован, но может содержать более 5 элементов". Это промежуточное состояние явно требуется кодом — но оно не требуется постановкой задачи и никоим образом из нее не следует!


В то же время в варианте take 5 . sortBy (comparing fst) состояние "отсортирован, но может содержать более 5 элементов" имеет не входной список и не выходной — а некоторый промежуточный, не имеющий даже имени. Он полностью скрыт реализацией.


Тем не менее, если сделать библиотеку на плюсах и написать что-то типа x = take(5) | sortBy(comparing(fst)) | x или x.transform(take(5) | sortBy(comparing(fst))) — то эта команда, хоть и написана в декларативном стиле, скорее всего будет являться частью какого-то императивного алгоритма.


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

НЛО прилетело и опубликовало эту надпись здесь
> то это станет императивным кодом?

Не станет, потому что у вас никакого промежуточного состояния нету. f x where x = y — это просто (x => f(x))(y)
PS Последовательность — это не полный порядок. К примеру, на множестве вещественных чисел полный порядок задан — но последовательности они не образуют.

А параллельный алгоритм обычно можно рассматривать как несколько таких последовательностей.

Вы, наверное, имеете в виду линейный порядок.

Да, точно. Наверное, 0xd34df00d тоже имел в виду именно его.
НЛО прилетело и опубликовало эту надпись здесь

Ну, да, наверное, скорее "выражение алгоритма". Я несколько неудачно выразился, а 0xd34df00d правильно понял, что я имел ввиду.

Я, кстати, так уже даже делал для задачи генерации кода обработки изображений в рантайме.
Позвольте не согласиться. Мне кажется, что как раз этот доклад — максимально про практичность, а не про «высокое искусство» и иже с ним. Потому что на каждую красивую конструкцию здесь приводится вполне конкретная и понятная проблема, которую эта конструкция отлично решает.
Ну и, разумеется, стоит понимать, что разнообразные функции «add» — это всего лишь максимально простой пример для демонстрации паттерна, а не реальный юзкейс. Разумный программист и без того понимает, что забивать гвозди микроскопом — не лучшая идея. А неразумный найдет способ сделать чушь и без функционального программирования.

Ага, особенно "практичен" вот этот кусок кода:


let printList anAction aList =
   for i in aList do
       anAction i

Был обычный алгоритм, который делал вполне конкретную вещь. Из него вынесли наружу всю конкретику, оставив тривиальный код. Но ведь [1..10] и printLn на самом деле никуда не делись! Они просто перешли к вызывающему коду, теперь каждый кто вызывает printList должен указывать еще и эти [1..10] и printLn.


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


Напомнило https://xkcd.com/1790/


Что самое веселое, даже после такого преобразования код все еще не абстрагирован от всего. К примеру, для асинхронных операций код будет работать некорректно — запустит их параллельно вместо последовательного выполнения. О ужас! Нам срочно нужна операция foldM!


PS в целом пост мне понравился, но конкретно этот пример вызвал возмущение своей бессмысленностью.

В целом согласен: мне тоже неприятно, когда основы ФП объясняют на какой-то тривиальщине типа вычисления факториалов, когда выигрыша от абстрагирования не видно.

Кстати, называть функцию `printList` после такого преобразования уже некорректно, правильнее `visitList`. А `printList` — результат частичного применения `visitList` с параметром `printLn`.
Они просто перешли к вызывающему коду, теперь каждый кто вызывает printList должен указывать еще и эти [1..10] и printLn.

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


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

Функция скрывает, как именно происходит перебор — например значение i уже наружи не видно и не надо о нем заботиться


К примеру, для асинхронных операций код будет работать некорректно

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

Функция скрывает, как именно происходит перебор — например значение i уже наружи не видно и не надо о нем заботиться

Но раньше-то она скрывала список и действия!


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

Хорошо, что ошибочный код не скомпилируется. Но плохо что он при этом не будет работать.

Но раньше-то она скрывала список и действия!

Это уже не она. Никто не мешает оставить старую, просто абстрагировать перебор оттуда


let printList = List.iter printItem

Хорошо, что ошибочный код не скомпилируется. Но плохо что он при этом не будет работать.

Если надо последовательно объединить асинхронные функции просто воспользуйтесь другой функцией

Никто не мешает оставить старую, просто абстрагировать перебор оттуда

Автору статьи тоже не помешало бы так сделать :-)


Если надо последовательно объединить асинхронные функции просто воспользуйтесь другой функцией

Но зачем в таком случае абстракция над перебором списка, если она не позволяет нам повторно использовать код?

Но зачем в таком случае абстракция над перебором списка, если она не позволяет нам повторно использовать код?

Она нам позволяет повторно использовать код. Но не весь. Какого поведения вы хотите от нее? Чтобы выполняла параллельно? Чтобы ждала выполнения каждого шага? Это и надо специфицировать. То есть передавать ей функцию, которая не возвращает task, а что-то делает.

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

Точно такой же бред можно сочинить и в обратную сторону:
— Функции высшего порядка / Класс
— Каррирование / Класс
— Композиция функций / Класс
— Предикат / Класс
— Функтор / Класс
— Лифт / Класс
— Моноид / Класс
— Монада / Класс

И так далее из списка "жаргона ФП"

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

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

Ну вот даже это что — «просто функция»? Или, все-таки, «паттерн»?
что делать с пустыми списками? Что делать, если у нас нечетное количество элементов? Правильно, добавить в список нейтральный элемент

И да, на счет КДПВ. Я, конечно, могу ошибаться, но, насколько я знаю, в ФП, Лисп — это просто баловство вроде JS в сравнении с Haskell, потому мне непонятно, почему он улетел так высоко.
Ношение красивой, но не тёплой шапки в мороз повышает вероятность дополнительного финансирования, на утеплитель. Это знают все теоретики красивых стилей, но не говорят о конечной цели: ).
Этот доклад раздражает не только вас. Подобным образом отреагировал и Дядя Боб. Кстати, его пост-ответ тоже заслуживает перевода.
Добавлю еще про манипуляции в статье:
А вот этот код но уже с обработкой ошибок:image


На самом деле, данный код совсем не соответствует семантике приведенного в пример «ужасного ООП с проверкой ошибок».

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

ФП вариант с проверкой ошибок соответствует ООП варианту без проверки, где каждый метод может кинуть RuntimeException в случае ошибки.
ФП вариант с проверкой ошибок соответствует ООП варианту без проверки, где каждый метод может кинуть RuntimeException в случае ошибки.

Здесь я с вами согласен, но только для однопоточного кода. С TPL вариант с exception'ами уже не такой хороший. Кроме этого, нужно гарантировать, что в программе только один catch, иначе высок риск «проглатывания» ошибок.
TPL
Это уже смесь фп и ооп подходов и является нетрадиционным. Потому этот кейс не стоит брать во внимание – там другие правила.

Кроме этого, нужно гарантировать, что в программе только один catch, иначе высок риск «проглатывания» ошибок.
Не обязательно.

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

Это все тот же ".catch()" в промисах, но в промисах он менее гибкий.
Потому этот кейс не стоит брать во внимание – там другие правила

Если вас кейс на данный момент не слишком волнует, это не значит, что его не стоит брать во внимание. Вот был у вас однопоточный код, стал он выполняться двое суток. Надо параллелить, а у вас внутри везде throw. Весь код на помойку, переписываем на TPL с нуля? Весело, но дорого.
1. Мои предыдущие комменты в этом треде относятся конкретно к данной статье, а не к всевозможным особенностям реализации каких-то фреймворков.

2. Теперь конкретно ваш пример:
Вот был у вас однопоточный код, стал он выполняться двое суток. Надо параллелить, а у вас внутри везде throw.
Это самое плохое решение в данном случае.

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

Такие проблемы не появляются на ровном месте, а если вы с нею столкнулись, то у вас проблемы гораздо большего масштаба, чем «у нас в коде везде throw».
Такие проблемы не появляются на ровном месте, а если вы с нею столкнулись, то у вас проблемы гораздо большего масштаба, чем «у нас в коде везде throw».
Случай из жизни. Жили были программисты. Совместили они логику валидации и прасинга документов. И выбрасывали они исключения для валидации. И нормально все было, пока не стали присылать сводные xls-файлы на несколько десятков миллионов строк. И, нет-нет, нельзя клиенту объяснить, что обмен xls-файлами такого размера не самое оптимальное решение.

Ждать сутки для разбора этого файла последовательно — не вариант, тем более, что железо на проде позволяет и не все строки файлов связаны. Map / Reduce — прям то, что нужно. В итоге не контролируемые побочные эффекты (функции валидации, дергающие БД и выбрасывающие исключения) значительно затруднили мне тот самый Map / Reduce.

У Either есть сильные и слабые стороны. Вы продолжаете настаивать на том, что есть один православный способ на все случаи жизни, а все остальное к дело не относится и вообще другой случай. У автора пример тоже максимально простой, чтобы не пугать монадами и прочими эндофункторами. В ASP.NET MVC с обработкой ошибок действительно хорошо — можно обработать все декларативно. Но вы точно уверены, что у вас все исключения должны возвращать код 500? 401, 412, 422, не? Посмотрите доклад про rop целиком, прежде чем судить. Там есть много здравых мыслей.

А еще вместо Either можно взять аналог Validation

Интересно, а без хаскеля примера нет?
НЛО прилетело и опубликовало эту надпись здесь

Ммм, а должна? Это ж аппликатив, аккумулирующий все ошибки в полугруппе "слева"?

НЛО прилетело и опубликовало эту надпись здесь

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


Validation же — своего рода Either, но со сбором всех ошибок в полугруппу (причем удобно в NonEmptyArray) без доступа к значению при провале (так как его, значения, нет). Поэтому, например, в scala cats Validated описывается как способ параллельной валидации, а Either — последовательной.


Если вы про early return для map функтора, то там да, все ок — failure-ветка "закорачивается".


PS. Вы можете меня поправить, я пока разбираюсь со всем этим добром :)

НЛО прилетело и опубликовало эту надпись здесь
Без примеров кода сложно.

Примеры там на гитхабе есть


Как я могу вызвать bar, если foo мне вернула ошибку, и никакого b нет?

Никак, так как при попытке определить bind/flatMap ломается ap и перестает аггрегировать ошибки. Так что только через ap и с одинаковым типом значения. Но, вроде, можно считерить и ненадолго перегнать в Either. В cats даже есть хелпер withEither

В итоге не контролируемые побочные эффекты (функции валидации, дергающие БД и выбрасывающие исключения) значительно затруднили мне тот самый Map / Reduce.

Что-то мне кажется, что дерганье БД было намного большей проблемой чем исключения.

ИМХО так и было, из личного опыта буквально на днях, с учётом ограничений xls на 1е6 строк в листе, не оптимизированный код грузит полный лист в БД как есть примерно за минуту(а вот если построчно вставлять теже самые пол миллиона, то выходит только на вставку в одной транзакции около получаса), дальнейшая обработка инструментами Oracle это никак не часы, видать просто при разработке использовались тестовые наборы по 100 строк на файл, а потом поставили на prod и внедрили.

Да, знакомая ситуация. Но тут ведь другая проблема:
Спроектированная система была не предназначена для такого – это архитектурная проблема.

Решение архитектурных проблем – это всегда больно. Даже если бы у Вас не было бы throw, но была бы логика хождения в базу и изменения какого-то общего состояния. Вы бы мучались не меньше. Но в этом случае вы бы проклинали «Stateful». Но вы упускаете тот момент, что Вы переделывали Архитектуру уже работающего приложения.

Вы переделывали самолет в вертолет во время полета, а жалуетесь на то, что заклепки хуже болтов.
В данном случае проблема вообще в нарушении SRP изначально как мне кажется. Исключения и доступ к БД уже сверху наложились.

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

В данном случае есть «правильный» вариант (болты). Нужно было их сразу ставить. Была бы дополнительная сложность, кривая обучения, но я заметил, что по примеру джуниоры копипастят код разного качества примерно с одной эффективностью. Лучше учить копипастить сразу хороший:)

Я строго на стороне дяди Боба: ФП и ООП — инструменты, не заменяющие, а дополняющие друг-друга. Полезно знать и применять при необходимости приемы и той и другой.

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

Практическая применимость и tool-support таких концепций — отдельный вопрос. Я не считаю, что IO — супер-элегантное решение проблемы ввода-вывода в хаскеле: «посмотрите это выглядит как императивный код, но это не он»:)

А что не так в TPL с исключениями?

Вы правы, что сам по себе TPL не при чем. Просто в многопоточном / асинхронном коде проблемы обработки исключений (exception handling) становятся более очевидными, особенно то, что касается отслеживания exit points.

Вообще монады vs исключения vs коды возврата тема, мягко говоря, спорная:)

Хорошо, а в чем особенность отслеживания exit points в многопоточном коде?

синхронный код:
try
{
    var a = someMethodThrowingArgumentException(123);
    var b = someMethodThrowingInvalidOperationException(a);
    var c =  someMethodThrowingGenericException(b);
    return c.ToString();
}
catch(ArgumentException)
{
    retrun "ArgumentException happened";
}
catch(InvalidOperationException)
{
    retrun "InvalidOperationException happened";
}
catch(Exception)
{
    retrun "Ooops!";
}

С TPL:
someTask.ContinueWith(a => {
    try
    {
        someSyncMethod(a);
        someAsyncMethod(a).ContunueWith(b => {
            try
            {
                // ну и так далее
            }
            catch(InvalidOperaionException)
            {
                 // только для синхронного метода
                 return "InvalidOperationException happened";
            }
        })
    }
    catch(ArgumentException)
    {
         return "ArgumentException happend"
    }
})

Да, есть async/await. Но код с async/await только выглядит императивным, а компилируется в нечто другое, работающее с помощью SyncronizationContext и по духу ничем не отличается от монад. Т.е. в чисто-императивном коде без «примочек» придется писать try/catch в каждом ContinueWith. Код становится менее читаемым, exit points «размазываются» по разным callback'ам и их приходится «собирать». Основная проблема такая.
> Да, есть async/await. Но код с async/await только выглядит императивным, а компилируется в нечто другое, работающее с помощью SyncronizationContext и по духу ничем не отличается от монад.

Код с async/await ничем не отличается от кода с Task.ContunueWith, а просто явялется синтаксическим сахаром. SynchronizationContext используется и там, и там.
Т.е. в чисто-императивном коде без «примочек» придется писать try/catch в каждом ContinueWith.

А разве в момент получения результата всей цепочки (т.е. var t = a.ContinueWith().ContinueWith().ContinueWith(); t.Wait();) вы не получите AggregateException?

Пожалуй, нужно ответить более развернуто. Да, я согласен, что в TPL с обработкой ошибок все здорово. Гораздо лучше, чем при работе с Thread напрямую. Да, сильно сложнее с TPL не становится. Я говорю о том, что с TPL появляются дополнительные нюансы.

var t = a.ContinueWith().ContinueWith().ContinueWith(); t.Wait();

Этот код не имеет практического применения. Какой смысл использовать асинхронную модель, чтобы потом принудительно ее «синхронизировать» с помощью t.Wait()?

Я имел в виду следующие особенности:

  • Fire & Forget: не все разработчики одинаково полезны ответственно относятся к обработке исключений. Кто-то просто напишет так и ошибка будет потеряна.
    Task.Run(() => {
        // ...
        throw new Exception("please help me")
    }; // Fire & Forget
  • Про AggregateException.Flatten для TaskCreationOptions.AttachedToParent нужно знать, иначе можно потерять часть исключений.
    void Handle(AggregateException ex)
    {
        foreach (var exception in ex.Flatten().InnerExceptions)
        {
            Console.WriteLine(exception.Message);
        }
    }
  • Возможность обработать исключение с помощью TaskContinuationOptions.OnlyOnFaulted, а не try/catch — еще один exit point.
  • Можно потерять одно из исключений при использовании Task.WhenAll (да, они ССЗБ).
  • UnobservedTaskException — настраиваемое поведение
  • Wrapping / Unwrapping AggregateException при использовании await или t.Wait() — нужно внимательно следить, что мы ловим в try/catch.

Возможно, для кого-то эти аргументы покажутся несущественными. Для меня это дополнительная сложность с которой нужно работать.

Спасибо за комментарий. Пока отвечал понял, что зря вообще докопался до TPL. Мне больше не понравилась идея строить на исключениях control-flow на самом деле, а TPL как-то под «замес» попал:)
Этот код не имеет практического применения.

Не имеет. Это просто иллюстрация для того, чтобы было понятно, где именно вылетит AggregateException.


Я имел в виду следующие особенности:

Знаете, когда у вас код заведомо многопоточный (например, веб-приложение), это все такие мелочи… TPL это, по крайней мере, упрощает и нормализует.


Мне больше не понравилась идея строить на исключениях control-flow на самом деле,

А не надо строить control flow на исключениях, они не для этого.

> В ООП, каждая ошибка выдает какой-то свой результат, в ФП варианте – все ошибки просто игнорируются.

Это в maybe. Если в either, то не будут игнорироваться.
Там конкретный код взятый из статьи, который игнорирует ошибки. Никаких «если».

ps: можете привести пример правильного кода, который будет обрабатывать ошибки точно так же, как это делается в ООП варианте? Будет интересно посмотреть, на сколько он будет лучше смотреться.
> там конкретный код взятый из статьи

Там ведь код на картинке будет тот же самый. Просто в другой монаде (она там, вообще говоря, не указана, просто мы из контекста знаем, что автор говорил о Maybe).

Отлично, я в целом про это и говорил. Автор спекулирует кодом.
В ООП версии но ставил некрасивые обработчики, чтобы показать всю уродливость, хотя так никто не делает.
А в ФП версии кода, он даже не добавил никаких обработчиков, мол «вот смотрите как красиво!».

Если бы автор привел пример, где каждая ветка, которая может пойти не так выдавала бы свое сообщение об ошибке, это выглядело бы уже не так красочно, как он показывал ;-)
Ошибки там не игнорируются. Возвращается первая ошибка, дальше выполнение не идет. Druu верно отметил: есть разница между Maybe и Either. Кстати, Скотт не кисло глумится над тем, как обычно объясняют монады апологеты ФП и какое впечатление это производит.
К сожалению в репозитории я не нашел указанного метода.

Под «игнорируются» я имел ввиду, что в данном коде нету явных обработчиков ошибок. Они будут где-то снаружи (или внутри).

После ошибки в первой функции мы «перейдем на красные пути» и выйдем из функции с результатом, который говорит про ошибку – я прав?

И тут вопрос, который я изначально подымал: Кто обрабатывает эти ошибки?
Судя по коду есть 2 варианта:
1. ошибки обрабатываются внутри каждой функции, которую вызывает updateCustomerWithErrorHandling;
2. ошибка обрабатывается снаружи updateCustomerWithErrorHandling.

В обоих случаях – мы получаем несоответствие между двумя примерами кода.
НЛО прилетело и опубликовало эту надпись здесь
Вы по ссылке пройдите все-таки. Там ещё один полуторачасовой доклад, посвященны этой теме. С примерами и доскональным разбором как же эта магия работает. Продолжительность доклада «паттерны фп» — ещё полтора часа. Если тема будет востребована, я могу попробовать собраться с силами и перевести и его:)
Всё очень просто, вот вам полуторочасовой про то, как просто этим пользоваться, а потом ещё один доклад— ещё полтора часа.
НЛО прилетело и опубликовало эту надпись здесь
Зачем?
НЛО прилетело и опубликовало эту надпись здесь
> Если бы автор привел пример, где каждая ветка, которая может пойти не так выдавала бы свое сообщение об ошибке

Так эти ветки внутри функций за |>
Функции выдают ошибку. В коде с монадами нам не надо при этом создавать ветки в вызывающем коде, а в коде с ООП — нужно было бы.

Так, может быть, тогда и [кажущаяся] разница в красоте ООП- и ФП-решений связана исключительно с тем, в каком месте размещается проверка и возврат сообщения об ошибке?

Ну да, исключения по control-flow изоморфны maybe/either/etc или что там еще. Однако, вариант с монадами лучше исключений тем, что, ну, там нет исключений :)
Плюс — всегда можно упороться трансформерами и засовывать туда на call site дополнительную логику, не меняя сам код. Но я лично не ярый сторонник подобного.

Эдак можно и про вариант с исключениями сказать что он лучше монад тем что в нем нет монад :-)

Дык монады это не сущность, это просто то, как мы называем определенную конструкцию. А так там обычные ф-и — к которым монады полностью сводятся. И именно в том, что у монад под капотом обычные функции, которые ведут себя как обычные функции — и есть преимущество «монадического» решения. Не требуются специальные костыли рантайма, информация о наличии ошибок — содержится в типе, при наличии статической типизации — компилятор гарантирует что ошибка будет обработана, ну и поскольку это все — first-class citizen, то оно может быть допилено под конкретные нужды. С исключениями такое уже не прокатит.

Монада — это тип данных, то есть нечто существующее в рантайме. Чем же в таком случае отличаются монады и костыли рантайма? Почему раскрутка стека — костыль рантайма, а целый лишний тип данных — нет?


По поводу типизации — вон в Java есть checked exceptions. Все как вы написали — информация содержится в типе, компилятор гарантирует что исключение будет поймано. И даже под свои нужны исключения прекрасно допиливаются.

> Монада — это тип данных, то есть нечто существующее в рантайме. Чем же в таком случае отличаются монады и костыли рантайма?

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

> По поводу типизации — вон в Java есть checked exceptions.

И они по определенным причинам не взлетели.
И они по определенным причинам не взлетели.

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

> Тем не менее, если конкретно вам она нужна — возможность ее использовать есть.

Я могу написать ф-ю, которая принимает лямбду с заданными checked exceptions? Нет.

Проблема у checked exceptions не с тем, что они, с-но, checked exceptions, а с тем, что их хреново реализовали в джаве. Можно реализовать по-человечески — но штука в том, что в этом случае оно будет только названием отличаться от встроенной в язык монады Either. Точнее — это _и будет_ монада Either!
Я могу написать ф-ю, которая принимает лямбду с заданными checked exceptions? Нет.

Да ладно?

Так это ж не лямбда, а чит чистой воды

Почему?

Может, потому-что это не лямбда, а объект с методом call?

Просто детали реализации?

И в чем же принципиальная разница?


Java довольно многословна, это факт. Но чем кроме 4 букв и 1 точки этот объект отличается от лямбды?

Странно, что вы тогда не apply использовали. Судя по докам, так оно и реализуется.

Э, вы вообще о чем?

Ну, запись x -> x + 2 это же тоже самое, что


new Function<Number, Number>() {
  @Override
  public Number apply(x: Number) {
    return x + 2;
  }
}

Не? Я не пишу на джаве

Это зависит от требуемого интерфейса. У Function метод называется apply, а у Callable — call. В своем интерфейсе можно объявить любой метод с любой сигнатурой.

Какой смысл в лямбдах, если надо для каждой велосипедить интерфейс? :)
О том речь и шла — в джаве checked exceptions реализованы крайне неудобно. Потому не взлетели. А если сделать так, чтобы взлетели, то оно будет как монада :)
Следуя вашей логике, Хаскель не взлетел, потому что неудобно, а если сделать так, чтобы взлетел, то будет как Джава.

Ну, ADT есть не только в хаскеле

Та не, я более абстрактно шутил. Хаскель менее популярный, чем Джава, а следуя той логике, чтобы сделать его популярным — нужно превратить в Джаву.
> Следуя вашей логике, Хаскель не взлетел, потому что неудобно

Нет, это не по моей логике. По чьей-то чужой, возможно.

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


Да, "классическое" ООП многословно, это его недостаток, который никто не скрывает — и именно потому в такие классические ООП-языки как C++, Java и C# активно добавляют элементы ФП (те же лямбды). Но к исключениям против монад это отношения не имеет.

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

Да это просто пример. У checked exceptions много разных проблем из-за врожденной кривизны.
Да это просто пример. У checked exceptions много разных проблем из-за врожденной кривизны.

Аргументируйте, пожалуйста.

НЛО прилетело и опубликовало эту надпись здесь

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


Но я спрашивал про недостатки checked exceptions в рамках подхода ООП. Где нет никаких функций, возвращающих функции.

Проблема состоит во взаимодействии двух вещей:
1. В случае ООП мы работаем со стейтом, вместо того, чтобы прокидывать аргументы явно
2. Проверяемые исключения не являются first class citizen

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

Ну не, не будет оно точно как Either никогда. Во-первых, есть уже Either, в javaslang. Попробуйте сделать пару-тройку уровней вызовов функций, каждый из которых возвращает свои ошибки (ну там условно — сетевой/сокеты, http, прикладной), и дотащите ошибки уровня сокетов до приложения в виде Either. Это будет похоже, но это тоже будет кучка геморроя — просто другого.


Я готов согласиться, что Either в javaslang тоже реализовали криво, но лично не вижу способа сделать сильно прямее.

НЛО прилетело и опубликовало эту надпись здесь

Пожалуйста, прочитайте ветку обсуждения сначала, а не с середины. Тут уже давно обсуждается совсем другой код.

НЛО прилетело и опубликовало эту надпись здесь

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


Дальше начался холивар "исключения — это плохо, но мы никому не расскажем почему именно".

НЛО прилетело и опубликовало эту надпись здесь

Ну, я упростил пересказ. Полная ветка выглядит пока что именно так: исключения — плохо, потому что их нет в сигнатуре, а checked exceptions — еще хуже, но почему мы не расскажем.

по взгляду на функцию нельзя понять, что она возвращает


Вон ниже про ФП такой диалог:
— Вот только эксепшены не объявлены в сигнатуре функции, и о наличии «красных путей» можно только догадываться.
— Это было бы аргументом если сторонники ФП брали за правило всегда объявлять сигнатуры своих функций. Вот только…
— В статически-типизированных все хорошо с сигнатурами, просто их можно опускать, потому что вывод типов работает лучше

Так в чем разница? В том же C# точно так же на этапе компиляции можно узнать обо всех потенциальных ошибках.
В том же C# точно так же на этапе компиляции можно узнать обо всех потенциальных ошибках.

Только для известных реализаций, не для интерфейсов. К сожалению. И не для переданных делегатов.


(Я, правда, не говорю, что это обязательно нужно. Просто указываю ряд ограничений.)

НЛО прилетело и опубликовало эту надпись здесь
В коде с монадами нам не надо при этом создавать ветки в вызывающем коде, а в коде с ООП — нужно было бы.
Вообще-то нет, не обязательно.

Метод, который выполняет какое-то действие кидает эксепшн (переходит на «красные пути»), который прерывает дальнейшее выполнение кода.

Функция validateRequest явно не занимается формированием ответа на, допустим, http запрос, значит она отдает ответ, который просто переводит исполнение на «красные пути», и где-то тааам, в конце кто-то примет этот ответ и сделает из него ответ на запрос.

в случае с эксепшеном:… и кто-то перехватит эксепшн и сделает из него ответ на запрос.

– суть та же, но способ реализации другой.

Вопрос: почему автор не использовал эксепшены и намеренно изуродовал ООП код? =)
Метод, который выполняет какое-то действие кидает эксепшн (переходит на «красные пути»), который прерывает дальнейшее выполнение кода.

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

Это было бы аргументом если сторонники ФП брали за правило всегда объявлять сигнатуры своих функций. Вот только ...


… ни одна функция из определенных в обсуждаемом тексте в примерах на F# не имеет явной сигнатуры! Тут не то что о наличии "красных путей", тут в принципе о типах аргументов и возвращаемых значений надо догадываться!


И самое интересное — это таки работает. А значит, "нужно догадываться" еще не означает "трудно разобраться". И с исключениями можно поступить так же.


Тем более, для сложных случаев есть документация.


А еще в Java есть checked exceptions.

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

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

Почему для аргументов и возвращаемых значений считается нормальным использовать "вывод типов", а для исключений всем достань и полож сигнатуру?

А как вы (окей, без checked exceptions) поймете, свалится ли у вас функция в рантайме, если в ее типе нет на это указания?

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


Но если мне нужно узнать про конкретную функцию — а посмотрю ее исходный код (если она моя) или документацию (если она сторонняя).

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

Сложно, несложно, это уже человеческий фактор.


Но если мне нужно узнать про конкретную функцию — а посмотрю ее исходный код (если она моя) или документацию (если она сторонняя).

Ну а можно этого не делать и положиться на компилятор.

Идея исключений — в удешевлении общего случая жертвуя частным.


Так вот: в общем случае мне не нужно смотреть какие именно исключения кидает код.

НЛО прилетело и опубликовало эту надпись здесь
> ни одна функция из определенных в обсуждаемом тексте в примерах на F# не имеет явной сигнатуры!

Явной не имеет, но по факту сигнатура есть. Вы увидите ее, если наведете мышкой на ф-ю в ИДЕ, ну и компилятор вам в любом случае напомнит, если вы вдруг не обработаете ошибку.

А явная сигнатура кому нужна-то, по факту?
НЛО прилетело и опубликовало эту надпись здесь
> Ну и, кроме того, не всегда компилятор может вывести тип

В этом случае и проблем с отсутствием сигнатуры не будет, очевидно :)

А сигнатура потому-что выводится. И если у вас у значения вывелся тип Option<number>, вам компилятор не даст работать с ним как с number.


Ну и не все на джаве пишут.

НЛО прилетело и опубликовало эту надпись здесь
Было бы действительно интересно почитать статью на тему паттернов проектирование в мире функционального программирования. Но к сожалению в данной статье ничего подобного не видно. Здесь имеется только обзор всем известных основ ФП и всё. Вот например какие паттерны мне надо применить при проектирование GUI библиотеки? Человек из ООП мира ответит сразу (ну или может пойдёт в начале полистает всем известную книжку и ответит потом). А что скажет сторонник ФП? «Использовать монады и функции»? Ну так это аналогично ответу «использовать классы» и ничего не говорит об архитектуре. В отличие от таких понятий как например «паттерн Посетитель» и т.п.

В общем я сильно порадовался названию статьи и полностью разочарован её содержимым. Буду ждать следующую попытку от адептов ФП…
> А что скажет сторонник ФП?

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

Раздел про монады — это ж самые, что ни на есть, паттерны. Далеко не все, конечно, статья бедновата даже по меркам введения.

> Так почему паттернов ФП нет? там все задачи уникальные?

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

Если какой-то подход имеет строго выраженную идею, структуру и применяется для достижения конкретных целей – это паттерн.

Каррирование, Промисы, Коллбеки – это все паттерны.
> Если какой-то подход имеет строго выраженную идею, структуру и применяется для достижения конкретных целей – это паттерн.

Я и говорю, в этом понимании в ФП нету паттернов.

> Каррирование, Промисы, Коллбеки – это все паттерны.

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

Я и говорю, в этом понимании в ФП нету паттернов.

О! А вот и целевая аудитория статьи, я выше о них смеялся.

Если в какой-нибудь парадигме нету паттернов, то это значит что она настолько ненужна в практической разработке, что под нее даже не пишут никакую теорию. Вы считаете, что это об ФП?
> то это значит что она настолько ненужна в практической разработке, что под нее даже не пишут никакую теорию

Под ФП есть огромное количество теории. Просто паттернов в этой теории нету. Там есть монадные трансформеры, стрелки, а также зигохистоморфные препроморфизмы с пределами и копределами. А паттернов — вот нет, не завезли :)
И, да, эту теорию можно применять на практике. Например, благодаря этой теории я знаю, что могу использовать синтаксис генераторов в js с любой монадой, в которой fmap применяет свой аргумент не более раза :)
То есть паттерны есть, но их нету? И вот пример паттерна, но это не паттерн, потому что это ФП, а не богомерзкое ООП, а если хипстеры узнают, что я признал это паттерном — засмеют на афтерпати.
> То есть паттерны есть, но их нету?

Давайте определимся с терминологией, чтобы друг друга понимать. Я под «паттерном» понимаю нечто, что, в частности, имеет четкое назначение. Это соответствует общепринятому смыслу, вкладываемому в термин «паттерн». В этом смысле паттернов в ФП нету, потому как все конструкции слишком универсальны. Если же под «паттерном» подразумеваются какие-то «классические» приемы, которые туда-сюда ходят и применяется в том или ином контексте — то такое, безусловно, есть.
Давайте определимся с терминологией

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

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

> А паттерн «Интерфейс» внезапно перестает быть паттерном?

Там же у вас в первых же строчках на вики написано конкретное назначение паттерна — обеспечить программисту простой или более программно-специфический способ доступа к другим классам. Сможете сходным образом описать назначение монад?

> И все просто потому вы хотите

Я ничего не хочу. Мне лично без разницы, как и что вы будете называть. Кажется, я достаточно ясно раскрыл свою точку зрения, а какими конкретно словами она выражается — для меня совершенно несущественно. Если фп-конструкции удовлетворяют вашему пониманию термина «паттерн» — ну так пусть удовлетворяют, я не против. Сущность объекта не зависит от того, как этот объект называют, пусть хоть «тирьямпампация» :)
То есть у монады нет назначения? У объекта первого класса нет назначения?
> То есть у монады нет назначения?

Именно так. У _конкретной_ монады назначение есть. У монады в общем (какой-то, неизвестно какой) — назначения нет. Это слишком общий объект, сказать про что-то «оно монада» — практически, ничего полезного не сказать. Что с этим чем-то будут делать — вывести уж точно нельзя.

> У объекта первого класса

Монада как бы не особо объект первого класса да и вообще не программный объект. Ее программным объектом, конечно, можно сделать (запаковать нужные ф-и в класс, например), но этого не требуется. Слово «конструкция» на мой взгляд наиболее точно отражает положение вещей. Есть некая конструкция (которая может быть реализована как угодно), и если она обладает определенными особенностями, то это — монада.
Организация последовательных вычислений — чем не назначение?
Тем, что этим занимается только некоторый класс монад, но далеко не все. Условно говоря, у нас есть несколько широких классов монад: монады контейнерные (типа List), монады, протаскивающие контекст (State), монады, управляющие потоком управления (вроде Maybe) ну и эзотерические, имя им легион (Cont, например)
Ну ладно. Тогда давайте попробуем так.
Монада Maybe, монада Option — это паттерны? Они четко соответствуют вашему определению: «нечто, что, в частности, имеет четкое назначение»
> Монада Maybe, монада Option — это паттерны?

Да, конкретные монады (из классических, то есть у которых есть какая-то общепринятая семантика, как у той же Maybe), наверное, можно считать паттернами.

Однако, почему тогда не считать паттерном в ООП какой-то _конкретный_ класс? Так ведь не поступают, по каким-то причинам.
Видите ли, интересует не только конктретный класс, но и паттерны его использования.

Джентльмены, откройте глаза!


Эта серия лекция популяризировала паттерн Railway [Oriented] Programming.


Гугль выдает: Результатов: примерно 15 600 000 (0,53 сек.)

Не все 15 миллионов по делу. Большая часть результатов относится к каким-то железнодорожным делам. Если искать то же в кавычках, то результатов становится примерно в тысячу раз меньше. Думаю, вы преувеличиваете известность этого словосочетания.

В тот день я встретил это сочетание дважды или трижды:
а. Обсуждение дизайна котлина
б. Неправильно употребенное по отношению к котлиновским проверкам на нулл
в. Какой джаваскриптов reactive framework

lmgtfy.com/?q=Railway+oriented+programming

Первую «железнодорожную» ссылку я вижу на 6-ой странице

Первые несколько страниц выдачи действительно по делу, но число 15 600 000 — не в кассу.


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

Я под «паттерном» понимаю нечто, что, в частности, имеет четкое назначение.

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

Вот например какие паттерны мне надо применить при проектирование GUI библиотеки?
Рекомендация «используйте react+redux» не подойдет? Функциональное программирование на UI.
«react+redux» не имеет никакого отношения к ФП.
Да? Абрамов несколько иного мнения 29:00.
Он может быть какого угодно мнения, реальности это не меняет. Весь визг про ФП в рамках реакта/редакса — не более чем маркетинговый прием. Недобросовестная реклама.
Позвольте. Редюсеры должны быть чистыми функциями, состояние не должно изменяться, вместо этого мы возвращаем новое состояние. Кажется это принципы функционального программирования. Пока вы не привели никакой аргументации, кроме прямых обвинений в недобросовестной рекламе.
Разве чистые функции — это единственная концепция ФП?

Зачем же тогда писать "никакого"?


«react+redux» не имеет никакого отношения к ФП.
> Позвольте. Редюсеры должны быть чистыми функциями, состояние не должно изменяться, вместо этого мы возвращаем новое состояние. Кажется это принципы функционального программирования.

Это были бы принципы функционального программирования, если бы на этом подходе строился поток управления в целом. Но 90% логики остается в грязных функциях с сайд-эффектами, а в редьюсерах — мелочь. Редакс ведь не про редьюсеры, редьюсеры можно исопльзовать и без редакса. Он про стор (который работает грязно) и про action creators и их middleware (которые работают грязно). В редаксе используются чистые функции? Используются, с этим не поспоришь. Но они в той же мере абсолютно везде используются. Важно — как используются. Если подход основан на использовании чистых ф-й — можно говорить об ФП. Если подход основан на использовании грязных функций (как в случае редакса) и там где-то применяются чистые функции — то тут об ФП говорить рановато.
Абрамов несколько иного мнения 29:00.

о! Группи Абрамова! Если Дан что-то сказал — надо отключить свой мозг и принять за истину! Думать не нужно, нужно помнить заветы Абрамова.

image

У Редакса есть элементы ФП. Код там процедурный. Диспатч — процедурный. В языке С sqrt — тоже чистая функция, которая не изменяет свой аргумент, но от этого весь язык не становится функциональным
Вы вырываете из контекста. Я сослался на мейнтейнера. У меня к redux много вопросов, особенно к процедурному стилю. Приходится писать очень много boilerplate или не идиоматичный redux'у код (проблема с обучением новых сотрудников).
«react+redux» не имеет никакого отношения к ФП.

Вы и другие участники ветки уже признали, что отношение есть: react / redux используют элементы ФП. Очевидно, что в них много побочных эффектов, потому что в web-ориентированном UI по-другому быть вообще не может.

В JS идеи начали просачиваться в том числе из-за проблем с data-flow и callback hell. Оказалось, что есть продолжения и с ними все попроще. А вот монады в JS пока никому не нужны, потому что никто целей чистоты, как в хаскеле не ставит.

Есть проблема — асинхронный код, который есть в 90% приложений и необходимость его как-то поддерживать и развивать. Есть решение — промисы. Копнули чуть дальше, оказывается еще и чистые функции есть. Подход прагматичный и инструментальный.
Ну а вы признаете, что были неправы тут?
Рекомендация «используйте react+redux» не подойдет? Функциональное программирование на UI.

Это не «функциональное программирование», пусть и с элементами.
Я сослался на мейнтейнера

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

Я никогда не высмеивал Абрамова. Я высмеиваю только его фанаток-программистов, которые заменяют свой мозг его речами.

И, кстати, так проигнорировали главный вопрос. Потому повторюсь:
вы признаете, что были неправы?
«react+redux» не имеет никакого отношения к ФП
и
У Редакса есть элементы ФП. Код там процедурный. Диспатч — процедурный. В языке С sqrt — тоже чистая функция, которая не изменяет свой аргумент, но от этого весь язык не становится функциональным

Спорите со мной уже вы про Абрамова. А оригинальный вопрос был:
Вот например какие паттерны мне надо применить при проектирование GUI библиотеки?

React и Redux — это примеры применения элементов ФП в UI-библиотеках. Это максимально точный ответ. В комментариях мне пишут, что «react и redux не имеют отношения» а потом «содержат элементы».

Содержать элементы уже != иметь отношение?

Очевидно, что react и redux — не хаскель. Это никто утверждать не будет. Кстати, если перейти на ссылку с видео то там он утверждает ровно тоже самое «redux использует идеи функционального программирования. Они не новые».

Суть вопроса «какие есть примеры ФП в UI» уже утратила актуальность. В ветке мы обсуждаем недобросовестный маркетинг и девочек.

вы признаете, что были неправы?

В чем конкретно я не прав? Может и признаю, пока не понял.
Процитирую вас:

Рекомендация «используйте react+redux» не подойдет? Функциональное программирование на UI.


Но это, как мы уже выяснили, не функциональное, а так, моментами использует функциональное.

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

Другой момент — был задан вопрос «какие есть практики создания UI на ФП». Все, что прилетело в ответ — «вот используйте библиотеку» (это просто фейспалм). Данное грустное обстоятельство указывает на слабую теоретическую базу.
Вы делаете pr на хейте Абрамова

И да, это лол. Я не выступаю на конференциях, где кого-то хейтю вместо выступления по теме, не рисую глуповатые сравнительнительные таблицы с хейтом, не пишу статьи, которые начинаю с хейта. А комментарии на хабре слишком быстро забываются, чтобы считаться пиаром.
А можно увидеть именно сами паттерны (словесное описание и т.п.), а не какую-то библиотечку из дикого JS мира? )
Да не то, что паттерны!
Как вообще можно проектировать на ФП, вот в чём вопрос.
НЛО прилетело и опубликовало эту надпись здесь
А через три месяца выбрасываете неподдерживаемый лапшекод и возвращаетесь в Джаву.
НЛО прилетело и опубликовало эту надпись здесь
Где через три месяца снова выбрасываете весь код и идете работать грузчиком.

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

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

Возможно, потому что в императивной технике люди хотя бы думают, как сделать результат поддерживаемым, а в фп единственный совет — «Пишете сигнатуры функций, добиваетесь, чтобы тайпчекалось — всё, спроектировали». Супер!
НЛО прилетело и опубликовало эту надпись здесь
«как вообще можно проектировать на ФП».

Ну смотрите — на вопрос «как вообще можно управлять самолетом?» можно просто ответить: «берете штурвал и двигаете им, пока не прилетите на место», а можно дать ссылку на пару талмудов об управлении самолетом.

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

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

пишете сначала модуль ...

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

Уже не функциональный подход.
Функции в ФП декларируются как стейтлесс, поэтому их можно звать из любого места в любое время. Смысла делить проект на модули, кроме как для оптимизации загрузки программы, нету.
НЛО прилетело и опубликовало эту надпись здесь
То есть — у тебя есть анализ в виде модульности, но нет синтеза вообще. И эту, не знаю, «хэшмапу» (которая, на самом деле, не хэшмапа, а «контейнер очень важных данных») ты зовёшь не напрямую, а через макаронины идиотских экспортов.
Собственно-говоря — так оно и выглядит в реале, но хотелось верить, что есть кто-то, кто знает как проектировать функционально.
НЛО прилетело и опубликовало эту надпись здесь
Анализ и Синтез это термины логики. Анализ — это разложение чего-либо на составные элементы, Синтез это сборка чего-то из элементов, которые получены в результате Анализа.
Что-же касается Хэшмапы.
Тебе не нужна Хэшмапа, но ты этого не понимаешь!
Тебе нужен контейнер для неких данных.
Возвращаясь к вопросу проектирования — для каких данных тебе нужен контейнер, как эти данные связаны с другими данными, какое место занимают эти данные во множестве других данных. На эти вопросы ответа нет, потому, что данные ты не формализовал и не обобщил. А из этого ответа, через долгое (или недолгое, как повезёт) исходит решение, какой конкретно контейнер ты должен применить в данном случае.
Модули — это не ответ. Модульность даёт некоторое смысловое разбиение предметной области на группы. Ещё большее и удачное разбиение (Анализ) даёт ОО. Но мы говорим о ФП, причём в теоретическом смысле, не вдаваясь в подробности.
Ещё большее и удачное разбиение (Анализ) даёт ОО.

Обосновать можете?

НЛО прилетело и опубликовало эту надпись здесь

Модуль — это прежде всего способ организации кода. Модули не имеют отношения к используемой парадигме.

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

Ээээ, что? Сокрытие информации никто не отменял, оно не только к состоянию относится.

А нету информации, нечего скрывать.
Есть функция, которая принимает нечто и нечто другое выдаёт.

Функция — это тоже информация.

Не может человеческий язык состоять из одних глаголов.

Не может, и что? К тому, что функция — это информация, и иногда нуждается в сокрытии, это отношения не имеет.

Отличная статья, кстати, если-бы там кругом не упоминалась богомерзкая Java.
Более забавная статья есть на хабре:
habrahabr.ru/post/161885
Только она больше вопросов порождает, чем отвечает на вопрос «как проектировать на ФЯ».

Лично я не понимаю этот вопрос, он слишком абстрактный. А как проектировать на не-ФЯ?

Ну, Гради Буч, Румбаух, Шлаер с Меллором, Якобсон много про ОО писали.
НЛО прилетело и опубликовало эту надпись здесь
Удивительное пристрастие к хэшмапу, оно похоже на стэндфордовский курс функционального программирования, только там они заботились о задаче «как мне быстренько посчитать атомную бомбу».

Реальные задачи немного другие.
Вот например, постановка задачи про «Грабить Корованы» — до хэшмапы там как до эльфов лесом.

Если проектировать «Корованы» с Функциональным подходом, то мы быстренько наталкиваемся на такую проблему, что нам тяжело описать «Корован» и очень легко описать операцию «Грабить».
Хэшмапа это не мой пример, смотри сообщение выше.
> что нам тяжело описать «Корован»

Почему же тяжело? Прикидываем данные, которые должны описывать караван, и пишем соответствующий тип.
В ФП у тебя минимум два «Корована»!
Первый это Неограбленный_Корован, а второй это Ограбленный_Корован.
И нет никакого жёстко заданного «типа».

Э, правда? Из какого же определения ФП это следует?

> И нет никакого жёстко заданного «типа».

Как это нет? Есть, конечно. Как же компилятор типы проверяет, если их нет? :)

> В ФП у тебя минимум два «Корована»!

Берете Clean (это такой чисто функциональный ЯП), объявляется тип вашего каравана uniqueness и будет только один караван! Чудо! :)
НЛО прилетело и опубликовало эту надпись здесь
type Caravan = {
  gold: number
};
const rob = (caravan: Caravan): Caravan => ({
  gold: 0
});

Парам парам пам. Caravan — жестко заданный тип.

НЛО прилетело и опубликовало эту надпись здесь
Персистентность ведёт к потере стейтлесс преимущества ФП.
НЛО прилетело и опубликовало эту надпись здесь
Хоть коммент удаляй — масло-масляное.
Ладно, напишу свой вариант:
rob({caravan,_Name,_CamelsCount,_Gold}) ->
    {caravan};
rob(_) ->
    ok.

НЛО прилетело и опубликовало эту надпись здесь
Эрланг. В котором, кстати, есть паттерны в виде ОТП.
Точно так же, как и не на ФП. Все те же способы прекрасно работают.
> Обратите внимание, сигнатура int -> int -> int не содержит скобок не случайно.

Она не содержит скобок из-за того, что скобки там ничего не значат, в силу правоассоциативности "->".

Скобки не "ничего не значат", а подразумеваются, причем определенным образом. В (int -> int) -> int их нельзя опустить.

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

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

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

Почему на рисунке «Lisp programmers» выше «Haskell programmers»?
Cherry — это на практике черешня, а не вишня. (Так же как dinner — это на практике ужин, а не обед.)
А черешня — не вишня? Это самая важная деталь, из-за которой в переводе весь смысл метафоры потерян?:)
Ну это все троллинг, конечно — извините. Мне просто подумалось, что если cherry — это вишня, то композиция функций — это фрактал, почему бы и нет. (Да, я понимаю, что это перевод. Но.)

Если говорить про метафоры, то вот еще проскальзывает в статье избитая тема: «функция в ФП- это объект первого порядка». Ну на практике (такой же, как с вишней-черешней) это не совсем тот «первый порядок», который имелся в виду, когда Чёрч придумывал лямбда-исчисление. Вот что, например, имеется в виду под «настоящим первым порядком» с практической точки зрения: habrahabr.ru/post/322052

True = t => f => t
False = t => f => f

И дальше все остальное через это выводится.

А то, что коллбэки можно передавать в параметрах или что функции комбинировать, так это как черешня-вишня: вроде бы и то же, а вроде бы и вообще нет.
Я целенаправленно скачал лекции по лябда-исчислению и даже посмотрел первые три. Кому как, конечно, но мне с вишенками и яблочками «заходит» лучше. Для математики такие аналогии смехотворны. Но мы же программы пишем, а не теоремы доказываем.
Кому лучше и почему?

Забавно, что сам Евгений, по прошествию стольких лет, сейчас кодит гораздо больше на Java, чем на Haskell )

С самого начала отличный пример:
(apple -> banana) (banana -> cherry)

apple, banana, cherry — это разные сущности. У них абсолютно разные аттрибуты, параметры и инварианты, но — функциональщики с лёгкостью необыкновенной преобразовывают одно в другое. При этом они даже не могут объяснить (или не удосуживаются), что это за преобразование такое (apple->banana), что за этим стоит из предметной области.
getCustomerFromDatabase — в функциональщине вообще невалидная функция (по названию). Потому, что нету ни Кастомера ни Датабазы.

Потому что основа функционального программирования — это stateless-операции над аргументами функции. Когда мы добавляем внешний изменяемый state в качестве, например, базы данных, то теряется идемпотентность функций, и результат композиции становится непредсказуемым. Чтобы жестко зафиксировать последовательный порядок применений функций, требуется использовать специальную монаду IO, с которой программирование фактически становится императивным. Кроме того, результат вычисления функции — это уже не просто Response, а еще и измененный State. То есть красивая идеальная схема HttpResponse = WebApplication(HttpRequest) уже не работает, и проблема здесь будет не в getCustomerFromDatabase(), а в updateCustomerInDatabase().
Поэтому для data-driven приложений функциональное программирование не лучший выбор.

Чтобы жестко зафиксировать последовательный порядок применений функций, требуется использовать специальную монаду IO, с которой программирование фактически становится императивным.
Все-таки точнее сказать: «код выглядит как императивный». IO позволяет отделить вычисления от исполнения.
Про монады я ничего не знаю, писал на Эрланге.
Так вот, этот самый Эрланг изначально противоречит «основе функционального программирования» — а именно в модульной структуре. Если наши функции такие стейтлесс, то нахрена ограничивать доступ к функциям в модуле с помощью -export([blablafun/1]).
Ну ладно… Ты не замечаешь, что в ответе ты применил Структуру Данных, которой быть не должно? То есть идеальная схема — это:
{http_response, _bla, _bla ,_bla, _bla} = handle({http_request,_bla,_bla,_bla,_bla}).
… и эти оба тупла — это совсем не структуры данных…
А до кастомеров в датабазах и о последовательности исполнения мы ещё и близко не добрались.
НЛО прилетело и опубликовало эту надпись здесь

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

Просто используйте неизменяемые коллекции

Всегда и везде и память ваша закончится:) Кроме шуток, R# перестал работать в VS2015 как раз из-за неизменяемых коллекций в Roslyn, которые выжрали всю оперативку. Анализаторы Roslyn выключить нельзя. Пришлось JetBrains запилить Rider.

Люди, у которых 2015-ая студия и решарпер, смотрят на вас с удивлением.

У меня студия на i7 с ssd и 12гб оперативки знатно так подтормаживает, особенно при наборе текста. Райдер работает значительно шустрее. Кажется, про проблему открыть проект «Решарпер» в VS2015 со включенным «решарпером» упоминал serjic в одном из интервью про Rider.

Судя по картинке, Лисп вообще вне конкуренции. Почему же речь идет идет за F#, который двумя этажами ниже? И как лисперы живут без типов, монад?

Вы не поверите, это юмор:) Лисп — старейший из «выживших» ЯП. На коболе и фортране никто уже нового не пишет, а Clojure — это современный диалект Лиспа.
На фортране всё ещё пишут те, для кого фортран писался изначально.
монада — это всего лишь моноид в категории эндофункторов
Не один год не мог понять, что такое монада, а после этой фразы резко понял.
Большое спасибо!
Функции с одинаковым типом входного и выходного значения являются моноидами

Странное утверждение. Функция из А в А — это бинарное отношение на множестве А, то есть, подмножество декартова произведения (АхА). Моноид — полугруппа с единицей. Может, имелось в виду, что множетво всех таких фнкций образует моноид относительно операции композиции?

Стиль изложения материала у автора намеренно упрощен, его даже обвиняют в популизме. При переводе я не стал корректировать, потому что тогда получилось бы уж совсем вольное изложение по мотивам, а это и так рерайт.
Стиль изложения автора — «открывай редактор, набивай код, смотри что получается, думай, изобретай».
Т.е абсолютно адекватный материалу.
Рекомендую!

И далеко не все у него просто. Когда он объясняет различие аппликатива и монад, если не seasoned haskeler/MLer — мозг приходится на повышенную тактовую частоту переключать.
Господа, я удивлен, что серия лекций, которая популяризировала выражение «Railway oriented programming» кого-то в 2017 году поражает «новизной».
Кстати, «Railway oriented programming» (который является ни чем иным как паттерном(!)) — имеет реализации (или подражания) почти для каждого значимого языка.
Я полагал, что если даже первоисточник кто-то не читал, то уже с последствиями его — обязательно должен был столкнуться.

Ну, а если вам так нравится удивляться тому что уже вошло в повсеместную практику — то может быть вам в новинку будет лекция Норвига 98-го года norvig.com/design-patterns/design-patterns.pdf
Рекомендую!

Академический ФП — академический и его надо оставить академикам. Но идеи из ФП вкупе с последними версиями популярных языков очень полезны. Вот пример java8 + spring после двух лет на скале. Ничего особо хитрого, но джавист так не напишет:


        long unsentEmailId = transactionTemplate.execute(status ->
                unsentEmailRepository.save(
                        new UnsentEmail()
                                .to(to)
                                .subject(subject)
                                .body(body)
                                .lastSent(new Date())
                ).getId()
        );
Кстати, тут не совсем ФП: fluent interface здесь — это ООП, а не ФП, т.к. вызовы не создают новый объект, а меняют состояние существующего.

Я про то же — "совсем ФП" беспощаден, медленнен и бесполезен. И не очень хорошо работает с J2EE.

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

«Совсем ФП» медленнен (по крайней мере, когда я на нем пишу), потому что немалая часть времени исполнения уходит в манипулирование иммутабельными контейнерами и структурами. Более или менее любой код на ФП можно переписать в «в основном процедурном» стиле (лишь с небольшими элементами ФП, типа как на c++ с использованием алгоритмов STL, но с малым количеством классов) так, чтобы он работал точно таким же образом. А потом можно заменить иммутабельные структуры и контейнеры на мутабельные, что, очевидно, лишь улучшит производительность.

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

Собственно говоря, я вполне допускаю, что «совсем ФП» для меня медленнен лишь потому, что у меня не так уж много в нем опыта — вон, если на каком-нибудь python писать начать, в первый день тоже все будет работать как Energizer — до десяти раз дольше. А вторая проблема — то, что я не вижу, где можно применить функциональную парадигму, собственно, и является основной причиной малого количества опыта, и, вполне возможно, вызывается всего лишь узостью моего мышления.

Насколько я понимаю, вы — достаточно опытный ФП-разработчик. Пожалуйста, посоветуйте, в проектах какого типа я, как разработчик, могу получить ощутимые плюсы от использования ФП (не сейчас, а хотя бы в будущем, через несколько лет опыта)? Это сразу отменило бы все три пункта, по крайней мере, лично для меня.

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


  • иммутабельные структуры данных значительно упрощают синхронизацию в многопоточном окружении. Вместо мьютексов и т.п. часто можно обойтись CAS над одной переменной
  • передавать функции в качестве аргументов другие функции — удобная вещь, как для декомпозиции кода так и для тестирования.
  • map, flatMap и filter над коллекциями — очень полезные операции, позволяющие изящно выразить большинство распространенных преобразований коллекций
  • концепция "всё является выражением" в языке программирования позволяет писать более качественный код. К примеру, if в современных языках это expression, а не statement.
  • pattern matching — тоже очень удачная находка из мира ФП. switch на стероидах.
  • добавление к четырем китам ООП (наследование, полиморфизм, инкапсуляция, агрегация) композиции (операция соединения двух объектов, возвращающая третий объект того же типа) позволяет многие вещи делать проще и изящнее.
  • монада Option вместо null — must use в 21 веке, кроме самого оптимизированного кода
  • монада Either позволяет писать безопасный код, где компилятор проверит, что программист обработал все ошибки
  • монада Future/Promise — новый стандарт для асинхронного кода.
Спасибо большое за перечисление того, что удобно в мире ФП. Многим из этого, что доступно в не-ФП языках, часто пользуюсь, действительно очень удобно. Но как раз факт того, что это и так доступно в мультипарадигменных языках, и удерживает меня от попыток сделать что-нибудь чисто на ФП: я не вижу в этом необходимости.
> «Совсем ФП» медленнен (по крайней мере, когда я на нем пишу)

Это не ФП медленен, это компиляторы не-ФП языков плохи в оптимизации идеоматических для ФП конструкций.
Ну, я пробовал Haskell, и мой код на нем работает сильно дольше, чем мой код на c++.
> Ну, я пробовал Haskell, и мой код на нем работает сильно дольше, чем мой код на c++.

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

Неочевидно
Я имел в виду, что «можно заменить» не может ухудшить производительность — где улучшит, заменяем (и нередко получаем очень большой буст), где не улучшит — оставляем как есть.

В любом случае, б-г с ней, с производительностью, я о ней написал лишь потому, что о ней писали выше. В моей практике обычно несравненно большее значение имеют скорость разработки, предсказуемость скорости разработки, читабельность и поддерживаемость кода (в том числе и с использованием всяких средств автоматизации и для человека, не знакомого с языком). Будь Haskell (или любой другой ФП-язык) хоть в десять раз медленнее на всех задачах, если он позволяет улучшить эти параметры, то я его самый преданный фанат. А сервера найти можно.
А какого типа проекты вы обычно разрабатываете?
Обычно всякие вычисления в глубоком бекенде. Предметные области разные были за годы работы — поиск в интернете, ортодонтия, финансы, криптовалюты.
Смогу лучше ответить после вашего ответа на предыдущий вопрос, ибо, возможно, у меня очень нерепрезентативная выборка моих проектов
Проблема в том, что сейчас я не вижу вообще никаких проектов, которые бы выиграли из-за применения существенного количества ФП (грубо говоря, большего его количества, чем то, что доступно в c++ из коробки). Но я предполагаю, что я их не вижу не потому, что их нет, а потому, что у меня недостаточно знаний или опыта в этой сфере.
Поэтому примеры совершенно любых проектов, которые, на ваш взгляд, лучше пишутся в ФП, будут очень кстати. А дальше я уже смогу над этими примерами думать, возможно, что-то реализовать как pet project, возможно, где-то в основной деятельности найти аналогии, возможно, еще что-то.
НЛО прилетело и опубликовало эту надпись здесь
> Выразительные типы, кроме того, являются документацией.

Как же тут не вспомнить канонiчное:
type Equality s t a b = forall p f. p a (f b) -> p s (f t)

hackage.haskell.org/package/tweak-0.1.0.1/docs/Control-Lens-Type.html
задокументировано на 11 из 10 :trollface.jpeg:
НЛО прилетело и опубликовало эту надпись здесь
> Я это прочитал где-то с третьей попытки, и то потому, что какие-то неудачные имена для переменных выбраны.

Дык о том-то и речь, что в хаскеле любят бессмысленные однобуквенные сокращения, что сразу «самодокументируемость типов» снижает до минимума. Прочитать-то не сложно, сложно понять какая семантика у этих буковок.

> Почему a и s, а не a и a', скажем?

А что, замена s на a' тут что-то существенно улучшает? :)

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

Да тут дело не в линзах, это хорошая демонстрация неких свойств кода на хаскеле в общем :)
НЛО прилетело и опубликовало эту надпись здесь
> f — это почти всегда функтор

Ну вот в рассматриваемом случае — не функтор.

> Третье перечитывание

Вообще, тот факт, что необходимость трех (ТРЕХ! даже не двух, Карл!) прочтений для парсинга «самодокументированной» строчки кода вас не смущает — показателен сам по себе :)

> Если же мы говорим о буковках для переменных, то \x y -> x == y не сильно хуже bool operator==(T left, T right).

Конечно не хуже, вот только в сколько-нибудь более сложных случаях пишут полные имена. Как пример — тип для Select в C#:
public static IEnumerable<TResult> SelectMany<TSource, TCollection, TResult>(this IEnumerable<TSource> source, Func<TSource, IEnumerable<TCollection>> collectionSelector, Func<TSource, TCollection, TResult> resultSelector);
НЛО прилетело и опубликовало эту надпись здесь
> Так это ж type-level programming, вы читаете реализацию этакой метафункции.

А что, для метафункции, в отличии от функции, понятных имен для переменных не надо? Хотя, погодите! Ведь хаскелисты и для обычных ф-й хреначат x, a, b, c, а когда не хватает — то x', a', b', c' :)

Другая деформация — нелюбовь к скобкам. Это прям отдельное направление спецолимпиады в хаскеле — взять понятный код со скобками и переписать его в нечитабельную дичь без скобок, зато с $,. и прочими радостями. И в одну строчку :)

> Ну ещё бы, с таким количеством синтаксического шума :)

Где там синтаксический шум?
НЛО прилетело и опубликовало эту надпись здесь
> Надо. Но в случае оператора сравнения и так всё ясно.

Что ясно?

> Ну так шум же!

Так в том и дело, что не шум, а необходимая часть кода. В которую хаскель просто не может в силу очень плохо спроектированного синтаксиса.

> Ну так шум же! Как по мне, первый вариант сильно лучше второго:

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

> В угловых скобках. Сравните с

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

> Хотя каноничный хаскелист выбрал бы имена src, col, res вместо source, collection, result, например.

Во-первых, каноничный хаскелист бы выбрал a,b,c. Во-вторых — даже так показательно. Зачем писать col если можно написать collection? В чем вообще смысл этого сокращения? Зачем искусственно занижать читабельность кода?

При этом вы сами, небось, предпочитаете писать


items.Where(x => x.Price > 10).Count()

вместо


Count(Where(items, x => x.Price > 10))

да?

> При этом вы сами, небось, предпочитаете писать

Конечно, ведь первую запись можно на несколько строчек по точкам разбить.

А вторую редактор не позволяет? :))))

> А вторую редактор не позволяет?

Спросите у людей, которые пишут на хаскеле, что им не позволяет :)

Им позволяет. Просто им не надо, потому, что строчка короткая

> Просто им не надо, потому, что строчка короткая

А короткая она именно потому, что длинную писать неудобно, хоть и нужно.

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

НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
> В хаскеле по строкам тоже можно разбить на каждом. или $.

Теоретически — да, но на практике этого не делают.

> Что именно там необходимо?

Угловые скобки, запятые.

> То есть, код на хаскеле писать нельзя вообще, раз он не может в необходимые части?

Так они не считаются необходимыми и код пишется без них.

> Синтаксиса в хаскеле, кстати, предельно мало, и это очень удобно.

Что? Несколько десятков контролирующих инструкций со своей ассоциативностью и приоритетами — это «мало синтаксиса»? Из неэзотерических языков — хаскель язык с_самым_ перегруженным синтаксисом.

> Ну да, Func<TSource, IEnumerable> гораздо лучше и читабельнее (source -> t collection).

Во-первых, на хаскеле не пишут (source -> t collection), там будет (a -> b c). Во-вторых, если уж делать аналог, то (source -> IEnumerable collection), в-третьих, в случае с несколькими аргументами сложных типов — да, читабельнее.

> Никто не запрещает ставить переносы строк:

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

> То же самое вряд ли получится, декларация типа и переменных таки разнесены.

Я и говорю, что не получится. И это плохо, т.к. основная часть «документации» из декларации типа сразу теряется.

> А каноничный шарпист бы выбрал синглтон фабрики декораторов селекторов. Будем биться стереотипами?

Вот вы хороший тезис привели. Теперь обратите внимание, что синглтоны фабрики декораторов селекторов считаются в шарпе раком, с которым борются, который режут на ревью. А a,b,c — это норма, скорее на ревью скажут: «зачем это ты написал тип source, пиши a». В этом и есть проблема. На хаскеле _можно_ (хоть и неудобно) писать с длинными именами, многострочно и т.п. Можно выработать какие-то соглашения, и код не будет превращаться в канонiчный хаскиарт. Но так не делают (не делают по бОльшей части, то есть это не считается какой-то общей нормой в комьюнити, какие-то отдельные специалисты, может, и делают).

> А почему вы пишете collection, а не itemsCollection, dataSetCollection, collectionWithResultsFromTheDatabaseOrOtherLinqQueryBackend?

Наверное, вы ожидали, что я скажу: «потому что оно понятно из контекста»? А вот и нет! Потому что это не важно. Не важно, откуда к вам пришла эта коллекция в данном участке кода, потом и не требуется писать, что она FromTheOtherDatabase. А вот понять, что это collection а не какой-то col — вполне себе важно, раз мы будем с ним работать как с коллекцией.
НЛО прилетело и опубликовало эту надпись здесь
> Потому что вышеупомянутая строчка короче даже 80 символов. Зачем её разбивать?

Так еще раз, у нас цепочка:
1. Нужна длинная строчка (как выше, если строчку записать полноценно, то она будет длиннее 80 символов, как в c#)
2. Но писать длинные строчки в хаскеле неудобно
3. По-этому пишут коротко => снижается качество кода.

> Зачем там угловые скобки-то?

Чтобы отделить типы от имен. В хаскеле этой проблемы нет, потому что там нету имен, что плохо (уже обсуждалось выше).

> А на сишарпе не пишут без фабрик синглтонов визиторов декораторов.

Конечно, пишут. Код без синглтонов и фабрик на шарпе — это норма. А на хаскеле норма a -> b -> c.

> Нет. Traversable t => source -> t collection

Ну вы t сократили, тогда Traversable traversable => source -> traversable collection. Видите как вы упорно сопротивляетесь «удлинению»?

> Почему? Вам же важен сам тип, а не то, под каким именем он используется в функции.

Конечно же мне важно, под каким именем он используется. Как это не важно? Имя описывает функцию. Назначение, в смысле

> Вы придумываете какой-то идеальный мир, где шарписты в белом, а хаскелисты пишут одни лишь однобуквенные фибоначчи и факториалы.

Я описываю вполне реальный мир. И в этом реальном мире синглтоны абстрактных фабрик — известный стереотип, повод посмеяться, на практике же — code smell. А в хаскеле _повсюду_ сокращения и попытки уложиться «в одну строчку и без скобочек», ну зайдите на hackage.

> А вдруг это не коллекция данных, а коллекция картин художника? Или музыкальная коллекция?

А нам это важно? Если нет — то и не требуются эти данные. Если же где-то дальше в работе важно, что это коллекция именно коллекция картин — тогда и стоит удлинить имя.
НЛО прилетело и опубликовало эту надпись здесь
Чем плох вынос функций налево и направо? Тем, что читая код, чтобы оценить, что он делает, нужно понимать, что делает каждая функция. Это можно сделать двумя способами: либо по названию этой функции, либо изучив её содержание. Для первого нужно дать функции достаточно ясное название, что как правило означает, что название будет достаточно длинное. Второй способ удовлетворительно работает, когда суммарное количество функций невелико, в противном случае получаем очередную инкарнацию спагетти-кода, только теперь в функциональном стиле.
НЛО прилетело и опубликовало эту надпись здесь
> Что значит полноценно?

С нормальными именами для типов, с именами аргументов

> В чём неудобство?

Язык не поддерживает переноса.

> Не разбивают на две или несколько функций, не выносят отдельные элементы композиции в let/where-блоки, а пишут коротко.

Вы забываете, что выделение ф-й/блоков — не бесплатно, само по себе оно сильно снижает качество кода. Когда вам приходится выделять ф-и не потому, что это требуется семантически, а для того, чтобы компенсировать недостатки ЯП — это плохо. У меня на этот счет есть такое правило — если за 3 секунды не получается придумать ф-и имя, которое бы полностью и исчерпывающе описывало назначение ф-и для человека незнакомого с контекстом, то эту ф-ю выделять нельзя, т.к. код с ее выделением сильно усложнится, а профитов — нет.

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

a b c d e f — вот, разделил пробелами. Где тут имена, а где типы? Вы просто попробуйте записать конструкцию C# без угловых скобок и поймете, зачем они нужны.

> А вы тоже вместо i пишете index?

Мы это уже обсуждали. Писать i в качестве индекса — общепринятое соглашение, которому все следуют. t в качестве traversable — не соглашение, ему не следуют. Не всегда traversable сокращаются как t, не всегда t означает traversable.

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

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

> Как люди пишут на языке?

Именно это. А что еще имеет смысл обсуждать?

> Или вы библиотечные операторы (всякие там $, ., <*>, >>=) тоже считаете захардкоженными в синтаксис языка?

Никто не говорил о «захардкоженности», речь шла об управляющих конструкциях. Если управляющая конструкция реализована как ф-я (или как макрос), то она управляющей конструкцией быть не перестает. И претензия не в самом факте ее наличия ав получающемся ascii-арте вместо синтаксиса. Лисп такого недостатка лишен, там как раз пишут вменяемые имена, особенно если мы о scheme-derived ветви :)
А вы в циклах в императивных языках пишете i или index?

Лучше вообще избегать индексов в циклах.
Вопрос не в том, избегать индексов или нет. Вопрос в том, какое имя переменной вы выберите для индекса, если его избежать нельзя.
Если индекса никак не избежать, посмотрю, есть ли смысл дать ему осмысленное название, например row/col. Иногда бывает, что i удобнее, но обычно в этом случае всё же можно заменить цикл на foreach

Ну вот, например, изменить значения массива:


void Modify<T>(this IList<T> data, Func<T, T> func)
{
    for (int i = 0; i < data.Count; i++)
        data[i] = func(data[i]);
}

Что тут лучше: i или index? Или нормально, что используется T вместо TItem?

Незачем выносить такую простую операцию в отдельную функцию. А внутри более длинного метода и для i найдётся более подходящее название, и для data, и T не потребуется в принципе.
Как же с вами сложно-то. Ну давайте так:

void Modify<T>(this IList<T> data, Func<T, T> func)
{
    ... // Some code

    for (int i = 0; i < data.Count; i++)
        data[i] = func(data[i]);

    ... // Some code
}
Что такое «… // Some code»? Для сферического примера в вакууме будут и не менее сферические имена переменных.
void MeaningfulMethod<TMeaningfulType>(this IList<TMeaningfulType> meaningfulList, Func<TMeaningfulType,TMeaningfulType> meaningfulDelegate)
{
  ...// Some meaningful code

  for (int meaningfulIndex = 0; meaningfulIndex <meaningfulList.Count; meaningfulIndex ++)
    meaningfulList[meaningfulIndex] = meaningfulDelegate(meaningfulList[meaningfulIndex]);

  ...// Some more meaningful code
}
А теперь посмотрите, как ваш код разорвало — читать же неудобно.

Вот вам кусок реального кода:

        public void Truncate(int Count)
        {
            if (Count >= array.Count)
                return;

            for (int i = Count; i < array.Count; i++)
            {
                TValue value = array[i];
                if (value != null)
                {
                    value.SetUpdated(value);
                    value.Rollback();
                    value.SetParent(null);
                    array[i] = null;
                }
            }

            Truncate();
        }


Впрочем, да, в проекте больше нет мест, где нельзя обойтись без индекса.
как ваш код разорвало

Нелегко жить в вакууме.
читать же неудобно.

Не знаю, что хуже: многословные названия, из-за которых строчка занимает всю ширину 21"—монитора, или практика запихивать все самые тривиальные действия в функции с односложным названием.
> или практика запихивать все самые тривиальные действия в функции с односложным названием.

Ну если с вашей точки зрения приведённая мной функция выполняет тривиальное действие, и от неё нужно отказаться в пользу вставки её содержимого в вызвающий код, то смысла продолжать спор не вижу.
Приведённая вами в первом примере функция — именно такая. Во втором примере мало что понятно без контекста. Почему если Count больше или равно количеству элементов в массиве, происходит просто возврат, а не, к примеру, выбрасывается исключение? Почему массив называется array, но не передаётся параметром? Что за тип TValue?
Почему если Count больше или равно количеству элементов в массиве, происходит просто возврат, а не, к примеру, выбрасывается исключение?

Наверное, потому, что функция называется Truncate? И обычная логика работы этой операции — обрезать лишнее.


Почему массив называется array, но не передаётся параметром?

Потому что это метод класса WatchableArray, имеющего поле array и generic параметр TValue.


Что за тип TValue?

А вот это уже не имеет значения. Да, у него есть constraints, но если его назвать TTrackableAndWatchableValue вместо просто TValue, то на читаемости кода это скажется отрицательно за счёт увеличения объёма кода и тавтологии (WatchableArray<TTrackableAndWatchableValue>).

Да, с первым — не сообразил, что это — для того, чтобы не выполнялась инструкция Truncate() если отрезать нечего. Однако, получается, что если, допустим, последние 3 значения в массиве равны null, то при вызове с параметром, равным array.Count-1 и array.Count-2 оно выдаст одинаковые результаты, а с array.Count — не сделает с ним ничего, хотя команда Truncate() отрезала бы последние 3 записи. Я не знаю, может быть так и надо, но выглядит как баг из-за преждевременной оптимизации.
Не преждевременная оптимизация, а вынесение повторяющегося кода в отдельную функцию.

Truncate(Count) забивает элементы null, а вызываемая в ней приватная функция Truncate() уже корректирует размер массива.
При чём тут вынесение повторяющегося кода? я про if…return, из-за которого функция работает не так, как я бы ожидал.
> Или нормально, что используется T вместо TItem?

Это вы, кстати, хороший пример привели. Если обратиться к официальному кодстайлу, то там будет указано, что все имена типов должны быть с префиксом T. Отсюда тривиально следует факт — у вас может быть лишь _один единственный_ однобуквенный тип, и это сам T :)
По-этому нормально писать T вместо TItem, потому что это одно и тоже по общему соглашению. А вот если у вас несколько типов — вам придется, по кодстайлу, дать им более значащие название.
> А вот если у вас несколько типов — вам придется, по кодстайлу, дать им более значащие название.

Иногда это T1 и T2 — всяко лучше, чем TFirstArgument и TSecondArgument.
А когда это T1, T1, TFirstArgument и TSecondArgument? Я никогда такого не видел.
Посмотрите определение типа `System.Func<,,,,>`
> Иногда это T1 и T2 — всяко лучше, чем TFirstArgument и TSecondArgument.

В случаях, когда у аргументов нет семантики — пусть будет T1 и T2. Когда мы пишем на шарпе, сам факт отсутствия «звучащих» имен и говорит, что семантики не стоит никакой за ними, иначе — перед нами плохой код. В хаскеле же, еще раз, f s t a b — это _норма_. В этом проблема. Речь не о том, что в принципе можно или нельзя написать, речь о том, что считается либо не считается нормальными, общепринятыми практиками.
НЛО прилетело и опубликовало эту надпись здесь
> Вопрос не в том, избегать индексов или нет. Вопрос в том, какое имя переменной вы выберите для индекса, если его избежать нельзя.

Ну вы не сравнивайте несравнимое. i, j, k — это стандартные общепринятые сокращения для индексов, которые понятны всем. И если вы так обзовете не-индекс, то вам зададут недоуменный вопрос на ревью. С другой же стороны, a,b,c,f,a',b',c',f',x' и прочая дичь — это просто буквы, которые никакого смысла не несут. И, нет, не надо про «f — функтор», или «m — монада», потому что очень часто они используются не так и это считается нормальным (в отличии от i,j,k).

Просто хаскелисты фигачат говнокод, потому что в хаскеле очень кривой синтаксис. Он проектировался для написания коротких формул-однострочников и не подходит для general-purpose вещей, многострочных код и код с длинными именами на хаскеле выглядит откровенно плохо, вот и извращаются кто во что горазд. Ну и плюс — это круто же! В одну строчку и без скобок!
Наверное, проблема в том, что функциональщики предпочитают нагородить таких абстракций, что осмысленного названия чему-либо уже дать невозможно.
НЛО прилетело и опубликовало эту надпись здесь
поэтому многострочный код бьют на функции.

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

Вполне допускаю, что это действительно так. Но тогда вы — исключение.

> Люди, радующиеся тому, что круто, на любом языке наговнокодят.

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

> Во-вторых, имена переменных (а их и нет) в хаскеле и в ФП вообще важны чуть меньше, чем в императивном программировании. Довольно часто Ъ-хаскелист в библиотечном коде, где никакой предметной области ещё толком нет

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

> Код с длинными именами выглядит тоже вполне нормально.

Видимо, именно по-этому вы выше вместо collections все-таки написали cols? Я, к слову, сперва подумал, что это сокращение от columns, и некоторое время недоумевал, при чем там columns? Только потом дошло.
Кстати, у меня есть гипотеза, почему такие названия. a, b… — первая треть алфавита, j, k… — вторая треть, s, t… — третья :)

Не подскажете ссылку на крупное опнсорс приложение на хаскеле? Не компилятор, не математика, не библиотека, а классическое приложение с базой данных, DAO, логикой и GUI?

xmonad. Правда, это — достаточно красноглазая штука.
То есть, если я верно понял, рекомендация «взять и попробовать, а там точно пойдет». Спасибо, обязательно воспользуюсь
НЛО прилетело и опубликовало эту надпись здесь

А какое отношение паттерн Builder, сугубо ООП-шный, имеет к ФП?

Паттерны однозначно есть, а вот книг по ним однозначно нет. До сих пор не понимаю, откуда ФП программисты их берут

Публикации

Истории