Интерфейсы против функций
От: varenikAA  
Дата: 05.11.20 02:38
Оценка:
Допустим, у нас есть сервис, который должен рассылать сообщения.
public Notificator(IEmailSender sender)

Мы внедряем зависимость через интерфейс
Теперь у нас связаны три сушности

    EmailSender
IEmailSender 
    Notificator


Отныне и навеки, так сказать.

Если же, сделать так:
type Notificator (sender: string -> string -> string -> bool) =
    if sender("guest@local", "hello", "who are you?") then


Получается, что функции высшего порядка делают код чище и проще, и интерфейсы тут как бы не особо уже нужны.
Достаточно набора функций, при наличии частичного применения и различные декораторы также становятся не нужны
ServiceUtils
    Notificator
☭ ✊ В мире нет ничего, кроме движущейся материи.
Re: Интерфейсы против функций
От: vsb Казахстан  
Дата: 05.11.20 03:45
Оценка: +1
Не вижу в чём тут чистота. По сути ты сначала дал именование этому интерфейсу, а потом убрал именование. По-мне стало только хуже. Это какое-то движение в сторону JS получается, а не чистота.
Re: Интерфейсы против функций
От: SomeOne_TT  
Дата: 05.11.20 03:45
Оценка:
Здравствуйте, varenikAA, Вы писали:

AA>Получается, что функции высшего порядка делают код чище и проще, и интерфейсы тут как бы не особо уже нужны.

AA>Достаточно набора функций, при наличии частичного применения и различные декораторы также становятся не нужны
AA>
AA>ServiceUtils
AA>    Notificator
AA>


Да (ты этого ответа ждал?).
Re: Интерфейсы против функций
От: scf  
Дата: 05.11.20 06:33
Оценка:
Здравствуйте, 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 в нотификаторе, чем "клей", преобразующий функции в коде сборки программы из независимых, изолированных функций.
Re[2]: Интерфейсы против функций
От: varenikAA  
Дата: 05.11.20 07:46
Оценка:
Здравствуйте, vsb, Вы писали:

vsb>Не вижу в чём тут чистота. По сути ты сначала дал именование этому интерфейсу, а потом убрал именование. По-мне стало только хуже. Это какое-то движение в сторону JS получается, а не чистота.


Имя переменной наделено смыслом и связано с типом. Только в случае интерфейса описание типа хранится отдельно от места использования.
☭ ✊ В мире нет ничего, кроме движущейся материи.
Re[3]: Интерфейсы против функций
От: vsb Казахстан  
Дата: 05.11.20 07:57
Оценка:
Здравствуйте, varenikAA, Вы писали:

vsb>>Не вижу в чём тут чистота. По сути ты сначала дал именование этому интерфейсу, а потом убрал именование. По-мне стало только хуже. Это какое-то движение в сторону JS получается, а не чистота.


AA>Имя переменной наделено смыслом и связано с типом.


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

AA>Только в случае интерфейса описание типа хранится отдельно от места использования.


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

type Notificator (sender: email: string -> subject: string -> body: string -> sent successfully: bool) = ...


Хотя в этом случае получается какая-то программа в одну строчку, где слишком много информации сконцентрировано. Всё равно первый вариант лучше. А то, что он хранится отдельно от места использования — ну храни рядом с местом использования, не вижу проблемы.
Отредактировано 05.11.2020 7:58 vsb . Предыдущая версия .
Re[3]: Интерфейсы против функций
От: klopodav  
Дата: 05.11.20 08:19
Оценка: +1
vsb>>Не вижу в чём тут чистота. По сути ты сначала дал именование этому интерфейсу, а потом убрал именование. По-мне стало только хуже. Это какое-то движение в сторону JS получается, а не чистота.

AA>Имя переменной наделено смыслом и связано с типом. Только в случае интерфейса описание типа хранится отдельно от места использования.


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

И еще — пусть, например, есть несколько разных несовместимых реализаций одного интерфейса. Конкретный пример — какой-нибудь интерфейс IArchiver для действий "архивация файлов", в этом интерфейсе методы CreateArchive и AddFileToArchive, реализации — ZipArchiver и RarArchiver.
Если это же делать тупо функциями — есть риск накосячить: одну функцию в процессе использования взять соответствующую одной реализации, а другую, взаимоувязанную с ней — соответсвующую другой реализации.

В общем, это была бы равноценнная замена только для для интерфейсов, состоящих из одной функции. Для более сложных интерфейсов — нет (можно, конечно, нагородить структуры из функций, но это получились бы те же интерфейсы, только вид сбоку)
Re: Интерфейсы против функций
От: gyraboo  
Дата: 05.11.20 08:44
Оценка:
Здравствуйте, varenikAA, Вы писали:

AA>Получается, что функции высшего порядка делают код чище и проще, и интерфейсы тут как бы не особо уже нужны.


Я вот тоже последнее время задаюсь вопросом, что если функциональное программирование лучше (в частности, для написания распределенных и многопоточных задач), то как в нём реализовать SOLID, GRASP, ООП-паттерны и прочие достижения ООП. Или же есть какая-то замена этим подходам, если да, то какая? В плане написания полноценного бизнес-приложения, как это делается например на Java/Spring. Пока что те книги, что я читал по ФП, полной картины не дают, и в своей работе ФП я пока применяю эпизодически, внутри классических ООП-обёрток и архитектуры, построенной по принципам SOLID и Ко. Возможно, в мире ФП пока эти принципы не выкристаллизовались ещё, или я просто не знаю где искать?
Re[4]: Интерфейсы против функций
От: varenikAA  
Дата: 05.11.20 09:44
Оценка:
Здравствуйте, klopodav, Вы писали:

K>В общем, это была бы равноценнная замена только для для интерфейсов, состоящих из одной функции.


Совершенно верно, IEmailSender в Asp.Net Core именно такой интерфейс.
Проблема случается когда такой интерфейс лежит в неподходящей библиотеке. В данном случае в 3.1 aspnet.core
соотвественно о совместимости реализации можно забыть.

Для более сложных случаев в ЯП типа F# можно создать тип/алис:

type SmtpOptions = {
 host : string
 port : int    
}

type IEmailSender : options : SmtpOptions -> mail : MailMessage -> Result
☭ ✊ В мире нет ничего, кроме движущейся материи.
Re[5]: Интерфейсы против функций
От: klopodav  
Дата: 05.11.20 10:42
Оценка:
AA>Для более сложных случаев в ЯП типа F# можно создать тип/алис:
AA>

AA>type SmtpOptions = {
AA> host : string
AA> port : int    
AA>}

AA>type IEmailSender : options : SmtpOptions -> mail : MailMessage -> Result
AA>


Не, для более сложных случаев понадобилось бы еще что-то такое:

type PackOfFunctions = {
 method1 : функция(набор параметров 1...)
 method2 : функция(набор параметров 2...)    
 method3 : функция(набор параметров 3...)    
}


И это тот же интерфейс, только вид сбоку
Re[2]: Интерфейсы против функций
От: mrTwister Россия  
Дата: 05.11.20 14:30
Оценка: 6 (1)
Здравствуйте, gyraboo, Вы писали:

G>Я вот тоже последнее время задаюсь вопросом, что если функциональное программирование лучше (в частности, для написания распределенных и многопоточных задач), то как в нём реализовать SOLID, GRASP, ООП-паттерны и прочие достижения ООП.


Практически все ООП паттерны и прочие "достижения" нужны только для того, чтобы решать проблемы привнесенные самим ООП. Из-за отсутствующей функциональной композиции начинаются пляски с декораторами, стратегиями и прочим.
лэт ми спик фром май харт
Re[3]: Интерфейсы против функций
От: gyraboo  
Дата: 05.11.20 14:51
Оценка: +1
Здравствуйте, mrTwister, Вы писали:

G>>Я вот тоже последнее время задаюсь вопросом, что если функциональное программирование лучше (в частности, для написания распределенных и многопоточных задач), то как в нём реализовать SOLID, GRASP, ООП-паттерны и прочие достижения ООП.


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


А есть примеры исходников проектов, написанных полностью в функциональном стиле, решающие типичные задачи enterprise? Т.е. наличие некоей предметной области со сложными отношениями между сущностями, слой бизнес-операций, слой персистенции, сквозной функционал логирования, безопасности, транзакционности и т.д. Задачи типа многопоточности и распределённости не беру, т.к. тут всё понятно, ФП в этом изначально сильна.
И ещё вопрос — насколько полностью ФП ПО поддерживаемо? Так же просто ли его читать, как "чистый" ООП-шный код? Просто ли его отлаживать или там всё залямбдено по самую крышу, что дебаггер не позволяет по-человечески дебажить? Есть ли сформированные стереотипы и шаблонные решения, когда ты видя большой кусок кода, понимаешь что это за шаблон и не вникаешь во все детали реализации?
Отредактировано 05.11.2020 14:59 gyraboo . Предыдущая версия . Еще …
Отредактировано 05.11.2020 14:52 gyraboo . Предыдущая версия .
Re[4]: Интерфейсы против функций
От: mrTwister Россия  
Дата: 05.11.20 15:38
Оценка: +1
Здравствуйте, klopodav, Вы писали:

K>И еще — пусть, например, есть несколько разных несовместимых реализаций одного интерфейса. Конкретный пример — какой-нибудь интерфейс IArchiver для действий "архивация файлов", в этом интерфейсе методы CreateArchive и AddFileToArchive, реализации — ZipArchiver и RarArchiver.

K>Если это же делать тупо функциями — есть риск накосячить: одну функцию в процессе использования взять соответствующую одной реализации, а другую, взаимоувязанную с ней — соответсвующую другой реализации.

Пример хороший, валидный. Без использования интерфейсов и их суррогатов в чисто-функциональном стиле, можно было бы записать так, чтобы generic функция CreateArchive (параметризованая типом архива) возвращала generic функцию AddFileToArchive, параметризованную тем же типом архива. И это было бы логично, перед тем, как добавлять файлы в архив, этот архив сначала надо создать.

Но нюанс в том, что сущностей, которые бы хорошо описывались интерфейсами со многими методами исчезающе мало. Хорошо, это значит, без нарушения interface segregation principle.
лэт ми спик фром май харт
Re[4]: Интерфейсы против функций
От: mrTwister Россия  
Дата: 05.11.20 16:03
Оценка: 8 (2) +2
Здравствуйте, 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. В результате программист вместо решения задачи из предметной области воюет с визиторами и декораторами.

В результате, ФП проект со временем менее подвержен превращением в кашу. В фп стиле легче писать правильно и труднее говнокодить.
лэт ми спик фром май харт
Отредактировано 05.11.2020 16:04 mrTwister . Предыдущая версия .
Re: Интерфейсы против функций
От: Muxa  
Дата: 05.11.20 18:38
Оценка:
AA>Мы внедряем зависимость через интерфейс
AA>Теперь у нас связаны три сушности
EmailSender вообще никак не связан с notifier’ом.
А Notifier можно использовать и без этого конкретного EmailSender’а.
Re[2]: Интерфейсы против функций
От: varenikAA  
Дата: 06.11.20 01:25
Оценка:
Здравствуйте, Muxa, Вы писали:

M>А Notifier можно использовать и без этого конкретного EmailSender’а.


На самом деле с интерфейсами проблем много больше,
Одна из них в том, что реализация тянет за собой сборку с интерфейсом. "Хороший пример" asp.net core 3.1. интерфейс лежит в Microsoft.AspNetCore.Identity.UI.dll
Т.е. реализация будет зависит не только от dotnet core 3.1. но и еще от всего asp.net
если использовать функцию, мы не зависим ни от одной сборки.
А это уже правила хорошей разработки по Роберту Мартину.
☭ ✊ В мире нет ничего, кроме движущейся материи.
Re[4]: Интерфейсы против функций
От: varenikAA  
Дата: 06.11.20 09:48
Оценка:
Здравствуйте, gyraboo, Вы писали:


G>А есть примеры исходников проектов, написанных полностью в функциональном стиле, решающие типичные задачи enterprise?


☭ ✊ В мире нет ничего, кроме движущейся материи.
Re: Интерфейсы против функций
От: yenik  
Дата: 08.11.20 12:32
Оценка:
AA>Достаточно набора функций,

А если набор функций — это 10 функций? Не легче ли инжектировать 1 интерфейс?
Re[3]: Интерфейсы против функций
От: yenik  
Дата: 08.11.20 12:42
Оценка: 1 (1)
AA>Одна из них в том, что реализация тянет за собой сборку с интерфейсом.

Определяй интерфейс в той же сборке, и не потянет.

AA>если использовать функцию, мы не зависим ни от одной сборки.


Не факт. Если заменить myTypeInstance.DoThings() на doThings(myTypeInstance), тогда то на то и выходит.
Вызов метода экземпляра класса — это тот же вызов функции, только экземпляр передаётся в виде неявного первого параметра.
А статические методы и вовсе ничем от функций не отличаются.
Re[2]: Интерфейсы против функций
От: samius Япония http://sams-tricks.blogspot.com
Дата: 08.11.20 15:20
Оценка:
Здравствуйте, yenik, Вы писали:

AA>>Достаточно набора функций,


Y>А если набор функций — это 10 функций? Не легче ли инжектировать 1 интерфейс?


Интерфейс нужно реализовать, что бы его инжектировать. А 10 функций можно взять готовых или скомбинировать их неким образом.
Более того, 10 функций можно заменить структурой с 10ю полями и в отношении количества инжектируемых функций такая структура будет ничем не хуже 1-го интерфейса. А в некотором отношении даже лучше, т.к. структуры не нужно реализовывать, достаточно лишь инициировать члены.
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.