Comments 113
Может хватит уже вести вечный холивар на тему «ООП отстой! Нет ФП отстой!», а понять наконец что для своей задачи свой инструмент. Доведение до абсурда ещё ни кого не приводило к хорошему результату.
даже функции высокого порядка в Java. Так выглядит Java в 2018-м… Функциональное программирование настолько полезно и удобно, что, насколько я вижу, проникло во все современные распространённые языки.
Сформулировано впечатляюще.
Но… «Функции высокого порядка» умели еще Pascal и C, еще порядка полувека тому назад.
А возможно и более древние языки.
Проникло в современные языки, говорите?
Все новое — хорошо забытое старое.
>Но… «Функции высокого порядка» умели еще Pascal и C, еще порядка полувека тому назад. А возможно и более древние языки.
Мне в этом комментарии не понравилось то, что даже если можно написать на C или паскале частичное применение или композицию функций (мне лень проверять, но вполне допускаю, что можно), все равно в этих языках нет и не было возможности вывести тип той функции, которая получится. И результат в лучшем случае будет небезопасен. То есть, по сравнению даже с современной Java, ее лямбдами, и ее возможностями по созданию функций высших порядков (а они далеко не идеал), в Pascal и C 50 лет назад все было намного хуже.
Если же убрать отсюда вот эти конкретные языки, то эта фраза вообще не вызывала бы никаких вопросов, потому что лисп, и он 1958 года, т.е. старше очень многих. Ну и упомянутый вами ML.
При чем тут Х-М? Исключительно при том, что в дополнение к функциям высших порядков все-таки хочется иметь порою и статическую типизацию, и выведение типов. В вашем примере результат
usersRange.map([](const auto& user) { return user.getUserName(); })
какого типа будет? Нужно ли его будет явно записать, или его нам выведут? Если вы поменяете устройство usersRange, вся эта конструкция в C++ все еще будет работать?
Я это вполне могу написать на плюсах, какое-нибудь usersRange.map([](const auto& user) { return user.getUserName(); })
И с какого кода вы можете написать auto в плюсах? Как будут работать замыкания?
Зачем тут вообще Хиндли-Милнер?
Автор комментария тут, очевидно, подразумевал любой вывод типов ("Хиндли-Милнер" ведь звучит намного круче, чем просто "вывод типов", правда?). Тип user в рассматриваемом примере компилятор откуда берет?
С 11-го.
Ой, как быстро время-то бежит. По ощущениям казалось, что совсем недавно это и было.
А что с ним?
Ну я если честно не знаю, как в плюсах работают замыкания, т.к. последний раз писал на плюсах еще до того, как они там появились. Как осуществляется захват переменных?
Паскаль и Си очень ограниченно умели возвращать функции из функций (для этого нужны или лямбды, или ещё какая поддержка от языка). Функцию можно было только вызвать и взять указатель на неё, а, к примеру, частичное применение функции сделать — опаньки.
(Хотя, конечно, можно эмулировать)
Глянул краем глаза (смотрел "partial application") — что-то на первый взгляд в книге нет даже очевидных для меня костылей, позволяющих сделать частичное применение (делаем аналог плюсового функционального объекта с чуть менее удобным синтаксисом), зато куча опасного вроде преобразования туда-сюда void*. Но всё равно спасибо за ссылку.
Помню как студентами мы делал лабы на ассемблере для MS DOS и аналогов IBM 360 с низкоуровневым доступом к периферийным устройстам. Так вот эта концепция промисов там уже была: при отправке в порты устройства команд, надо было оставить где-нибудь адрес колбека, который вызвался по завершении. Запрограммировать циклы таких операций можно было функциональным подходом и рекурсией, но было жутко неудобно. Наверное поэтому в любых ОС есть более высокоуровневые интерфейсы, при вызове которых процесс может приостанавливаеться до завершения, что позволяет программировать логику «в лоб».
Но ОС проектируют годы, а джаваскрипт за 10 дней.
Вообще забавно наблюдать как хайпят ФП… и прочие сомнительные фичи джаваскриптаА причём тут джаваскрипт?
Так вот эта концепция промисов там уже была: при отправке в порты устройства команд, надо было оставить где-нибудь адрес колбекаА причём тут промисы? Промисы как раз и сделаны, чтобы вручную коллбэки не передавать.
Запрограммировать циклы таких операций можно было функциональным подходом и рекурсией, но было жутко неудобно.Так это и в JS не приходится рекурсией решать.
Наверное поэтому в любых ОС есть более высокоуровневые интерфейсы, при вызове которых процесс может приостанавливаеться до завершения, что позволяет программировать логику «в лоб».А если не нужно, чтобы процесс «спал»? А если нужно отправит 10 запросов, дождаться, пока вернётся хотя бы три, и что-то делать дальше? А если таких задач тысячи, то спавним тысячи процессов?
Я в принципе не против ни одного из этих подходов, в каждом есть что-то полезное. Но вот конкретно с JS уже не первый раз, когда проблемы дизайна языка прикрывают хайпом. Например коллбеки — хорошая вещь, но когда это единственно возможный способ работы с медленными функциями, то это проблема дизайна.
А чтобы не спавнить процессы на каждый из 10 запросов уже десятки лет как существуют функции типа posix select или poll, и по крайней мере есть выбор, спавнить или нет.
Настоящий толчек в массы для ФП дал JS
Ничего подобного. Настоящий толчёк ФП в массы дал некоторый снобистский хайп вокруг Haskell, Erlang и Scala. В JS можно (и иногда нужно) писать в ФП-стиле, но надо понимать, что в половине случаев это не даёт тех потрясающих плюшек, что дают взрослые ФП-языки.
А в массы они не выходили по причине того, что долгое время по-сути и не было проблем, для которых были необходимы ФП-концепции. Как минимум можно передать горячий привет многоядерности и параллелизму.
Но ведь всё, что можно написать в парадигме ФП, можно написать и в ООП, в крайнем случае, наплодив объектов с методом apply(). Более того, жили же на java до введения лямбд.
И наоборот, любой объект можно переписать как функцию.
Не вижу смысла от ФП отказываться, даже в рамках примера. Так можно и от умножения отказаться, и говорить, как плохо. Война с мельницами какая-то.
God object — пример плохо пахнущего кода. Хорошо структурированный код гораздо проще переписать на чистые функции. Вопрос только в том, нужно ли это?
Почему реализации map/fold где-нибудь в js зачастую неполноценны? Потому что ленивости скажем нет, и в итоге где-то в цепочке могут сохраняться промежуточные результаты непонятного размера, просаживая производительность. Почему функции в java не совсем полноценные? Потому что чистоту мы не можем ни обеспечить, ни проверить, нет механизмов для этого.
Но это все не значит, что мы не можем думать о программах на js или java таким же способом, как делали бы это в идеальном ФП языке. И мне кажется, что ФП это больше про способ думать о коде, о композиции его из частей, о том, какие это должны быть части. И «эти несчастные лямбды/мап/фолд в JS, Java и так далее.» — ничто иное, как именно способ думать о коде иначе. Не иначе чем в плюсовых темплейтах, а иначе чем мы делали в этом же языке ранее.
Функциональное программирование — это парадигма использования чистых функций в качестве неделимых единиц композиции, с избеганием общего изменяемого состояния и побочных эффектов.Функциональное программирование не просто избегает общего изменяемого состояния, а не содержит никакого изменяемого состояния — ни общего, ни частного. Если же, все-таки, изменяемое состояние есть, то речь идёт о смеси функционального и императивного прогаммирования.
Может определение и любимое, но оно, как минимум, неточное и ведёт к неверным выводам.
Если же учесть, что функциональная и императивная парадигмы на базовом уровне — это вопрос интерпретации, то можно прийти к заключению, что статья является упражнением в софистике. Ведь если идти до конца, то любую императивную программу можно интерпретировать как функциональную и запрет на функциональное программирование будет обозначать запрет на программирование вообще.
Но обычно имеют ввиду не это.
А программировать надо так, как навязывает язык и его расширения-библиотеки, иначе всё время придётся бороться с ветряными мельницами. Только космических выводов из этого делать, пожалуй, не стоит. Большинство ЯВУ, начиная с Фортрана, были синтетическими.
Хороший язык программирования помогает программистам писать хорошие программы. Ни один из языков программирования не может запретить своим пользователям писать плохие программы.
(идет за чипсами и колой)
Давайте рефакторим код так, чтобы он больше не относился к ФП. Можно сделать класс с публичным свойством. Поскольку инкапсуляция не используется, было бы преувеличением назвать это ООП.
И в очередной раз инкапсуляцию связывают со способностью языка явным образом помечать поля класса как «private». Не об этом же инкапсуляция!
В вашем примере:
class User {
constructor ({name}) {
this.name = name;
}
...
}
name — вполне себе инкапсулированное поле.
Существует мнение (я его не разделяю), что инкапсуляция это и есть сокрытие.
https://ru.wikipedia.org/wiki/Инкапсуляция_(программирование)
приводит к другому распространённому заблуждению — рассмотрению инкапсуляции неотрывно от сокрытия. В частности, в сообществе С++ или Java принято рассматривать инкапсуляцию без сокрытия как неполноценную. Однако, некоторые языки (например, Smalltalk, Python) реализуют инкапсуляцию в полной мере, но не предусматривают возможности скрытия в принципе.
«Но это же разные типы. Конечно, вы не можете применять метод класса person к стране!».
На это я отвечу: «А почему нет?»
Действительно, почему нет?
Узнайте имя у страны, потом фамилию и паспортные данные.
Смело двигайтесь дальше — извлекайте корни из любого поля любого объекта. Если у какого-то объекта корень из ip-адреса не извлекается, то это его проблемы.
Зато строчек кода мало!
У автора единственная реализация getName, и утверждается, что она может работать с любым объектом. Это не так (если ожидать корректную работу функции и наличие некоторого смысла).
У вас же описан частный случай реализации для определенного объекта, и я не понимаю, почему вы мне возражаете.
На scala это через рефлексию вызывается, обычно лучше всё-таки сгородить trait.
Стандартная проблема фпшника: на яблоках и пальцах всё красиво считается и другим доказывается, а в реальных задачах без монад, переусложнений, кровавых соплей и неразумной траты ресурсов концы не сходятся.
Мне кажется, что дело не в ФП. Не скажу за react, но в angular используются реактивное программирование, что значительно отличается от функционального. И самый большой минус, что используется далеко не во всем коде, поэтому следить за тем, что observable, а что нет и какие изменения к чему приводят новичкам (например, мне) бывает сложно.
Всё отлично когда «чисто».
Как только смешивают в одном приложении (то есть коде) ФП, ООП, реактивное — то надо, похоже, смотреть в оба и не допустить «мути» — то есть «взбалтывания» всех этих парадигм в одном «флаконе» — что на практике постоянно и происходит ибо это уже практика — то есть «жизнь кода» вне академических «песочниц».
А разве это не так? :) Честно говоря, я не вижу ничего плохого в замене циклов на map-reduce, при одном простом условии — что это делается осознанно, понимая, что мы приобретаем, и чем за это платим. При этом, заметьте, человек, способный проделать такую замену, уже не ограничен javascript и фронтендом, ему можно завтра дать в руки Hadoop — и он не потеряется там совсем.
Что мешает объявить интерфейс IName и реализовать у классов для которых это имеет смысл?
Функциональное программирование — это парадигма использования чистых функций в качестве неделимых единиц композиции, с избеганием общего изменяемого состояния и побочных эффектов.
Чистая функция:
Получая одни и те же входные данные, всегда возвращает один и тот же результат
Как быть с random? Эта функция получает одинаковые данные (интервал чисел), но всегда возвращает случайное число из заданного интервала. Получается она не чистая и не может быть использована в ФП по определению выше.
Нет. Это, если хотите, часть соглашения о чистых функциях: есть некое внешнее хранилище состояний World, к которому можно обращаться только через монаду IO. Если сильно упростить — то функция, для работы которой монада IO не нужна, которая сама по себе ничего во внешнем мире изменить не способна — считается чистой, и ее можно лениво выполнять, безопасно параллелить итд. Как видите, тут «внешний мир» — хорошая, годная, даже необходимая абстракция.
> считаю радикалов, которые не хотят признать, что ни ФП, ни ООП, ни молоток не являются наилучшими инструментами
А такие вообще встречаются в дикой природе?
Получается она не чистаяОна не чистая. Да и любая функция ввода/вывода или обращения к базе или читающая файл или пишущая в файл — не чистая.
Где вы видели random, возвращающий именно случайное число?
Хорошо, пусть это псевдослучайное число или даже другая функция, которая при каждом вызове возвращает следующее значение некой последовательности чисел (вспоминаем python, его yield, итераторы и генераторы). Передавая одни и те же входные данные, будем получать разные результаты. Правильно ли я понял, эти функции не чистые по определению выше и не могут быть использованы в ФП?
P.S. Немного промахнулся в ответе на сообщение s-kozlov
Предыдущая функция getName() работала с любым входящим объектом.
Такую функцию в указанном виде можно написать только при наличии структурного сабтайпинга (если не учитывать динамику). При чем тут функциональное программирование, если 99% функциональных языков такового не имеют?
Промисы — это монады.
На практике промисы и async/await НЕ реализованы как монады.
У автора каша в голове, смешались вместе кони, люди и все остальное..
Можно ли осознанно отказаться от функционального программирования?