Здравствуйте, gyraboo, Вы писали:
G>А есть примеры исходников проектов, написанных полностью в функциональном стиле, решающие типичные задачи enterprise? Т.е. наличие некоей предметной области со сложными отношениями между сущностями, слой бизнес-операций, слой персистенции, сквозной функционал логирования, безопасности, транзакционности и т.д. Задачи типа многопоточности и распределённости не беру, т.к. тут всё понятно, ФП в этом изначально сильна.
Под рукой ссылок на конкретные исходники нет, но начать можно отсюда: How to design and code a complete program Ну и книга хороша: Domain Modeling Made Functional: Tackle Software Complexity with Domain-Driven Design and F#
G>И ещё вопрос — насколько полностью ФП ПО поддерживаемо? Так же просто ли его читать, как "чистый" ООП-шный код? Просто ли его отлаживать или там всё залямбдено по самую крышу, что дебаггер не позволяет по-человечески дебажить? Есть ли сформированные стереотипы и шаблонные решения, когда ты видя большой кусок кода, понимаешь что это за шаблон и не вникаешь во все детали реализации?
На мой взгляд, если исключить неадекватов, лепящих монады и теории категорий во все дыры, то значительно проще.
1) ООП вынуждает привязывать методы к данным, хотя чаще всего в домене эта связь отсутствует. В результате, эта связь делается случайным образом, что приводит к каше в коде. Например, можно написать код в стиле ООП: "Холст.НарисуйКартину(кисть)", либо "Кисть.НарисуйКартину(холст)". Какой из этих двух вариантов правильный? Тут нет неправильного варианта. Реализован будет произвольный из них, смотря какая пятка зачесалась у программиста. Хотя в ФП стиле все однозначно: "НарисуйКартину(холст, кисть)". Нет никаких разночтений.
2) Данные жёстко связаны с методами. Какой бы из описанных в предыдущем пункте мы не выбрали вариант, мы будем жёстко связаны принятым случайным образом решением. Если мы выбираем вариант "Холст.НарисуйКартину(кисть)", то НарисуйКартину жёстко привязана к холсту и мы сможем в дальнейшем рисовать картины исключительно только на холстах. В другом варианте мы будем жёстко привязаны к кисти и сможем рисовать только кистью. С другой стороны, при ФП декомпозиции, функция НарисуйКартину(где, чем) ни к чему не привязана и может рисовать на чём угодно и чем угодно, если это пригодно для рисования.
3) ООП провоцирует на создание большого количества лишних сущностей. Для борьбы с предыдущими проблемами приходится вводить большое количество искусственных сущностей, которые отсутствуют в исходном домене. Для этих сущностей часто даже невозможно придумать адекватного названия, вот и появляются всякие FooManager, BarHelper, все это обмазывается толстым слоем абстрактных фабрик, визиторов, репозиториев и прочих паттернов, которые для эксперта из доменной области звучат, как тарабарщина и мешают тем самым применению DDD. В результате программист вместо решения задачи из предметной области воюет с визиторами и декораторами.
В результате, ФП проект со временем менее подвержен превращением в кашу. В фп стиле легче писать правильно и труднее говнокодить.
Здравствуйте, gyraboo, Вы писали:
G>Я вот тоже последнее время задаюсь вопросом, что если функциональное программирование лучше (в частности, для написания распределенных и многопоточных задач), то как в нём реализовать SOLID, GRASP, ООП-паттерны и прочие достижения ООП.
Практически все ООП паттерны и прочие "достижения" нужны только для того, чтобы решать проблемы привнесенные самим ООП. Из-за отсутствующей функциональной композиции начинаются пляски с декораторами, стратегиями и прочим.
AA>Одна из них в том, что реализация тянет за собой сборку с интерфейсом.
Определяй интерфейс в той же сборке, и не потянет.
AA>если использовать функцию, мы не зависим ни от одной сборки.
Не факт. Если заменить myTypeInstance.DoThings() на doThings(myTypeInstance), тогда то на то и выходит.
Вызов метода экземпляра класса — это тот же вызов функции, только экземпляр передаётся в виде неявного первого параметра.
А статические методы и вовсе ничем от функций не отличаются.
Не вижу в чём тут чистота. По сути ты сначала дал именование этому интерфейсу, а потом убрал именование. По-мне стало только хуже. Это какое-то движение в сторону JS получается, а не чистота.
vsb>>Не вижу в чём тут чистота. По сути ты сначала дал именование этому интерфейсу, а потом убрал именование. По-мне стало только хуже. Это какое-то движение в сторону JS получается, а не чистота.
AA>Имя переменной наделено смыслом и связано с типом. Только в случае интерфейса описание типа хранится отдельно от места использования.
Зато в случае интерфейса функции логически сгруппированы по их назначению, за счет этого получается меньше бардака.
И еще — пусть, например, есть несколько разных несовместимых реализаций одного интерфейса. Конкретный пример — какой-нибудь интерфейс IArchiver для действий "архивация файлов", в этом интерфейсе методы CreateArchive и AddFileToArchive, реализации — ZipArchiver и RarArchiver.
Если это же делать тупо функциями — есть риск накосячить: одну функцию в процессе использования взять соответствующую одной реализации, а другую, взаимоувязанную с ней — соответсвующую другой реализации.
В общем, это была бы равноценнная замена только для для интерфейсов, состоящих из одной функции. Для более сложных интерфейсов — нет (можно, конечно, нагородить структуры из функций, но это получились бы те же интерфейсы, только вид сбоку)
Здравствуйте, mrTwister, Вы писали:
G>>Я вот тоже последнее время задаюсь вопросом, что если функциональное программирование лучше (в частности, для написания распределенных и многопоточных задач), то как в нём реализовать SOLID, GRASP, ООП-паттерны и прочие достижения ООП.
T>Практически все ООП паттерны и прочие "достижения" нужны только для того, чтобы решать проблемы привнесенные самим ООП. Из-за отсутствующей функциональной композиции начинаются пляски с декораторами, стратегиями и прочим.
А есть примеры исходников проектов, написанных полностью в функциональном стиле, решающие типичные задачи enterprise? Т.е. наличие некоей предметной области со сложными отношениями между сущностями, слой бизнес-операций, слой персистенции, сквозной функционал логирования, безопасности, транзакционности и т.д. Задачи типа многопоточности и распределённости не беру, т.к. тут всё понятно, ФП в этом изначально сильна.
И ещё вопрос — насколько полностью ФП ПО поддерживаемо? Так же просто ли его читать, как "чистый" ООП-шный код? Просто ли его отлаживать или там всё залямбдено по самую крышу, что дебаггер не позволяет по-человечески дебажить? Есть ли сформированные стереотипы и шаблонные решения, когда ты видя большой кусок кода, понимаешь что это за шаблон и не вникаешь во все детали реализации?
Здравствуйте, klopodav, Вы писали:
K>И еще — пусть, например, есть несколько разных несовместимых реализаций одного интерфейса. Конкретный пример — какой-нибудь интерфейс IArchiver для действий "архивация файлов", в этом интерфейсе методы CreateArchive и AddFileToArchive, реализации — ZipArchiver и RarArchiver. K>Если это же делать тупо функциями — есть риск накосячить: одну функцию в процессе использования взять соответствующую одной реализации, а другую, взаимоувязанную с ней — соответсвующую другой реализации.
Пример хороший, валидный. Без использования интерфейсов и их суррогатов в чисто-функциональном стиле, можно было бы записать так, чтобы generic функция CreateArchive (параметризованая типом архива) возвращала generic функцию AddFileToArchive, параметризованную тем же типом архива. И это было бы логично, перед тем, как добавлять файлы в архив, этот архив сначала надо создать.
Но нюанс в том, что сущностей, которые бы хорошо описывались интерфейсами со многими методами исчезающе мало. Хорошо, это значит, без нарушения interface segregation principle.
Здравствуйте, mrTwister, Вы писали:
T> На мой взгляд, если исключить неадекватов, лепящих монады и теории категорий во все дыры, то значительно проще.
T> 1) ООП вынуждает привязывать методы к данным, хотя чаще всего в домене эта связь отсутствует. В результате, эта связь делается случайным образом, что приводит к каше в коде. Например, можно написать код в стиле ООП: "Холст.НарисуйКартину(кисть)", либо "Кисть.НарисуйКартину(холст)". Какой из этих двух вариантов правильный? Тут нет неправильного варианта. Реализован будет произвольный из них, смотря какая пятка зачесалась у программиста. Хотя в ФП стиле все однозначно: "НарисуйКартину(холст, кисть)". Нет никаких разночтений.
Хрень какая-то. Выбор в данном случае будет основан на том кому нужен доступ к приватным членам и диспатчу по динамическому типу (т.е. виртуальный метод). Т.е. как раз те дыры, что пытаются заткнуть монадами и теориями категорий в ФП.
Иначе будет просто статическая функция как в ФП.
T> 2) Данные жёстко связаны с методами. Какой бы из описанных в предыдущем пункте мы не выбрали вариант, мы будем жёстко связаны принятым случайным образом решением.
Если не надо — не связывай, не будут. И решения случайные лучше не принимать. Да и не проблема это вовсе, есть рефакторинг, часто автоматический.
T> 3) ООП провоцирует на создание большого количества лишних сущностей. Для борьбы с предыдущими проблемами приходится вводить большое количество искусственных сущностей, которые отсутствуют в исходном домене. Для этих сущностей часто даже невозможно придумать адекватного названия, вот и появляются всякие FooManager, BarHelper, все это обмазывается толстым слоем абстрактных фабрик, визиторов, репозиториев и прочих паттернов, которые для эксперта из доменной области звучат, как тарабарщина и мешают тем самым применению DDD. В результате программист вместо решения задачи из предметной области воюет с визиторами и декораторами.
Паттерны в ФП тоже есть в достатке, просто они другие.
T> В результате, ФП проект со временем менее подвержен превращением в кашу. В фп стиле легче писать правильно и труднее говнокодить.
По-моему уже давно договорились, что оптимум: ООП на глобальном уровне дизайна, ФП — локально на уровне реализации тела методов.
Здравствуйте, mrTwister, Вы писали:
T>Не жабные, а ООПэшные. Да, ООП можно не использовать.
Так ООП, ФП и т.п. — это сферовакуум, а в реальном мире есть вполне конкретные ЯП. И они часто поддерживают много парадигм как раз потому, что нет весомого повода загонять разработчика в какую-то одну.
Переубедить Вас, к сожалению, мне не удастся, поэтому сразу перейду к оскорблениям.
Здравствуйте, mrTwister, Вы писали:
T> ·>Хрень какая-то. Выбор в данном случае будет основан на том кому нужен доступ к приватным членам и диспатчу по динамическому типу (т.е. виртуальный метод). Т.е. как раз те дыры, что пытаются заткнуть монадами и теориями категорий в ФП. T> Приватные данные есть не сами по себе, а появляются для конкретной реализации, и появляются они в том месте, где происходит реализация. То есть не приватные данные определяют реализацию, а наоборот.
Это проблема курицы и яйца. Какая разница кто кого определяет?
Простой пример. Пусть у нас будет тип Стек. Его можно реализовать разными способами. Например, в виде списка и в виде массива. Приватными данными будут разные вещи. Например, указатель на голову и счётчик размера в первом случае; или массив и индекс в нём во втором случае.
Вот тут и диспатч, тут и приватные данные. Как это делать в ФП без монад и паттернов — неясно.
T> По поводу диспатча — диспатч нужен не типу, а конкретному алгоритму, этот тип использующему. Но при этом задается диспатч в типе. То есть при проектировании типа нам надо иметь ввиду конкретный алгоритм. И если алгоритм немного меняется, либо надо написать немного другой алгоритм, то внезапно может выяснится, что диспатч надо делать иначе и наш старый код вместо переиспользования обрастает костылями, декораторами, адаптерами и стратегиями
Или код рефакторится и пишется немного другой алгоритм... Ты как-то демонизируешь ООП.
Костылей в ФП тоже вагон и маленькая тележка.
Костыли это результат определённого процесса разработки, а не стиля программирования.
Здравствуйте, ути-пути, Вы писали:
УП>Какие-то жабные стереотипы. Большинство языков на это не повелись, и в них все это легко и непринужденно решается свободными функциями. Да и в жабе это возможно, если рассматривать часть классов лишь как пространства имен. УП>Ничто не мешает на плюсах написать "НарисуйКартину(холст, кисть)", а ведь только на этом все эти претензии строятся
Не, не только на этом. На С тоже можно написать "НарисуйКартину(холст, кисть)", но это не сделает его функциональным.
Вот смотри, как это работает. Допустим, мы хотим написать функцию filter(collection, predicate) так, чтобы можно было применить её к коллекции целых, и отфильтровать все меньше определённого числа.
На классическом С нам придётся изобрести что-то типа
(это я пренебрегаю сложностью изобретения концепции "произвольная перечислимая коллекция" на C)
и после этого мы будем писать что-то вроде
collection filterlessthan(collection c, int limit)
{
return filter(collection, &lessthan, &limit);
}
bool lessthan(int a, void* limit)
{
return a < *(int*)limit;
}
То есть помимо (на самом деле независимо от) возможности писать свободные функции нужна возможность описывать замыкания неявно.
Потому что без этого наш код сильно лучше не становится — мы сможем заманить корявую пару из (predicate, context) на функтор, но конструировать функтор придётся по-прежнему руками.
А когда мы оборудованы возможностью делать лексические замыкания и конструировать кортежи, ООП становится не очень нужно — его можно эмулировать практически без потерь.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, alexanderfedin, Вы писали:
A>Вероятно, потому что правильным будет "Художник.НарисуйКартину(холст, кисть)"
Класс без состояния с одним методом? Это не ООП. Зачем этот класс вообще нужен, не проще ли оставить только один метод из класса? Не думаешь же ты, что если взять ФП программу, каждую функцию вынести в отдельный класс, то мы получим ООП программу?
A>Мне кажется, ты не в курсе существования генерализации типов в ООП.
Перекрестись
A>То, ч то ты написал, можно резюмировать парой слов: "ООП ниасилил".
ООП ниасилил тот, кто делает классы без состояния с одним методом, потому что это не ООП
AA>Получается, что функции высшего порядка делают код чище и проще, и интерфейсы тут как бы не особо уже нужны. AA>Достаточно набора функций, при наличии частичного применения
Интерфейс объединяет методы общей семантикой.
Интерфейс только лишь из одного метода — да, вызывает вопросы. Но вот IDisposable считаю удачным.
Изучение кода, особенно с решарпером в обнимку, проще на интерфейсах, это мой личный опыт. Скорее всего по той же причине что интерфейс наделяет семантикой даже один метод.
Однозначно ответа думаю нет. Везде надо чувство меры.
Недавно опробовал такой класик:
class AsDisposable : IDisposable
{
public AsDisposable(Action onDispose)
{}
//IDisposable
}
— тупо оберточка из делегата делающая из делегата IDisposable.
Хорошо зашло. using синтаксис придает смысл. А если б можно было имплементации фигачить лямбдами то и этого не надо было бы.
Допустим, у нас есть сервис, который должен рассылать сообщения.
public Notificator(IEmailSender sender)
Мы внедряем зависимость через интерфейс
Теперь у нас связаны три сушности
EmailSender
IEmailSender
Notificator
Отныне и навеки, так сказать.
Если же, сделать так:
type Notificator (sender: string -> string -> string -> bool) =
if sender("guest@local", "hello", "who are you?") then
Получается, что функции высшего порядка делают код чище и проще, и интерфейсы тут как бы не особо уже нужны.
Достаточно набора функций, при наличии частичного применения и различные декораторы также становятся не нужны
Здравствуйте, varenikAA, Вы писали:
AA>Получается, что функции высшего порядка делают код чище и проще, и интерфейсы тут как бы не особо уже нужны. AA>Достаточно набора функций, при наличии частичного применения и различные декораторы также становятся не нужны AA>
Здравствуйте, varenikAA, Вы писали:
AA>Получается, что функции высшего порядка делают код чище и проще, и интерфейсы тут как бы не особо уже нужны. AA>Достаточно набора функций, при наличии частичного применения и различные декораторы также становятся не нужны
У обоих вариантов есть плюсы и минусы. Сервис, настраиваемый функциями высшего порядка, не имеет зависимостей, не требует лишних зависимостей (если в интерфейсе n функций, а нужна только одна), его можно скопировать в другой проект и он скомпилируется и запустится без изменений кода. С другой стороны, ты заменил
IEmailSender.sender(email: String, title: String, body: String): bool
на
sender: string -> string -> string -> bool.
Я думаю, очевидно, что второй вариант без документации совершенно непонятен. Если добавить типы, как в "настоящем фп", то будет лучше, но все равно плохо:
sender: Email -> MailTitle -> MailBody -> bool
Далее. Есть Notificator, принимающий функцию высшего порядка и есть EmailSender, который тоже функция высшего порядка. Если их писали не одновременно, их сигнатуры не будут совпадать, придется добавлять "клей", преобразующий одну функцию в другую. Это сложнее, чем подправить реализацию.
Вывод: ФП подход лучше обеспечивает инкапсуляцию, но она требует значительно больше усилий и строчек кода, ухудшает читабельность, усложняет доработки — проще изменить сигнатуру вызова IEmailSender.send в нотификаторе, чем "клей", преобразующий функции в коде сборки программы из независимых, изолированных функций.
Здравствуйте, vsb, Вы писали:
vsb>Не вижу в чём тут чистота. По сути ты сначала дал именование этому интерфейсу, а потом убрал именование. По-мне стало только хуже. Это какое-то движение в сторону JS получается, а не чистота.
Имя переменной наделено смыслом и связано с типом. Только в случае интерфейса описание типа хранится отдельно от места использования.
Здравствуйте, varenikAA, Вы писали:
vsb>>Не вижу в чём тут чистота. По сути ты сначала дал именование этому интерфейсу, а потом убрал именование. По-мне стало только хуже. Это какое-то движение в сторону JS получается, а не чистота.
AA>Имя переменной наделено смыслом и связано с типом.
Ну вот смотри. Хочу я передать свой Sender. Во втором случае я вижу в сигнатуре только типы параметров. Мне надо читать код Notificator-а, чтобы понять, что ты передаёшь в эти параметры. Ну или читать какую-то документацию, где ты русским или английским языком будешь пытаться описать эти параметры. В первом случае у меня есть чёткий интерфейс, где ясно видно, какие названия у каких параметров этого колл-бека.
AA>Только в случае интерфейса описание типа хранится отдельно от места использования.
Частично это можно было бы исправить, если твой язык позволяет указывать имена параметров в сигнатуре. Т.е. что-то вроде
type Notificator (sender: email: string -> subject: string -> body: string -> sent successfully: bool) = ...
Хотя в этом случае получается какая-то программа в одну строчку, где слишком много информации сконцентрировано. Всё равно первый вариант лучше. А то, что он хранится отдельно от места использования — ну храни рядом с местом использования, не вижу проблемы.
Здравствуйте, varenikAA, Вы писали:
AA>Получается, что функции высшего порядка делают код чище и проще, и интерфейсы тут как бы не особо уже нужны.
Я вот тоже последнее время задаюсь вопросом, что если функциональное программирование лучше (в частности, для написания распределенных и многопоточных задач), то как в нём реализовать SOLID, GRASP, ООП-паттерны и прочие достижения ООП. Или же есть какая-то замена этим подходам, если да, то какая? В плане написания полноценного бизнес-приложения, как это делается например на Java/Spring. Пока что те книги, что я читал по ФП, полной картины не дают, и в своей работе ФП я пока применяю эпизодически, внутри классических ООП-обёрток и архитектуры, построенной по принципам SOLID и Ко. Возможно, в мире ФП пока эти принципы не выкристаллизовались ещё, или я просто не знаю где искать?
Здравствуйте, klopodav, Вы писали:
K>В общем, это была бы равноценнная замена только для для интерфейсов, состоящих из одной функции.
Совершенно верно, IEmailSender в Asp.Net Core именно такой интерфейс.
Проблема случается когда такой интерфейс лежит в неподходящей библиотеке. В данном случае в 3.1 aspnet.core
соотвественно о совместимости реализации можно забыть.
Для более сложных случаев в ЯП типа F# можно создать тип/алис:
type SmtpOptions = {
host : string
port : int
}
type IEmailSender : options : SmtpOptions -> mail : MailMessage -> Result
AA>Мы внедряем зависимость через интерфейс AA>Теперь у нас связаны три сушности
EmailSender вообще никак не связан с notifier’ом.
А Notifier можно использовать и без этого конкретного EmailSender’а.
Здравствуйте, Muxa, Вы писали:
M>А Notifier можно использовать и без этого конкретного EmailSender’а.
На самом деле с интерфейсами проблем много больше,
Одна из них в том, что реализация тянет за собой сборку с интерфейсом. "Хороший пример" asp.net core 3.1. интерфейс лежит в Microsoft.AspNetCore.Identity.UI.dll
Т.е. реализация будет зависит не только от dotnet core 3.1. но и еще от всего asp.net
если использовать функцию, мы не зависим ни от одной сборки.
А это уже правила хорошей разработки по Роберту Мартину.
Здравствуйте, yenik, Вы писали:
AA>>Достаточно набора функций,
Y>А если набор функций — это 10 функций? Не легче ли инжектировать 1 интерфейс?
Интерфейс нужно реализовать, что бы его инжектировать. А 10 функций можно взять готовых или скомбинировать их неким образом.
Более того, 10 функций можно заменить структурой с 10ю полями и в отношении количества инжектируемых функций такая структура будет ничем не хуже 1-го интерфейса. А в некотором отношении даже лучше, т.к. структуры не нужно реализовывать, достаточно лишь инициировать члены.
Здравствуйте, yenik, Вы писали:
Y>Определяй интерфейс в той же сборке, и не потянет.
Это если есть доступ к исходному коду.
Y>Не факт. Если заменить myTypeInstance.DoThings() на doThings(myTypeInstance), тогда то на то и выходит.
не совсем, все же в этом случае зависимость односторонняя. в первом случае зависимость в обе стороны.
AA>>>Достаточно набора функций,
Y>>А если набор функций — это 10 функций? Не легче ли инжектировать 1 интерфейс?
AA>Уверены что в одном методе нужно 10 функций?
В одном методе вряд ли. Но мы же говорим не о методе, а о классе. И цепочка вызовов в этом классе может не заканчиваться, и этот набор функций может быть нужно передать дальше.
AA>Возможно следует использовать композицию для получения из 10-ти 1-й функции.
Также возможно, что эти функции нужны по отдельности.
Y>>Определяй интерфейс в той же сборке, и не потянет. AA>Это если есть доступ к исходному коду.
Y>>Не факт. Если заменить myTypeInstance.DoThings() на doThings(myTypeInstance), тогда то на то и выходит.
AA>не совсем, все же в этом случае зависимость односторонняя. в первом случае зависимость в обе стороны.
Не понял эту мысль. Одна сборка зависит от другой. Почему в обе стороны?
AA>>>Достаточно набора функций,
Y>>А если набор функций — это 10 функций? Не легче ли инжектировать 1 интерфейс?
S>Интерфейс нужно реализовать, что бы его инжектировать. А 10 функций можно взять готовых или скомбинировать их неким образом. S>Более того, 10 функций можно заменить структурой с 10ю полями и в отношении количества инжектируемых функций такая структура будет ничем не хуже 1-го интерфейса. А в некотором отношении даже лучше, т.к. структуры не нужно реализовывать, достаточно лишь инициировать члены.
Да, структура — это получше, чем 10 параметров. Но в итоге получается некое квази-ООП в стиле C с указателями на функции, в котором отсутствуют возможности ООП в стиле C++. От этого в своё время ушли, и прогрессивность такого подхода неочевидна. Хотя в каких-то случаях он может быть вполне уместен.
Необходимость реализовывать интерфейс, как всякое техническое решение, имеет плюсы и минусы. Плюс в том, что код сгруппирован, его легче поддерживать.
А вообще было бы прикольно, если бы C# позволял такие вещи:
interface IFoo
{
string GetA();
}
var a = "%$#@$%#%^";
var bar = new<IFoo>
{
GetA = () => { return a; }
}
Здравствуйте, yenik, Вы писали:
AA>>>>Достаточно набора функций,
Y>>>А если набор функций — это 10 функций? Не легче ли инжектировать 1 интерфейс?
S>>Интерфейс нужно реализовать, что бы его инжектировать. А 10 функций можно взять готовых или скомбинировать их неким образом. S>>Более того, 10 функций можно заменить структурой с 10ю полями и в отношении количества инжектируемых функций такая структура будет ничем не хуже 1-го интерфейса. А в некотором отношении даже лучше, т.к. структуры не нужно реализовывать, достаточно лишь инициировать члены.
Y>Да, структура — это получше, чем 10 параметров. Но в итоге получается некое квази-ООП в стиле C с указателями на функции, в котором отсутствуют возможности ООП в стиле C++. От этого в своё время ушли, и прогрессивность такого подхода неочевидна. Хотя в каких-то случаях он может быть вполне уместен.
Уместно это там, кде кучка функций не связана общим состоянием, как это в ООП. Т.е. мы не интерфейс изменяемого контейнера распилили и передаем эти функции отдельно, сгруппировав их в структуру. В таком случае преимущества над интерфейсом нет.
Но если в метод поиска пути в графе передаем функции для возврата смежных вершин и функцию для определения стоимости перехода, то такую пару в интерфейс объединять как раз не нужно.
Y>Необходимость реализовывать интерфейс, как всякое техническое решение, имеет плюсы и минусы. Плюс в том, что код сгруппирован, его легче поддерживать.
Y>А вообще было бы прикольно, если бы C# позволял такие вещи: Y>
Y>interface IFoo
Y>{
Y> string GetA();
Y>}
Y>var a = "%$#@$%#%^";
Y>var bar = new<IFoo>
Y>{
Y> GetA = () => { return a; }
Y>}
Y>
Здравствуйте, gyraboo, Вы писали:
G> Задачи типа многопоточности и распределённости не беру, т.к. тут всё понятно, ФП в этом изначально сильна.
Это миф, кстати. Многие ФЯзыки или до сих пор не умеют в многоядерность, или много лет не умели. У многих самая популярная структура данных — односвязный список, работу с которым параллелить довольно сложно. У флагмана ФП Хаскеля есть умение во многоядерность и есть разнообразие структур данных, но сборщик мусора ставит все потоки на паузу, порой надолго.
Здравствуйте, D. Mon, Вы писали:
DM>Это миф, кстати. Многие ФЯзыки или до сих пор не умеют в многоядерность, или много лет не умели. У многих самая популярная структура данных — односвязный список, работу с которым параллелить довольно сложно. У флагмана ФП Хаскеля есть умение во многоядерность и есть разнообразие структур данных, но сборщик мусора ставит все потоки на паузу, порой надолго.
Разве там не регулируется кол-во потоков как в ГО (1 поток на ядро)? В этом случае, как подсказывает интуиция, не должно быть проблем со сборкой мусора. если только сборщик не наткнулся на циклические ссылки. т.е. это возможно со сборщиком что-то не так.
Здравствуйте, varenikAA, Вы писали:
AA>Разве там не регулируется кол-во потоков как в ГО (1 поток на ядро)?
В Хаскеле есть и зеленые потоки, вроде горутин, и обычные системные. Наверняка регулируется. Но толку-то, если сборщику нужно старое поколение собирать, он все равно все остановит, ибо старое поколение кучи общее для всех потоков, а чистить параллельно, как в свежих версиях Го, он пока не умеет, вроде.
Здравствуйте, mrTwister, Вы писали:
T>1) ООП вынуждает привязывать методы к данным, хотя чаще всего в домене эта связь отсутствует. В результате, эта связь делается случайным образом, что приводит к каше в коде. Например, можно написать код в стиле ООП: "Холст.НарисуйКартину(кисть)", либо "Кисть.НарисуйКартину(холст)". Какой из этих двух вариантов правильный? Тут нет неправильного варианта. Реализован будет произвольный из них, смотря какая пятка зачесалась у программиста. Хотя в ФП стиле все однозначно: "НарисуйКартину(холст, кисть)". Нет никаких разночтений.
T>2) Данные жёстко связаны с методами. Какой бы из описанных в предыдущем пункте мы не выбрали вариант, мы будем жёстко связаны принятым случайным образом решением. Если мы выбираем вариант
"Холст.НарисуйКартину(кисть)", то НарисуйКартину жёстко привязана к холсту и мы сможем в дальнейшем рисовать картины исключительно только на холстах. В другом варианте мы будем жёстко привязаны к кисти и сможем рисовать только кистью. С другой стороны, при ФП декомпозиции, функция НарисуйКартину(где, чем) ни к чему не привязана и может рисовать на чём угодно и чем угодно, если это пригодно для рисования.
T>3) ООП провоцирует на создание большого количества лишних сущностей. Для борьбы с предыдущими проблемами приходится вводить большое количество искусственных сущностей, которые отсутствуют в исходном домене. Для этих сущностей часто даже невозможно придумать адекватного названия, вот и появляются всякие FooManager, BarHelper, все это обмазывается толстым слоем абстрактных фабрик, визиторов, репозиториев и прочих паттернов, которые для эксперта из доменной области звучат, как тарабарщина и мешают тем самым применению DDD. В результате программист вместо решения задачи из предметной области воюет с визиторами и декораторами.
Какие-то жабные стереотипы. Большинство языков на это не повелись, и в них все это легко и непринужденно решается свободными функциями. Да и в жабе это возможно, если рассматривать часть классов лишь как пространства имен.
Ничто не мешает на плюсах написать "НарисуйКартину(холст, кисть)", а ведь только на этом все эти претензии строятся
Переубедить Вас, к сожалению, мне не удастся, поэтому сразу перейду к оскорблениям.
Здравствуйте, gyraboo, Вы писали:
G>Здравствуйте, varenikAA, Вы писали:
AA>>Получается, что функции высшего порядка делают код чище и проще, и интерфейсы тут как бы не особо уже нужны.
G>Я вот тоже последнее время задаюсь вопросом, что если функциональное программирование лучше (в частности, для написания распределенных и многопоточных задач), то как в нём реализовать SOLID, GRASP, ООП-паттерны и прочие достижения ООП. Или же есть какая-то замена этим подходам, если да, то какая? В плане написания полноценного бизнес-приложения, как это делается например на Java/Spring. Пока что те книги, что я читал по ФП, полной картины не дают, и в своей работе ФП я пока применяю эпизодически, внутри классических ООП-обёрток и архитектуры, построенной по принципам SOLID и Ко. Возможно, в мире ФП пока эти принципы не выкристаллизовались ещё, или я просто не знаю где искать?
Подвернулась презентация. Сам не смотрел, но думаю, что там тема раскрыта. На сайте Влашина я давно пасусь. https://vimeo.com/113588389
Здравствуйте, mrTwister, Вы писали:
T>1) ООП вынуждает привязывать методы к данным, хотя чаще всего в домене эта связь отсутствует. В результате, эта связь делается случайным образом, что приводит к каше в коде. Например, можно написать код в стиле ООП: "Холст.НарисуйКартину(кисть)", либо "Кисть.НарисуйКартину(холст)". Какой из этих двух вариантов правильный? Тут нет неправильного варианта. Реализован будет произвольный из них, смотря какая пятка зачесалась у программиста. Хотя в ФП стиле все однозначно: "НарисуйКартину(холст, кисть)". Нет никаких разночтений.
А если сделать Картина.Нарисовать(где, чем)?
Тогда, как и в твоём примере мы будем жёстко привзавны. Но теперь к картине. То есть мы не сможем на холсте нарисовать не картину, а, например, рисунок
·>Хрень какая-то. Выбор в данном случае будет основан на том кому нужен доступ к приватным членам и диспатчу по динамическому типу (т.е. виртуальный метод). Т.е. как раз те дыры, что пытаются заткнуть монадами и теориями категорий в ФП.
Приватные данные есть не сами по себе, а появляются для конкретной реализации, и появляются они в том месте, где происходит реализация. То есть не приватные данные определяют реализацию, а наоборот.
По поводу диспатча — диспатч нужен не типу, а конкретному алгоритму, этот тип использующему. Но при этом задается диспатч в типе. То есть при проектировании типа нам надо иметь ввиду конкретный алгоритм. И если алгоритм немного меняется, либо надо написать немного другой алгоритм, то внезапно может выяснится, что диспатч надо делать иначе и наш старый код вместо переиспользования обрастает костылями, декораторами, адаптерами и стратегиями
Здравствуйте, DenisCh, Вы писали:
DC>А если сделать Картина.Нарисовать(где, чем)? DC>Тогда, как и в твоём примере мы будем жёстко привзавны. Но теперь к картине. То есть мы не сможем на холсте нарисовать не картину, а, например, рисунок
Только это будет не картина (картина получается в результате работы метода), а скорее функция рисования картины, которая на вход получает данные. Вот мы и пришли к функциональному программированию, когда есть функция и данные для нее, только в нагрузку получили еще кучу синтаксического оверхеда (тм) от ООП, когда вместо того, чтобы просто написать функцию мы пишем класс-функцию.
Здравствуйте, D. Mon, Вы писали:
DM>Это миф, кстати. Многие ФЯзыки или до сих пор не умеют в многоядерность, или много лет не умели. У многих самая популярная структура данных — односвязный список, работу с которым параллелить довольно сложно.
Эмм, а в чём сложность, если он иммутабельный? Естественно, в предположении, что "работа" стоит больше, чем собственно итерирование по списку, поэтому skip(100) выполняется достаточно быстро по сравнению с process(take(100)).
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, mrTwister, Вы писали:
T>1) ООП вынуждает привязывать методы к данным, хотя чаще всего в домене эта связь отсутствует. В результате, эта связь делается случайным образом, что приводит к каше в коде. Например, можно написать код в стиле ООП: "Холст.НарисуйКартину(кисть)", либо "Кисть.НарисуйКартину(холст)". Какой из этих двух вариантов правильный? Тут нет неправильного варианта. Реализован будет произвольный из них, смотря какая пятка зачесалась у программиста. Хотя в ФП стиле все однозначно: "НарисуйКартину(холст, кисть)". Нет никаких разночтений.
Вероятно, потому что правильным будет "Художник.НарисуйКартину(холст, кисть)"
T>2) Данные жёстко связаны с методами. Какой бы из описанных в предыдущем пункте мы не выбрали вариант, мы будем жёстко связаны принятым случайным образом решением. Если мы выбираем вариант "Холст.НарисуйКартину(кисть)", то НарисуйКартину жёстко привязана к холсту и мы сможем в дальнейшем рисовать картины исключительно только на холстах. В другом варианте мы будем жёстко привязаны к кисти и сможем рисовать только кистью. С другой стороны, при ФП декомпозиции, функция НарисуйКартину(где, чем) ни к чему не привязана и может рисовать на чём угодно и чем угодно, если это пригодно для рисования.
Мне кажется, ты не в курсе существования генерализации типов в ООП.
T>3) ООП провоцирует на создание большого количества лишних сущностей. Для борьбы с предыдущими проблемами приходится вводить большое количество искусственных сущностей, которые отсутствуют в исходном домене. Для этих сущностей часто даже невозможно придумать адекватного названия, вот и появляются всякие FooManager, BarHelper, все это обмазывается толстым слоем абстрактных фабрик, визиторов, репозиториев и прочих паттернов, которые для эксперта из доменной области звучат, как тарабарщина и мешают тем самым применению DDD. В результате программист вместо решения задачи из предметной области воюет с визиторами и декораторами.
Смотри выше.
T>В результате, ФП проект со временем менее подвержен превращением в кашу. В фп стиле легче писать правильно и труднее говнокодить.
То, ч то ты написал, можно резюмировать парой слов: "ООП ниасилил".
S>(это я пренебрегаю сложностью изобретения концепции "произвольная перечислимая коллекция" на C)
S>и после этого мы будем писать что-то вроде
S>
S>collection filterlessthan(collection c, int limit)
S>{
S> return filter(collection, &lessthan, &limit);
S>}
S>bool lessthan(int a, void* limit)
S>{
S> return a < *(int*)limit;
S>}
S>
S>То есть помимо (на самом деле независимо от) возможности писать свободные функции нужна возможность описывать замыкания неявно.
S>Потому что без этого наш код сильно лучше не становится — мы сможем заманить корявую пару из (predicate, context) на функтор, но конструировать функтор придётся по-прежнему руками.
S>А когда мы оборудованы возможностью делать лексические замыкания и конструировать кортежи, ООП становится не очень нужно — его можно эмулировать практически без потерь.
Здравствуйте, alexanderfedin, Вы писали:
A>Много бреда от незнания
Хорошо зашел!
S>>На классическом С нам придётся изобрести что-то типа S>... S>То есть помимо (на самом деле независимо от) возможности писать свободные функции нужна возможность описывать замыкания неявно.
Здравствуйте, varenikAA, Вы писали:
AA>>>Достаточно набора функций,
Y>>А если набор функций — это 10 функций? Не легче ли инжектировать 1 интерфейс?
AA>Уверены что в одном методе нужно 10 функций? AA>Возможно следует использовать композицию для получения из 10-ти 1-й функции.
Ну про это есть буква "I" в слове "SOLID".
Функций в интерфейсе должно быть столько, сколько надо, и не больше.
Если же их вообще все поодиночке передавать, то это будет IMHO капец.
Например, как будешь автокомплит (киллер-фичу ООП) реализовывать?
ООП взлетело в том числе потому, что оно позволяет "обучение по месту" в IDE (тот самый автокомплит),
давая пользователю возможность выбрать из списка вариантов, а не написать.
bnk>Если же их вообще все поодиночке передавать, то это будет IMHO капец. bnk>Например, как будешь автокомплит (киллер-фичу ООП) реализовывать?
Вместо IList передаешь структурку где Add/Remove — это тупо поля с делегатами нужной сигнатуры. Все, комплит тебе также предложит вызывать Add/Remove как только вобьешь точку.
На самом деле конечно можно обойтись или только интерфейсами, или только делегатами. Прелесть шарпа в мультипарадигменности: выбирай любой стиль, или смесь — и вперед гавнокодить )
Здравствуйте, barn_czn, Вы писали:
AA>>Получается, что функции высшего порядка делают код чище и проще, и интерфейсы тут как бы не особо уже нужны. AA>>Достаточно набора функций, при наличии частичного применения
_>Интерфейс объединяет методы общей семантикой. _>Интерфейс только лишь из одного метода — да, вызывает вопросы. Но вот IDisposable считаю удачным. _>Изучение кода, особенно с решарпером в обнимку, проще на интерфейсах, это мой личный опыт. Скорее всего по той же причине что интерфейс наделяет семантикой даже один метод. _>Однозначно ответа думаю нет. Везде надо чувство меры.
_>Недавно опробовал такой класик:
_>
_>- тупо оберточка из делегата делающая из делегата IDisposable. _>Хорошо зашло. using синтаксис придает смысл. А если б можно было имплементации фигачить лямбдами то и этого не надо было бы.
В плане фичей под dotnet мне больше нравится F#
// (unit -> unit) -> IDisposable
let disp f = {new IDisposable with member it.Dispose() = f() }
use it = disp (fun _ -> printfn "it is work!")
it.Dispose()
//it is work!
Здравствуйте, D. Mon, Вы писали:
DM>В Хаскеле есть и зеленые потоки, вроде горутин, и обычные системные. Наверняка регулируется. Но толку-то, если сборщику нужно старое поколение собирать, он все равно все остановит, ибо старое поколение кучи общее для всех потоков, а чистить параллельно, как в свежих версиях Го, он пока не умеет, вроде.
Здравствуйте, Poopy Joe, Вы писали:
DM>>В Хаскеле есть и зеленые потоки, вроде горутин, и обычные системные. Наверняка регулируется. Но толку-то, если сборщику нужно старое поколение собирать, он все равно все остановит, ибо старое поколение кучи общее для всех потоков, а чистить параллельно, как в свежих версиях Го, он пока не умеет, вроде.
PJ>Разве не оно?! PJ>https://www.microsoft.com/en-us/research/wp-content/uploads/2016/07/local-gc.pdf
А оно есть в GHC из коробки? А не в чьей-то частной ветке?
IComparer<T> — вот пример уродского интерфейса который меня постоянно бесит.
Практически всегда удобнее передать делегат Comparison<T>. Но бывает что целевой класс (коллекция например с методом Sort) не умеет его принимать, подавай IComparer.
DM>А оно есть в GHC из коробки? А не в чьей-то частной ветке?
Из коробки.
-qg ⟨gen⟩¶
Default
0
Since 6.12.1
Use parallel GC in generation ⟨gen⟩ and higher. Omitting ⟨gen⟩ turns off the parallel GC completely, reverting to sequential GC.
The default parallel GC settings are usually suitable for parallel programs (i.e. those using GHC.Conc.par, Strategies, or with multiple threads). However, it is sometimes beneficial to enable the parallel GC for a single-threaded sequential program too, especially if the program has a large amount of heap data and GC is a significant fraction of runtime. To use the parallel GC in a sequential program, enable the parallel runtime with a suitable -N ⟨x⟩ option, and additionally it might be beneficial to restrict parallel GC to the old generation with -qg1.
Здравствуйте, Poopy Joe, Вы писали:
DM>>А оно есть в GHC из коробки? А не в чьей-то частной ветке?
PJ>Из коробки. PJ>Use parallel GC in generation ⟨gen⟩ and higher. Omitting ⟨gen⟩ turns off the parallel GC completely, reverting to sequential GC.
Насколько я понимаю, это совершенно другая вещь. Весь мир останавливается и старое поколение собирается в несколько тредов. Толку-то. Это не thread-local heaps и не параллельно выполнению программы.
Здравствуйте, D. Mon, Вы писали:
DM>Здравствуйте, Poopy Joe, Вы писали:
DM>>>А оно есть в GHC из коробки? А не в чьей-то частной ветке?
PJ>>Из коробки. PJ>>Use parallel GC in generation ⟨gen⟩ and higher. Omitting ⟨gen⟩ turns off the parallel GC completely, reverting to sequential GC.
DM>Насколько я понимаю, это совершенно другая вещь. Весь мир останавливается и старое поколение собирается в несколько тредов. Толку-то. Это не thread-local heaps и не параллельно выполнению программы.
Здравствуйте, Poopy Joe, Вы писали:
PJ>Ну, я отвечал на " а чистить параллельно, как в свежих версиях Го, он пока не умеет, вроде." Чистить параллельно он умеет, [s]он не умеет это делать конкуретно.
Ну это я выразился неудачно. Имел в виду именно параллельно программе, т.е. конкурентно.
Здравствуйте, barn_czn, Вы писали: _>- методы Add и Remove неразрывно связаны, как их на отдельные коллбеки разделишь? То что они идут в связке — подчеркивает семантику
Напишите пример кода, который работает с этой семантикой. Не выдуманный, а практически полезный. То есть я передаю вам ICollection, а вы внутри своего кода используете Add и Remove. А вот, скажем, Get или Find при этом — не используете.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Sinclair, Вы писали:
S>Здравствуйте, barn_czn, Вы писали: _>>- методы Add и Remove неразрывно связаны, как их на отдельные коллбеки разделишь? То что они идут в связке — подчеркивает семантику S>Напишите пример кода, который работает с этой семантикой. Не выдуманный, а практически полезный. То есть я передаю вам ICollection, а вы внутри своего кода используете Add и Remove. А вот, скажем, Get или Find при этом — не используете.
Легко — стек вызова. Add- добавляю аргументы, Remove — удаляю. И хочу для этого использовать именно ICollection. И попробуйте скажите что это "надуманый" пример, я тут же попрошу строгое определение надуманности.
Здравствуйте, barn_czn, Вы писали: _>Легко — стек вызова. Add- добавляю аргументы, Remove — удаляю. И хочу для этого использовать именно ICollection. И попробуйте скажите что это "надуманый" пример, я тут же попрошу строгое определение надуманности.
Ну почему же надуманный — совершенно нормальный пример. Но лучше бы всё же написать пример кода — потому что непонятно, что за код собирается работать с этим стеком. Вы пишете интерпретатор?
Ещё непонятно, зачем вам потребовался именно интерфейс — ведь для стека вызовов достаточно банального односвязного списка. Почему вы захотите туда передавать разные реализации интерфейса ICollection?
Непонятно, почему вы решили, что вам передадут именно стек — ведь придуманный вами ICollection может реализовывать также и очередь (FIFO), и ваш код интерпретатора просто сломается.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, varenikAA, Вы писали:
AA>Получается, что функции высшего порядка делают код чище и проще, и интерфейсы тут как бы не особо уже нужны. AA>Достаточно набора функций, при наличии частичного применения и различные декораторы также становятся не нужны
1) Как это будет работать с DI?
2) Сигнатуры функций станут менее читаемые
Кроме этого действительно разницы между функцией и интерфейсом с одним членом-функцией нету. Если предполагается что согласованых функций будет больше одной (добавить+удалить, коллекции, итд), то лучше интерфейс.
Здравствуйте, mrTwister, Вы писали:
T>1) ООП вынуждает привязывать методы к данным, хотя чаще всего в домене эта связь отсутствует.
Ничего подобного ООП не вынуждает. Вынуждают древние книги про ООП типа Гради Буча, идеи из которых до сих пор копипастят во все мурзилки типа "Профессиональная пазработка за 21 день", пишущиеся исключительно чтобы поднять бабла на ньюбах.
При этом в упомянутом шарпе есть такая штука как extension methods, позволяющая убрать синтаксические различия между методами объектов и статическими методами. Так что руки в выборе сделать метод привязанным к стейту или нет полностью развязаны.
T>Реализован будет произвольный из них, смотря какая пятка зачесалась у программиста. Хотя в ФП стиле все однозначно: "НарисуйКартину(холст, кисть)". Нет никаких разночтений.
Ну и? Этот вариант лишает код очень приятного бонуса — discoverability. Что тут хорошего?
T>2) Данные жёстко связаны с методами.
Как только в ФП появляются замыкания, мы получаем ровно такую же связь.
T>3) ООП провоцирует на создание большого количества лишних сущностей.
Здравствуйте, mrTwister, Вы писали:
T>Приватные данные есть не сами по себе, а появляются для конкретной реализации, и появляются они в том месте, где происходит реализация. То есть не приватные данные определяют реализацию, а наоборот.
Попробуй это обосновать.
Вообще, есть очень простой принцип, позволяющий не содавать в ООП бредовые конструкции. Он очень простой — если метод может быть вынесен за пределы класса, он должен быть вынесен за пределы класса. Его применение спасает практически от всех тех ужасов, которые ты расписал.
T>По поводу диспатча — диспатч нужен не типу, а конкретному алгоритму, этот тип использующему. Но при этом задается диспатч в типе.
Только в случае визитора или виртуальных методов. Которые нужны только если язык убогий и не предоставляет нормальных средств типа паттерн матчинга. Дихотомия ФП/ООП тут совершенно ортогональна, просто без специальных средств диспетчеризации ООП язык остается относительно пригодным, а вот ФП язык можно смело выкидывать на помойку.
T>То есть при проектировании типа нам надо иметь ввиду конкретный алгоритм.
Опять же нет. Конкретный алгоритм нужен только в случае использования виртуальных методов. А страшный и ужасный визитор как раз и предназначен для развязки алгоритмов и обрабатываемых.