getUserName(users, user -> user.getUserName());
Функциональное программирование настолько полезно и удобно, что, насколько я вижу, проникло во все современные распространённые языки.
Но не всё так радужно. Многие разработчики сопротивляются этому тектоническому сдвигу в нашем подходе к ПО. Честно говоря, сегодня трудно найти работу, связанную с JavaScript, которая не требует знания концепций ФП.
Функциональное программирование лежит в основе обоих доминирующих фреймворков: React (архитектура и односторонняя передача данных создавались с целью избежать общего изменяемого DOM) и Angular (RxJS — широко используемая по всему фреймворку библиотека utility-операторов, которые работают с потоками посредством функций высшего порядка). Redux и ngrx/store тоже являются функциональными.
Новичков может пугать подход ФП, и ради быстрого втягивания в работу кто-нибудь из вашей команды может предложить освоиться с кодовой базой, отказавшись от ФП.
Для менеджеров, незнакомых с самим ФП или его влиянием на современную экосистему программирования, такое предложение может показаться разумным. В конце концов, разве ООП не служит нам верой и правдой 30 лет? Почему не оставить всё как есть?
Давайте разберёмся с самой формулировкой. Что вообще означает «бан» ФП на уровне политики?
Что такое функциональное программирование?
Моё любимое определение:
Функциональное программирование — это парадигма использования чистых функций в качестве неделимых единиц композиции, с избеганием общего изменяемого состояния и побочных эффектов.
Чистая функция:
- Получая одни и те же входные данные, всегда возвращает один и тот же результат
- Не имеет побочных эффектов
То есть суть ФП сводится к:
- Программированию с функциями
- Избеганию общего изменяемого состояния и побочных эффектов
Сложив всё вместе, мы получаем разработку ПО со строительными блоками в виде чистых функций (неделимые единицы композиции).
К слову, Алан Кэй (основоположник современного ООП) считает, что суть объектно-ориентированного программирования заключается в:
- Инкапсуляции
- Передаче сообщений
Так что ООП просто ещё один способ избежать общего изменяемого состояния и побочных эффектов.
Очевидно, что противоположностью ФП является не ООП, а неструктурированное, процедурное программирование.
Язык Smalltalk (в котором Алан Кэй заложил основы ООП) одновременно объектно-ориентированный и функциональный, а необходимость выбирать что-то одно является чуждой и неприемлемой идеей.
То же самое верно и для JavaScript. Когда Брендана Эйха наняли разрабатывать этот язык, то основной его идеей было создать:
- Scheme для браузера (ФП)
- Язык, который выглядит как Java (ООП)
В JavaScript вы можете попытаться придерживаться какой-то одной парадигмы, но, к добру или нет, обе они неразрывно связаны. Инкапсуляция в JavaScript зависит от замыканий — концепции из ФП.
Наверняка вы уже применяете функциональное программирование, быть может даже не зная об этом.
Как НЕ использовать ФП
Чтобы избежать функционального программирования, вам придётся избегать использования чистых функций. Но тогда вы не сможете писать подобный код, потому что он может оказаться чистым:
const getName = obj => obj.name;
const name = getName({ uid: '123', name: 'Banksy' }); // Banksy
Давайте рефакторим код так, чтобы он больше не относился к ФП. Можно сделать класс с публичным свойством. Поскольку инкапсуляция не используется, было бы преувеличением назвать это ООП. Возможно, «процедурное объектное программирование»?
class User {
constructor ({name}) {
this.name = name;
}
getName () {
return this.name;
}
}
const myUser = new User({ uid: '123', name: 'Banksy' });
const name = myUser.getName(); // Banksy
Поздравляю! Только что мы превратили 2 строки кода в 11, а также привнесли возможность появления неконтролируемой внешней мутации. А что получили взамен?
Ну, вообще-то, ничего. Фактически, мы потеряли в универсальности и возможности значительной экономии кода.
Предыдущая функция getName() работала с любым входящим объектом. Новая функция тоже работает (потому что это JS и мы можем делегировать методы любым объектам), но получается гораздо неуклюжей. Можно позволить двум классам наследовать от обычного родительского класса, но это подразумевает наличие ненужных взаимоотношений между классами.
Забудьте о многократном использовании. Мы только что спустили его в унитаз. Так выглядит дублирование кода:
class Friend {
constructor ({name}) {
this.name = name;
}
getName () {
return this.name;
}
}
Старательный ученик с задней парты воскликнет: «Ну так создайте класс person!»
Тогда:
class Country {
constructor ({name}) {
this.name = name;
}
getName () {
return this.name;
}
}
«Но это же разные типы. Конечно, вы не можете применять метод класса person к стране!».
На это я отвечу: «А почему нет?»
Одним из невероятных преимуществ функционального программирования является тривиальная простота программирования обобщённого. Можно назвать это «обобщённость по умолчанию»: возможность написать одну функцию, которая будет работать с любым типом, удовлетворяющим его обобщённым требованиям.
Примечание для Java-программистов: речь не идёт о статической типизации. Некоторые ФП-языки имеют прекрасные системы статических типов, но всё же пользуются преимуществами структурированных типов и/или HKT(higher-kinded types).
Это очевидный пример, но получаемая с помощью этого трюка экономия объёма кода получается огромной.
Это позволяет библиотекам вроде autodux автоматически генерировать доменную логику для любого объекта, созданного из пары геттер/сеттер (и многих других). Также мы можем уменьшить объём кода доменной логики вдвое, а то и больше.
Больше никаких функций высшего порядка
Поскольку многие (но не все) функции высшего порядка пользуются преимуществами чистых функций для возврата одних и тех же значений при получении одних и тех же входных данных, вы не можете без возникновения побочных эффектов использовать функции вроде
.map(), .filter(), .reduce()
:const arr = [1,2,3];
const double = n => n * 2;
const doubledArr = arr.map(double);
Становится:
const arr = [1,2,3];
const double = (n, i) => {
console.log('Random side-effect for no reason.');
console.log('Oh, I know, we could directly save the output to the database and tightly couple our domain logic to our I/O. That will be fine. Nobody else will need to multiply by 2, right?');
saveToDB(i, n);
return n * 2;
};
const doubledArr = arr.map(double);
Покойся с миром, композиция функции. 1958–2018
Забудьте о point-free-композиции компонентов высшего порядка для инкапсулирования сквозной функциональности на страницах. Этот удобный, декларативный синтаксис превращается в табу:
const wrapEveryPage = compose(
withRedux,
withEnv,
withLoader,
withTheme,
withLayout,
withFeatures({ initialFeatures })
);
Вам придётся всё это вручную импортировать в каждый компонент, а то и хуже — погружаться в запутанную, негибкую иерархию с наследованием классов (которую большинство сознательных граждан, и даже (особенно?) канон объектно-ориентированного программирования справедливо признают антипаттерном).
Прощайте, промисы и async/await
Промисы — это монады. Технически, они относятся к теории категорий, но я слышал, что промисы всё же относятся к ФП, также потому что в Haskell они используются для обеспечения чистоты и ленивости (lazy).
Честно говоря, будет не так плохо избавиться от монад и функторов. Они гораздо проще, чем мы их выставляем! Я не просто так учу людей использовать
Array.prototype.map
и промисы до того, как рассказываю об общих концепциях функторов и монад.Знаете, как их применять? Значит, вы уже на полпути к пониманию функторов и монад.
Итак, чтобы избежать функционального программирования
- Не используйте популярные JavaScript-фреймворки и библиотеки (все они предадут вас ради ФП!)
- Не пишите чистые функции
- Не используйте многие из встроенных возможностей JavaScript: большинство математических функций (потому что они чистые), неизменяемые методы строк и массивов,
.map(), .filter(), .forEach()
, промисы или async/await - Пишите ненужные классы
- Удваивайте (или ещё больше увеличивайте) доменную логику, вручную набивая геттеры и сеттеры буквально для всего подряд
- Возьмите «читабельный, явный» императивный подход и запутайте доменную логику задачами отрисовки и сетевого ввода-вывода
И попрощайтесь с:
- Time travel-отладкой
- Простой разработкой фич undo/redo
- Надёжными, согласованными модульными тестами
- Заглушками и тестированием без D/I
- Быстрыми модульными тестами без зависимостей с сетевым вводом-выводом
- Маленькими кодовыми базами, удобными для тестирования, отладки и сопровождения
Избегаете функционального программирования? Без проблем.
Или погодите…
Комментарии (100)
awesomer
07.05.2018 18:21-1даже функции высокого порядка в Java. Так выглядит Java в 2018-м… Функциональное программирование настолько полезно и удобно, что, насколько я вижу, проникло во все современные распространённые языки.
Сформулировано впечатляюще.
Но… «Функции высокого порядка» умели еще Pascal и C, еще порядка полувека тому назад.
А возможно и более древние языки.
Проникло в современные языки, говорите?
Все новое — хорошо забытое старое.
sshikov
07.05.2018 20:04users.stream().map(user -> user.getUserName()) — какого тут типа результат? А теперь все тоже самое про C и Pascal расскажите. Не было полвека тому назад никакого Хиндли-Милнера в общедоступных языках. И в паскале его не было, и в C тоже. Вот в этом и разница.
0xd34df00d
08.05.2018 00:53+1users.stream().map(user -> user.getUserName()) — какого тут типа результат? А теперь все тоже самое про C и Pascal расскажите.
Я это вполне могу написать на плюсах, какое-нибудьusersRange.map([](const auto& user) { return user.getUserName(); })
, которое абсолютно синтаксической заменой дешугарится в структуру с шаблонным оператором-скобочки и становится валидным C++98. Зачем тут вообще Хиндли-Милнер?
Не было полвека тому назад никакого Хиндли-Милнера в общедоступных языках.
ML появился в 73-м году (через год после С, ага), так что всего через 5 лет это утверждение станет неверным даже формально.sshikov
09.05.2018 09:04Тем не менее, в упомянутых конкретных C и паскале его нет и сегодня. А C++ не упоминали. Да, возможно я тут формалист, но говорить что с тех пор ничего не изменилось тоже неправильно.
0xd34df00d
09.05.2018 18:33Так его (если вы о Х-М) и в С++ нет, а на нём аналогичные по смыслу, внешнему виду и так далее конструкции вполне себе пишутся, как демонстрирует пример выше.
Как формалист формалисту, я опровергаю другое утверждение.sshikov
09.05.2018 20:28Ну, давайте вернемся к началу:
>Но… «Функции высокого порядка» умели еще Pascal и C, еще порядка полувека тому назад. А возможно и более древние языки.
Мне в этом комментарии не понравилось то, что даже если можно написать на C или паскале частичное применение или композицию функций (мне лень проверять, но вполне допускаю, что можно), все равно в этих языках нет и не было возможности вывести тип той функции, которая получится. И результат в лучшем случае будет небезопасен. То есть, по сравнению даже с современной Java, ее лямбдами, и ее возможностями по созданию функций высших порядков (а они далеко не идеал), в Pascal и C 50 лет назад все было намного хуже.
Если же убрать отсюда вот эти конкретные языки, то эта фраза вообще не вызывала бы никаких вопросов, потому что лисп, и он 1958 года, т.е. старше очень многих. Ну и упомянутый вами ML.
При чем тут Х-М? Исключительно при том, что в дополнение к функциям высших порядков все-таки хочется иметь порою и статическую типизацию, и выведение типов. В вашем примере результат
usersRange.map([](const auto& user) { return user.getUserName(); })
какого типа будет? Нужно ли его будет явно записать, или его нам выведут? Если вы поменяете устройство usersRange, вся эта конструкция в C++ все еще будет работать?0xd34df00d
09.05.2018 21:54какого типа будет?
Статически известного. Какого — зависит от реализацииmap
(как и с Х-М, впрочем). Может,std::vector<std::string>
, может, тоже рейндж какой со строками.
Нужно ли его будет явно записать, или его нам выведут?
Напишетеauto
— выведут.
Если вы поменяете устройство usersRange, вся эта конструкция в C++ все еще будет работать?
Смотря как поменяю. Если совместимым образом, и если особенно вы нигде явно не требуете никаких типов, а полагаетесь на их вывод, то да.
Druu
10.05.2018 01:13Я это вполне могу написать на плюсах, какое-нибудь usersRange.map([](const auto& user) { return user.getUserName(); })
И с какого кода вы можете написать auto в плюсах? Как будут работать замыкания?
Зачем тут вообще Хиндли-Милнер?
Автор комментария тут, очевидно, подразумевал любой вывод типов ("Хиндли-Милнер" ведь звучит намного круче, чем просто "вывод типов", правда?). Тип user в рассматриваемом примере компилятор откуда берет?
aamonster
07.05.2018 22:59Паскаль и Си очень ограниченно умели возвращать функции из функций (для этого нужны или лямбды, или ещё какая поддержка от языка). Функцию можно было только вызвать и взять указатель на неё, а, к примеру, частичное применение функции сделать — опаньки.
(Хотя, конечно, можно эмулировать)Gryphon88
08.05.2018 15:12Если интересно, посмотрите «Functional C», Hartel & Muller, там рядом часто примеры на SML и С, читал, когда пытался понять причины хайпа. Можно использовать частичное применение, можно ад на шаблонах, но получится довольно многословно и временами грязновато.
aamonster
08.05.2018 15:30Глянул краем глаза (смотрел "partial application") — что-то на первый взгляд в книге нет даже очевидных для меня костылей, позволяющих сделать частичное применение (делаем аналог плюсового функционального объекта с чуть менее удобным синтаксисом), зато куча опасного вроде преобразования туда-сюда void*. Но всё равно спасибо за ссылку.
Gryphon88
08.05.2018 15:45
0xd34df00d
07.05.2018 18:50+4Примечание для Java-программистов: речь не идёт о статической типизации. Некоторые ФП-языки имеют прекрасные системы статических типов, но всё же пользуются преимуществами структурированных типов и/или HKT(higher-kinded types).
Я не очень понял противопоставление в этом предложении. Структурированные типы (а что это такое, кстати? ADT?) и HKT — это вполне себе элементы статической типизации, как и тайпклассы, как и ad-hoc polymorphism.Druu
10.05.2018 01:19Структурированные типы (а что это такое, кстати? ADT?)
Очевидно, структурный сабтайпинг (как в TS). TaPL, chapter 15.
amaksr
07.05.2018 18:59-6Вообще забавно наблюдать как хайпят ФП, промисы и прочие сомнительные фичи джаваскрипта, вызванные, в основном, ограничениями JS движка в плане асинхронных операций и многопоточности.
Помню как студентами мы делал лабы на ассемблере для MS DOS и аналогов IBM 360 с низкоуровневым доступом к периферийным устройстам. Так вот эта концепция промисов там уже была: при отправке в порты устройства команд, надо было оставить где-нибудь адрес колбека, который вызвался по завершении. Запрограммировать циклы таких операций можно было функциональным подходом и рекурсией, но было жутко неудобно. Наверное поэтому в любых ОС есть более высокоуровневые интерфейсы, при вызове которых процесс может приостанавливаеться до завершения, что позволяет программировать логику «в лоб».
Но ОС проектируют годы, а джаваскрипт за 10 дней.Chamie
07.05.2018 21:56+3А причём тут вообще JS?
Вообще забавно наблюдать как хайпят ФП… и прочие сомнительные фичи джаваскрипта
А причём тут джаваскрипт?
Так вот эта концепция промисов там уже была: при отправке в порты устройства команд, надо было оставить где-нибудь адрес колбека
А причём тут промисы? Промисы как раз и сделаны, чтобы вручную коллбэки не передавать.
Запрограммировать циклы таких операций можно было функциональным подходом и рекурсией, но было жутко неудобно.
Так это и в JS не приходится рекурсией решать.
Наверное поэтому в любых ОС есть более высокоуровневые интерфейсы, при вызове которых процесс может приостанавливаеться до завершения, что позволяет программировать логику «в лоб».
А если не нужно, чтобы процесс «спал»? А если нужно отправит 10 запросов, дождаться, пока вернётся хотя бы три, и что-то делать дальше? А если таких задач тысячи, то спавним тысячи процессов?amaksr
08.05.2018 00:29+2ФП-языки известны давно, но широко никогда не применялись. ФП-стиль можно использовать со многими языками, и это делалось, по мере целесообразности. Настоящий толчек в массы для ФП дал JS, но не потому что ФП это прямо всегда самый лучший подход, а потому что в JS иначе трудно писать сложную логику с последовательную вызовами IO. Реальная альтернатива в виде генераторов, и позже async/await на их основе появилась когда языку было уже 20 лет.
Я в принципе не против ни одного из этих подходов, в каждом есть что-то полезное. Но вот конкретно с JS уже не первый раз, когда проблемы дизайна языка прикрывают хайпом. Например коллбеки — хорошая вещь, но когда это единственно возможный способ работы с медленными функциями, то это проблема дизайна.
А чтобы не спавнить процессы на каждый из 10 запросов уже десятки лет как существуют функции типа posix select или poll, и по крайней мере есть выбор, спавнить или нет.0xd34df00d
08.05.2018 00:56Настоящий толчек в массы для ФП дал JS, но не потому что ФП это прямо всегда самый лучший подход, а потому что в JS иначе трудно писать сложную логику с последовательную вызовами IO.
Я не пишу на JS (да и не знаю его толком), но я не могу сказать, что JS дал толчок ФП в массы.
Примерно с тем же успехом толчок ФП в массы дал какой-нибудь питон несколько лет назад, когда некоторые люди внезапно решили, что map, fold и лямбды — это прям квинтэссенция ФП.
Chamie
09.05.2018 13:43Вы как-то хитро сравниваете язык (JS) с библиотеками и ОС, при этом почему-то записывая ограничения браузерного IO API в минусы самого языка. В JS вам тоже никто не мешает писать синхронные функции. В NodeJS даже блокирующий IO из коробки есть.
wert_lex
10.05.2018 06:18Настоящий толчек в массы для ФП дал JS
Ничего подобного. Настоящий толчёк ФП в массы дал некоторый снобистский хайп вокруг Haskell, Erlang и Scala. В JS можно (и иногда нужно) писать в ФП-стиле, но надо понимать, что в половине случаев это не даёт тех потрясающих плюшек, что дают взрослые ФП-языки.
А в массы они не выходили по причине того, что долгое время по-сути и не было проблем, для которых были необходимы ФП-концепции. Как минимум можно передать горячий привет многоядерности и параллелизму.
Akon32
07.05.2018 19:16Но ведь всё, что можно написать в парадигме ФП, можно написать и в ООП, в крайнем случае, наплодив объектов с методом apply(). Более того, жили же на java до введения лямбд.
И наоборот, любой объект можно переписать как функцию.
Не вижу смысла от ФП отказываться, даже в рамках примера. Так можно и от умножения отказаться, и говорить, как плохо. Война с мельницами какая-то.
0xd34df00d
08.05.2018 01:00+2Но ведь всё, что можно написать в парадигме ФП, можно написать и в ООП, в крайнем случае, наплодив объектов с методом apply(). Более того, жили же на java до введения лямбд.
Но лямбды — это не ФП, как и ФП — не лямбды. ФП — это про чистые функции, алгебру типов и всякое такое. Можно сказать, что плюсовые темплейты ближе к ФП, чем эти несчастные лямбды/мап/фолд в JS, Java и так далее.
Хотя, конечно, любители лиспа и производных со мной не согласятся, но про это есть известная шутка.
И наоборот, любой объект можно переписать как функцию.
Я бы посмотрел на переписывание какого-нибудь god object на чистых функциях с минимумом эффектов.sharikov_d
08.05.2018 08:47God object — пример плохо пахнущего кода. Хорошо структурированный код гораздо проще переписать на чистые функции. Вопрос только в том, нужно ли это?
sshikov
09.05.2018 22:30Тут уже упоминали фразу, что на любом языке можно писать, как на Фортране. Я подозреваю, что Фортран можно заменить на любой другой, и смысл не сильно изменится.
Почему реализации map/fold где-нибудь в js зачастую неполноценны? Потому что ленивости скажем нет, и в итоге где-то в цепочке могут сохраняться промежуточные результаты непонятного размера, просаживая производительность. Почему функции в java не совсем полноценные? Потому что чистоту мы не можем ни обеспечить, ни проверить, нет механизмов для этого.
Но это все не значит, что мы не можем думать о программах на js или java таким же способом, как делали бы это в идеальном ФП языке. И мне кажется, что ФП это больше про способ думать о коде, о композиции его из частей, о том, какие это должны быть части. И «эти несчастные лямбды/мап/фолд в JS, Java и так далее.» — ничто иное, как именно способ думать о коде иначе. Не иначе чем в плюсовых темплейтах, а иначе чем мы делали в этом же языке ранее.0xd34df00d
10.05.2018 00:45Про JS не знаю, а ниблеровские рейнджи-вьюшки в С++ вроде как вполне ленивы. На любом языке можно писать как на хаскеле, в конце концов.
Вопрос в том, о каком коде вы думаете и как именно вы думаете.
Когда я думаю о своём коде, я думаю в рамках типов. Нередко самое большое усилие для меня — написать сигнатуры, оставив undefined где надо, и потом тупо заполнять дырки, почти выключив мозг, чтобы оно тайпчекалось. Ну или generate definition, case split, make case, case split, obvious proof search, и в продакшен. А мапы-лямбды — так, инструмент. Можно и без них. В конце концов, вы же даже map id = id доказать не сможете, а в хаскеле это и не выполняется, потому что Hask is not a category.
Когда я думаю о чужом коде (или о своём через год), я смотрю на типы. Я не доверяю автору и его размышлениям о мапах-фолдах и чистоте, мне надо, чтобы компилятор мне что-то интересное рассказал, живёт ли эта функция в IO, нет ли там костылей вродеif (typeof(param) == "Kludgy") doKludge();
, и так далее.
Comdiv
07.05.2018 19:42+1Функциональное программирование — это парадигма использования чистых функций в качестве неделимых единиц композиции, с избеганием общего изменяемого состояния и побочных эффектов.
Функциональное программирование не просто избегает общего изменяемого состояния, а не содержит никакого изменяемого состояния — ни общего, ни частного. Если же, все-таки, изменяемое состояние есть, то речь идёт о смеси функционального и императивного прогаммирования.
Может определение и любимое, но оно, как минимум, неточное и ведёт к неверным выводам.
Если же учесть, что функциональная и императивная парадигмы на базовом уровне — это вопрос интерпретации, то можно прийти к заключению, что статья является упражнением в софистике. Ведь если идти до конца, то любую императивную программу можно интерпретировать как функциональную и запрет на функциональное программирование будет обозначать запрет на программирование вообще.
Но обычно имеют ввиду не это.
А программировать надо так, как навязывает язык и его расширения-библиотеки, иначе всё время придётся бороться с ветряными мельницами. Только космических выводов из этого делать, пожалуй, не стоит. Большинство ЯВУ, начиная с Фортрана, были синтетическими.0xd34df00d
08.05.2018 01:03Если же, все-таки, изменяемое состояние есть, то речь идёт о смеси функционального и императивного прогаммирования.
В правильном™ ФП нет никакого изменяемого состояния, и даже все эти монадыST
иIO
— это просто возможность программисту оперировать чистыми функциями для построения чистой композиции этих самых функций, оперирующих нечистым миром (или нечистым состоянием) при помощи рантайма. В конце концов,IO
— это всего лишьState
, тегированный#RealWorld
.
Но да, вы абсолютно правы, на базовом уровне это вопрос интерпретации. В конце концов, можно написать библиотечкуIO
-функций таких, что переписывание кода с произвольных плюсов или, не знаю, ассемблера будет по большей части тупым синтаксическим преобразованием.
viklequick
07.05.2018 20:03«На любом языке программирования можно писать на Фортране» (С) ;-)
Хороший язык программирования помогает программистам писать хорошие программы. Ни один из языков программирования не может запретить своим пользователям писать плохие программы.
(идет за чипсами и колой)
BasicWolf
07.05.2018 20:40+3Давайте рефакторим код так, чтобы он больше не относился к ФП. Можно сделать класс с публичным свойством. Поскольку инкапсуляция не используется, было бы преувеличением назвать это ООП.
И в очередной раз инкапсуляцию связывают со способностью языка явным образом помечать поля класса как «private». Не об этом же инкапсуляция!
В вашем примере:
class User { constructor ({name}) { this.name = name; } ... }
name — вполне себе инкапсулированное поле.k12th
07.05.2018 22:27Существует мнение (я его не разделяю), что инкапсуляция это и есть сокрытие.
https://ru.wikipedia.org/wiki/Инкапсуляция_(программирование)
приводит к другому распространённому заблуждению — рассмотрению инкапсуляции неотрывно от сокрытия. В частности, в сообществе С++ или Java принято рассматривать инкапсуляцию без сокрытия как неполноценную. Однако, некоторые языки (например, Smalltalk, Python) реализуют инкапсуляцию в полной мере, но не предусматривают возможности скрытия в принципе.
s-kozlov
08.05.2018 06:07+2Существует еще мнение, что публичное поле — это всегда нарушение инкапсуляции, а приватное поле с тривиальными геттерами и сеттерами — нет. Карго-культы — они такие.
0xd34df00d
08.05.2018 07:24В случае С++ ещё круче приватное поле с геттерами и сеттерами вида
const T& getFoo() const; T& setFoo();
Бессмысленно чуть более чем. Разве что, факт обращений к полю логгировать.s-kozlov
08.05.2018 11:00Смысл как раз есть (да, то же логирование в будущем захотим добавить), но инкапсуляция может быть нарушена.
0xd34df00d
08.05.2018 15:05Но то же устанавливаемое значение вы уже залогировать не сможете.
Равно как и не сможете завернуть поле в мьютекс. Или вообще генерировать его лениво из закешированных данных, например.
DeadKnight
08.05.2018 18:04Прелесть геттеров и сеттеров программист начинает ценить, когда 10-й раз переделывает интерфейс из-за того, что возникает необходимость изменить поведение объекта на установку или запрос какого-то поля.
0xd34df00d
08.05.2018 18:32В случае процитированного кода вам интерфейс всё равно придётся менять.
DeadKnight
08.05.2018 21:06Не факт. Если придерживаться правила, что геттеров и сеттеров не имею только простые структуры, а доступ к членам более/менее сложных классов осуществляется только через геттеры и сеттеры, то это сильно упрощает жизнь в будущем. Например в будущем может понадобится, чтобы обновление поля дублироваллось соответствующей записью в БД, или при обновлении нужно будет добавить верификацию данных.
0xd34df00d
08.05.2018 21:29Там ссылки возвращаются. Использование таких геттеров выглядит как
bar.setFoo() = 10;
Негде тут в БД ничего писать без модифицирования интерфейса.
VolCh
10.05.2018 06:32Ужас геттеров и сеттеров программист начинает «ценить», когда 10-й раз обнаруживает, что на запрос каких-то свойств, происходит мутация других свойств, а на установку свойства его установка не происходит, умтановка происходит в другое значение или происходит установка и других свойств. Геттеры и сеттеры с тупым поведением практически практически (два «практически» не ошибка) бесполезны, а со сложным — нарушают ожидаемый контракт. Единственное разумное для них ожидаемое поведение кроме непосредственной работы со свойствами — генерация ошибок при невалидном состоянии свойств или попытке их привести в таковое.
evseev
09.05.2018 06:52Я не знаю как Smalltalk, а Python предусматривает возможность сокрытия полей. Нужно добавить "__" перед именем.
k12th
09.05.2018 10:57Это разве не соглашение? Ведь всё еще можно обратиться к такому полю, зная его имя, не так ли? (я не помню, писал на питоне чуть не 10 лет назад).
fairwind
07.05.2018 20:40«Но это же разные типы. Конечно, вы не можете применять метод класса person к стране!».
На это я отвечу: «А почему нет?»Действительно, почему нет?
Узнайте имя у страны, потом фамилию и паспортные данные.
Смело двигайтесь дальше — извлекайте корни из любого поля любого объекта. Если у какого-то объекта корень из ip-адреса не извлекается, то это его проблемы.
Зато строчек кода мало!s-kozlov
08.05.2018 06:12При чем тут
городская баняфамилия страны и корни из любого поля?
Речь идет об обычном duck typing:
def quacker(duck: {def quack(value: String): String}) { println (duck.quack("Quack")) }
Это Scala, тут строгая статическая типизация, но не нужно городить trait ради одного случая (особенно если это вообще чужие классы и trait там не навешаешь чисто физически).fairwind
08.05.2018 10:40У автора единственная реализация getName, и утверждается, что она может работать с любым объектом. Это не так (если ожидать корректную работу функции и наличие некоторого смысла).
У вас же описан частный случай реализации для определенного объекта, и я не понимаю, почему вы мне возражаете.s-kozlov
08.05.2018 11:09У автора getName возвращает свойство name у объекта. У страны может быть name (строка), у человека — тоже. У нас есть код, который может работать с «чем-то, у чего есть имя». Problems?
fukkit
08.05.2018 15:56Согласен, тоже резануло. У человека — имя, у страны — наименование, ничего общего в реальных моделях.
Стандартная проблема фпшника: на яблоках и пальцах всё красиво считается и другим доказывается, а в реальных задачах без монад, переусложнений, кровавых соплей и неразумной траты ресурсов концы не сходятся.
SirEdvin
07.05.2018 20:52Мне кажется, что дело не в ФП. Не скажу за react, но в angular используются реактивное программирование, что значительно отличается от функционального. И самый большой минус, что используется далеко не во всем коде, поэтому следить за тем, что observable, а что нет и какие изменения к чему приводят новичкам (например, мне) бывает сложно.
taujavarob
07.05.2018 21:27Верно.
Всё отлично когда «чисто».
Как только смешивают в одном приложении (то есть коде) ФП, ООП, реактивное — то надо, похоже, смотреть в оба и не допустить «мути» — то есть «взбалтывания» всех этих парадигм в одном «флаконе» — что на практике постоянно и происходит ибо это уже практика — то есть «жизнь кода» вне академических «песочниц».
s-kozlov
08.05.2018 06:14Когда это вдруг реактивное программирование перестало быть функциональным?
Может, значительно отличается от функционального то, что в ангуляре называют реактивным?
IvanNochnoy
07.05.2018 21:54Главная проблема функционального программирования, что его адепты не могут отличить монаду от моноида. Из за этого ФП туго идёт в массы, так как теперь все считают, что ФП — это когда цикл for заменяют на map-reduce.
sshikov
07.05.2018 22:30>ФП — это когда цикл for заменяют на map-reduce.
А разве это не так? :) Честно говоря, я не вижу ничего плохого в замене циклов на map-reduce, при одном простом условии — что это делается осознанно, понимая, что мы приобретаем, и чем за это платим. При этом, заметьте, человек, способный проделать такую замену, уже не ограничен javascript и фронтендом, ему можно завтра дать в руки Hadoop — и он не потеряется там совсем.
IvanNochnoy
07.05.2018 22:03+2И, да, использовать JavaScript как пример функционального стиля, это всё равно, что показывать красоту фотомодели на костылях после автомобильной аварии.
slavap
08.05.2018 05:35> «Но это же разные типы. Конечно, вы не можете применять метод класса person к стране!»
Что мешает объявить интерфейс IName и реализовать у классов для которых это имеет смысл?s-kozlov
08.05.2018 06:16А если это чужие классы?
VolCh
08.05.2018 06:47Проверять реализацию интерфейса по наличию метода getName(), а не по наличию строчки типа implements IName :)
slavap
08.05.2018 07:01А если нет obj.name? Как чистая функция отработает :-)?
0xd34df00d
08.05.2018 07:25+1Не скомпилируется.
slavap
08.05.2018 07:28на каком языке?
0xd34df00d
08.05.2018 07:33На функциональном.
slavap
08.05.2018 07:36А здесь?
const getName = obj => obj.name;
const name = getName({ uid: '123', name1: 'Banksy' });0xd34df00d
08.05.2018 07:38А здесь я не знаю, потому что я не знаю, что это за язык. Но явно не ML-подобный.
Собственно, поэтому не имеет особого смысла рассматривать всю эту функциональную ерунду в отрыве от типизации, которая обеспечивает лютую долю удобства. Зачем вам чистые функции, если вы их чистоту можете гарантировать только пристальным вглядыванием?
0xd34df00d
08.05.2018 07:26В случае ФП с Хиндли-Милнером никто вам не мешает определить тайпкласс в вашем модуле.
donRumatta
08.05.2018 17:38Зависит от языка. В шарпе приравнял к dynamic и вперед, вызывай, что хочешь. Только кому это нужно, когда скатываешься в итоге, как ниже заметили, к «пристальному вглядыванию».
theonlymirage
08.05.2018 05:49+1Функциональное программирование — это парадигма использования чистых функций в качестве неделимых единиц композиции, с избеганием общего изменяемого состояния и побочных эффектов.
Чистая функция:
Получая одни и те же входные данные, всегда возвращает один и тот же результат
Как быть с random? Эта функция получает одинаковые данные (интервал чисел), но всегда возвращает случайное число из заданного интервала. Получается она не чистая и не может быть использована в ФП по определению выше.s-kozlov
08.05.2018 06:17-1Где вы видели random, возвращающий именно случайное число?
VolCh
08.05.2018 06:59+1Таковыми можно считать функции, использующие аппаратные средства типа шумов в аналоговых трактах или квантовые флуктуации.
s-kozlov
08.05.2018 11:21-1В таких случаях упоротые ФПшники передают состояние «мира» в функцию и возвращают новое состояние.
Jedi_PHP
08.05.2018 14:18+1Эм… но почему упоротые? Random = во внешнем мире произошло какое-то (псевдо)случайное событие, и его состояние изменилось. Что вас собственно смущает?
s-kozlov
08.05.2018 17:03Меня смущает, что абстракция «внешний мир», а также передача соответствующего инстанса туда-сюда — чистой воды костыль, вызванный не какой-то реальной необходимостью, а непременным желанием любой ценой подогнать суровую действительность под чистое ФП.
Упоротыми я считаю радикалов, которые не хотят признать, что ни ФП, ни ООП, ни молоток не являются наилучшими инструментами во всех ситуациях.Jedi_PHP
08.05.2018 17:44+1> абстракция «внешний мир»… — чистой воды костыль… вызванный… непременным желанием любой ценой подогнать суровую действительность
Нет. Это, если хотите, часть соглашения о чистых функциях: есть некое внешнее хранилище состояний World, к которому можно обращаться только через монаду IO. Если сильно упростить — то функция, для работы которой монада IO не нужна, которая сама по себе ничего во внешнем мире изменить не способна — считается чистой, и ее можно лениво выполнять, безопасно параллелить итд. Как видите, тут «внешний мир» — хорошая, годная, даже необходимая абстракция.
> считаю радикалов, которые не хотят признать, что ни ФП, ни ООП, ни молоток не являются наилучшими инструментами
А такие вообще встречаются в дикой природе?s-kozlov
09.05.2018 19:14Как видите, тут «внешний мир» — хорошая, годная, даже необходимая абстракция.
«Тут» — это в рамках ФП всё-таки.
А такие вообще встречаются в дикой природе?
К сожалению, довольно часто.VolCh
10.05.2018 06:43«Внешний мир» — хорошая абстракция для любой (наверное) парадигмы программирования. Правила работы с ним могут быть разными, могут реализовываться на уровне синтаксиса или семантики ЯП, пользовательского кода или соглашений, но само наличие правил (с какими-то гарантиями их ненарушения, конечно) очень помогают при разработке сложных систем, упрощая контроль над тем, что делает та или иная программная единица без изучения её кода. Простое соглашение типа «используем глобальные переменные исключительно на глобальном уровне в пределах одного файла» очень упрощает жизнь.
0xd34df00d
08.05.2018 18:52Это не костыль, да и передача не явная и не торчит везде.
Просто это правда удобно, когда вы можете посмотреть на один лишь тип функции и сразу сделать выводы, что она не полезет на сервер, не начнёт читать файлы, не сохранит что-то в БД, не будет использовать источники недетерминированности для своего результата.
Ну и внезапно оказывается, что и тестировать такой код сильно легче, например.s-kozlov
09.05.2018 19:170xd34df00d
09.05.2018 21:58Я не согласен с цитатой (и с окружающим её контекстом).
Да, научиться программировать на ФП с монадками и этим всем действительно требует больших усилий, у них крутая кривая обучения. Но после того, как вы научились, мозг можно включать всё реже, и это приятно. А что мозг практически не нужно включать для отладки, потому что отладка нужна очень нечасто — приятно вдвойне.
Не рассказывайте только авторам таких статей про зависимые типы, они ж совсем изойдут.s-kozlov
10.05.2018 05:58А вы всю статью прочитайте, не поленитесь. Там как раз ниже того куска следующее:
Иронично, но Amr Sabry (мой консультант в PhD) является со-автором одной из таких статей. Он пытался реализовать семейство языков miniKanren Дэна Фридмена, но так и не смог понять, каким образом сконструировать монады. Он попросил помощи у Олега Киселева, человека, который возможно лучше всех разбирается в системе типов Haskell. И, если вы не знаете, Amr Sabry, вероятно, самый осведомленный в мире человек по теме чистых функциональных языков программирования. Они стали соавторами целой статьи после того, как с помощью Олега, им все-таки удалось реализовать желаемое. Ирония в том, что у Дена Фридмана не возникло вообще никаких проблем с реализацией этого куска кода на Scheme.
VolCh
08.05.2018 06:53random конечно не является чистой функцией. В лучшем (для чистоты) случае это функция со своим внутренним состоянием, в худшем — получает энтропию из физического мира.
0xd34df00d
08.05.2018 07:32+3
taujavarob
08.05.2018 21:27Получается она не чистая
Она не чистая. Да и любая функция ввода/вывода или обращения к базе или читающая файл или пишущая в файл — не чистая.Chamie
09.05.2018 13:58Ну, почему же? Если состояние базы/файла — это часть и входных параметров, и возвращаемого значения (если она его меняет), то вполне может быть чистой. Как редьюсеры в Redux'е.
VolCh
10.05.2018 06:47Нет, чистая функция работы с базой или файлом возвращает новую базу или файл, а не меняет переданные. :) Редьюсер в редакс не меняет стейт, он возвращает новый. Меняет стейт грязный dispatch, который передаёт редьюсеру текущий стейт, получает новый и заменяет текущий на новый в сторе.
evseev
09.05.2018 07:09Функция random возвращает число по одному и тому-же закону распределения. Сколько-бы вы ее раз не вызвали закон останется прежним.
s-kozlov
09.05.2018 19:18Чистая функция должна быть детерминированной.
VolCh
10.05.2018 06:59Функции random, да и вообще любые подпрограммы в реальных вычсистемах детерминированы. Отличие чистых функций от нечистых в плане детерминированности на практике в том, может ли программист контролировать все параметры функций при её вызове или она под капотом имеет дополнительные, внешние и внутренние состояния.
s-kozlov
10.05.2018 09:47Детерминированность — необходимое, но не достаточное свойство чистой функции.
VolCh
10.05.2018 06:55Термин «закон распределения» в контексте функции random обычно относят к случайным величинам, но в подавляющем большинстве случаев функция random к ним отношения не имеет даже в теории.
theonlymirage
08.05.2018 06:24Где вы видели random, возвращающий именно случайное число?
Хорошо, пусть это псевдослучайное число или даже другая функция, которая при каждом вызове возвращает следующее значение некой последовательности чисел (вспоминаем python, его yield, итераторы и генераторы). Передавая одни и те же входные данные, будем получать разные результаты. Правильно ли я понял, эти функции не чистые по определению выше и не могут быть использованы в ФП?
P.S. Немного промахнулся в ответе на сообщение s-kozlov0xd34df00d
08.05.2018 07:35+1вспоминаем python, его yield, итераторы и генераторы
Правильный аналог yield, итераторов и генераторов в ФП — это (бесконечные ленивые) списки, по большому счёту. И тогда функция, принимающая сид и производящая таковой список, вполне себе будет чистой.
Собственно, если к спискам относиться не как к структуре данных, а как к управляющей структуре, то всё встаёт на свои места.
Druu
10.05.2018 01:08Предыдущая функция getName() работала с любым входящим объектом.
Такую функцию в указанном виде можно написать только при наличии структурного сабтайпинга (если не учитывать динамику). При чем тут функциональное программирование, если 99% функциональных языков такового не имеют?
Промисы — это монады.
На практике промисы и async/await НЕ реализованы как монады.
У автора каша в голове, смешались вместе кони, люди и все остальное..
taujavarob
10.05.2018 10:17У автора каша в голове, смешались вместе кони, люди и все остальное..
… "и залпы тысячи орудий" вбивают в головы людей
Монад неслыханных идей…
Alozar
Это шутка такая?
Может хватит уже вести вечный холивар на тему «ООП отстой! Нет ФП отстой!», а понять наконец что для своей задачи свой инструмент. Доведение до абсурда ещё ни кого не приводило к хорошему результату.
AnutaU
Доведение до абсурда в этой статье явно намеренное.
maxzh83
We need to go deeper… Где в статье про линзы? Без них никуда в современном ФП
0xd34df00d
Про кубическую теорию типов.