Pull to refresh

Comments 113

Это шутка такая?
Может хватит уже вести вечный холивар на тему «ООП отстой! Нет ФП отстой!», а понять наконец что для своей задачи свой инструмент. Доведение до абсурда ещё ни кого не приводило к хорошему результату.
Доведение до абсурда в этой статье явно намеренное.
UFO just landed and posted this here
UFO just landed and posted this here
даже функции высокого порядка в Java. Так выглядит Java в 2018-м… Функциональное программирование настолько полезно и удобно, что, насколько я вижу, проникло во все современные распространённые языки.


Сформулировано впечатляюще.
Но… «Функции высокого порядка» умели еще Pascal и C, еще порядка полувека тому назад.
А возможно и более древние языки.

Проникло в современные языки, говорите?
Все новое — хорошо забытое старое.

users.stream().map(user -> user.getUserName()) — какого тут типа результат? А теперь все тоже самое про C и Pascal расскажите. Не было полвека тому назад никакого Хиндли-Милнера в общедоступных языках. И в паскале его не было, и в C тоже. Вот в этом и разница.
UFO just landed and posted this here
Тем не менее, в упомянутых конкретных C и паскале его нет и сегодня. А C++ не упоминали. Да, возможно я тут формалист, но говорить что с тех пор ничего не изменилось тоже неправильно.
UFO just landed and posted this here
Ну, давайте вернемся к началу:
>Но… «Функции высокого порядка» умели еще Pascal и C, еще порядка полувека тому назад. А возможно и более древние языки.

Мне в этом комментарии не понравилось то, что даже если можно написать на C или паскале частичное применение или композицию функций (мне лень проверять, но вполне допускаю, что можно), все равно в этих языках нет и не было возможности вывести тип той функции, которая получится. И результат в лучшем случае будет небезопасен. То есть, по сравнению даже с современной Java, ее лямбдами, и ее возможностями по созданию функций высших порядков (а они далеко не идеал), в Pascal и C 50 лет назад все было намного хуже.

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

При чем тут Х-М? Исключительно при том, что в дополнение к функциям высших порядков все-таки хочется иметь порою и статическую типизацию, и выведение типов. В вашем примере результат

usersRange.map([](const auto& user) { return user.getUserName(); })

какого типа будет? Нужно ли его будет явно записать, или его нам выведут? Если вы поменяете устройство usersRange, вся эта конструкция в C++ все еще будет работать?
UFO just landed and posted this here
Я это вполне могу написать на плюсах, какое-нибудь usersRange.map([](const auto& user) { return user.getUserName(); })

И с какого кода вы можете написать auto в плюсах? Как будут работать замыкания?


Зачем тут вообще Хиндли-Милнер?

Автор комментария тут, очевидно, подразумевал любой вывод типов ("Хиндли-Милнер" ведь звучит намного круче, чем просто "вывод типов", правда?). Тип user в рассматриваемом примере компилятор откуда берет?

UFO just landed and posted this here
С 11-го.

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


А что с ним?

Ну я если честно не знаю, как в плюсах работают замыкания, т.к. последний раз писал на плюсах еще до того, как они там появились. Как осуществляется захват переменных?

UFO just landed and posted this here
Лямбды разворачиваются в анонимную структуру вроде той, что в предыдущем комментарии. Соответственно, захваченные переменные — это просто поля этой структуры.

А какой тип у лямбды? Это, получается, пара из указателя на ф-ю и данной структуры?

UFO just landed and posted this here

Понятно, спасибо.

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

Если интересно, посмотрите «Functional C», Hartel & Muller, там рядом часто примеры на SML и С, читал, когда пытался понять причины хайпа. Можно использовать частичное применение, можно ад на шаблонах, но получится довольно многословно и временами грязновато.

Глянул краем глаза (смотрел "partial application") — что-то на первый взгляд в книге нет даже очевидных для меня костылей, позволяющих сделать частичное применение (делаем аналог плюсового функционального объекта с чуть менее удобным синтаксисом), зато куча опасного вроде преобразования туда-сюда void*. Но всё равно спасибо за ссылку.

Это я и называю — грязно. Не знаю иного пути сделать параметрический полиморфизм в С кроме как через как к void * и обратно, или через макросы (особенно generic; видел страшенный вариант, где мы генерируем generic macro и соответствующие функции с помощью X-macros, жаль, ссылку не найду)
UFO just landed and posted this here
Структурированные типы (а что это такое, кстати? ADT?)

Очевидно, структурный сабтайпинг (как в TS). TaPL, chapter 15.

UFO just landed and posted this here

Автор с таплом, очевидно, не знаком, но знаком с описываемыми системами (или системами, построенными на их базе) :)

Вообще забавно наблюдать как хайпят ФП, промисы и прочие сомнительные фичи джаваскрипта, вызванные, в основном, ограничениями JS движка в плане асинхронных операций и многопоточности.
Помню как студентами мы делал лабы на ассемблере для MS DOS и аналогов IBM 360 с низкоуровневым доступом к периферийным устройстам. Так вот эта концепция промисов там уже была: при отправке в порты устройства команд, надо было оставить где-нибудь адрес колбека, который вызвался по завершении. Запрограммировать циклы таких операций можно было функциональным подходом и рекурсией, но было жутко неудобно. Наверное поэтому в любых ОС есть более высокоуровневые интерфейсы, при вызове которых процесс может приостанавливаеться до завершения, что позволяет программировать логику «в лоб».
Но ОС проектируют годы, а джаваскрипт за 10 дней.
А причём тут вообще JS?
Вообще забавно наблюдать как хайпят ФП… и прочие сомнительные фичи джаваскрипта
А причём тут джаваскрипт?
Так вот эта концепция промисов там уже была: при отправке в порты устройства команд, надо было оставить где-нибудь адрес колбека
А причём тут промисы? Промисы как раз и сделаны, чтобы вручную коллбэки не передавать.
Запрограммировать циклы таких операций можно было функциональным подходом и рекурсией, но было жутко неудобно.
Так это и в JS не приходится рекурсией решать.
Наверное поэтому в любых ОС есть более высокоуровневые интерфейсы, при вызове которых процесс может приостанавливаеться до завершения, что позволяет программировать логику «в лоб».
А если не нужно, чтобы процесс «спал»? А если нужно отправит 10 запросов, дождаться, пока вернётся хотя бы три, и что-то делать дальше? А если таких задач тысячи, то спавним тысячи процессов?
ФП-языки известны давно, но широко никогда не применялись. ФП-стиль можно использовать со многими языками, и это делалось, по мере целесообразности. Настоящий толчек в массы для ФП дал JS, но не потому что ФП это прямо всегда самый лучший подход, а потому что в JS иначе трудно писать сложную логику с последовательную вызовами IO. Реальная альтернатива в виде генераторов, и позже async/await на их основе появилась когда языку было уже 20 лет.
Я в принципе не против ни одного из этих подходов, в каждом есть что-то полезное. Но вот конкретно с JS уже не первый раз, когда проблемы дизайна языка прикрывают хайпом. Например коллбеки — хорошая вещь, но когда это единственно возможный способ работы с медленными функциями, то это проблема дизайна.
А чтобы не спавнить процессы на каждый из 10 запросов уже десятки лет как существуют функции типа posix select или poll, и по крайней мере есть выбор, спавнить или нет.
UFO just landed and posted this here
Вы как-то хитро сравниваете язык (JS) с библиотеками и ОС, при этом почему-то записывая ограничения браузерного IO API в минусы самого языка. В JS вам тоже никто не мешает писать синхронные функции. В NodeJS даже блокирующий IO из коробки есть.
Настоящий толчек в массы для ФП дал JS

Ничего подобного. Настоящий толчёк ФП в массы дал некоторый снобистский хайп вокруг Haskell, Erlang и Scala. В JS можно (и иногда нужно) писать в ФП-стиле, но надо понимать, что в половине случаев это не даёт тех потрясающих плюшек, что дают взрослые ФП-языки.
А в массы они не выходили по причине того, что долгое время по-сути и не было проблем, для которых были необходимы ФП-концепции. Как минимум можно передать горячий привет многоядерности и параллелизму.

Но ведь всё, что можно написать в парадигме ФП, можно написать и в ООП, в крайнем случае, наплодив объектов с методом apply(). Более того, жили же на java до введения лямбд.


И наоборот, любой объект можно переписать как функцию.


Не вижу смысла от ФП отказываться, даже в рамках примера. Так можно и от умножения отказаться, и говорить, как плохо. Война с мельницами какая-то.

UFO just landed and posted this here

God object — пример плохо пахнущего кода. Хорошо структурированный код гораздо проще переписать на чистые функции. Вопрос только в том, нужно ли это?

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

Почему реализации map/fold где-нибудь в js зачастую неполноценны? Потому что ленивости скажем нет, и в итоге где-то в цепочке могут сохраняться промежуточные результаты непонятного размера, просаживая производительность. Почему функции в java не совсем полноценные? Потому что чистоту мы не можем ни обеспечить, ни проверить, нет механизмов для этого.

Но это все не значит, что мы не можем думать о программах на js или java таким же способом, как делали бы это в идеальном ФП языке. И мне кажется, что ФП это больше про способ думать о коде, о композиции его из частей, о том, какие это должны быть части. И «эти несчастные лямбды/мап/фолд в JS, Java и так далее.» — ничто иное, как именно способ думать о коде иначе. Не иначе чем в плюсовых темплейтах, а иначе чем мы делали в этом же языке ранее.
UFO just landed and posted this here
Функциональное программирование — это парадигма использования чистых функций в качестве неделимых единиц композиции, с избеганием общего изменяемого состояния и побочных эффектов.
Функциональное программирование не просто избегает общего изменяемого состояния, а не содержит никакого изменяемого состояния — ни общего, ни частного. Если же, все-таки, изменяемое состояние есть, то речь идёт о смеси функционального и императивного прогаммирования.
Может определение и любимое, но оно, как минимум, неточное и ведёт к неверным выводам.

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

Но обычно имеют ввиду не это.

А программировать надо так, как навязывает язык и его расширения-библиотеки, иначе всё время придётся бороться с ветряными мельницами. Только космических выводов из этого делать, пожалуй, не стоит. Большинство ЯВУ, начиная с Фортрана, были синтетическими.
UFO just landed and posted this here
«На любом языке программирования можно писать на Фортране» (С) ;-)

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


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


И в очередной раз инкапсуляцию связывают со способностью языка явным образом помечать поля класса как «private». Не об этом же инкапсуляция!
В вашем примере:
class User {
  constructor ({name}) {
    this.name = name;
  }
  ...
}

name — вполне себе инкапсулированное поле.

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


https://ru.wikipedia.org/wiki/Инкапсуляция_(программирование)


приводит к другому распространённому заблуждению — рассмотрению инкапсуляции неотрывно от сокрытия. В частности, в сообществе С++ или Java принято рассматривать инкапсуляцию без сокрытия как неполноценную. Однако, некоторые языки (например, Smalltalk, Python) реализуют инкапсуляцию в полной мере, но не предусматривают возможности скрытия в принципе.
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
Прелесть геттеров и сеттеров программист начинает ценить, когда 10-й раз переделывает интерфейс из-за того, что возникает необходимость изменить поведение объекта на установку или запрос какого-то поля.
UFO just landed and posted this here
Не факт. Если придерживаться правила, что геттеров и сеттеров не имею только простые структуры, а доступ к членам более/менее сложных классов осуществляется только через геттеры и сеттеры, то это сильно упрощает жизнь в будущем. Например в будущем может понадобится, чтобы обновление поля дублироваллось соответствующей записью в БД, или при обновлении нужно будет добавить верификацию данных.
UFO just landed and posted this here
Ужас геттеров и сеттеров программист начинает «ценить», когда 10-й раз обнаруживает, что на запрос каких-то свойств, происходит мутация других свойств, а на установку свойства его установка не происходит, умтановка происходит в другое значение или происходит установка и других свойств. Геттеры и сеттеры с тупым поведением практически практически (два «практически» не ошибка) бесполезны, а со сложным — нарушают ожидаемый контракт. Единственное разумное для них ожидаемое поведение кроме непосредственной работы со свойствами — генерация ошибок при невалидном состоянии свойств или попытке их привести в таковое.
Я не знаю как Smalltalk, а Python предусматривает возможность сокрытия полей. Нужно добавить "__" перед именем.

Это разве не соглашение? Ведь всё еще можно обратиться к такому полю, зная его имя, не так ли? (я не помню, писал на питоне чуть не 10 лет назад).

«Но это же разные типы. Конечно, вы не можете применять метод класса person к стране!».
На это я отвечу: «А почему нет?»

Действительно, почему нет?
Узнайте имя у страны, потом фамилию и паспортные данные.
Смело двигайтесь дальше — извлекайте корни из любого поля любого объекта. Если у какого-то объекта корень из ip-адреса не извлекается, то это его проблемы.
Зато строчек кода мало!

UFO just landed and posted this here

У автора единственная реализация getName, и утверждается, что она может работать с любым объектом. Это не так (если ожидать корректную работу функции и наличие некоторого смысла).
У вас же описан частный случай реализации для определенного объекта, и я не понимаю, почему вы мне возражаете.

UFO just landed and posted this here

На scala это через рефлексию вызывается, обычно лучше всё-таки сгородить trait.

UFO just landed and posted this here
Согласен, тоже резануло. У человека — имя, у страны — наименование, ничего общего в реальных моделях.
Стандартная проблема фпшника: на яблоках и пальцах всё красиво считается и другим доказывается, а в реальных задачах без монад, переусложнений, кровавых соплей и неразумной траты ресурсов концы не сходятся.
UFO just landed and posted this here

Мне кажется, что дело не в ФП. Не скажу за react, но в angular используются реактивное программирование, что значительно отличается от функционального. И самый большой минус, что используется далеко не во всем коде, поэтому следить за тем, что observable, а что нет и какие изменения к чему приводят новичкам (например, мне) бывает сложно.

Верно.

Всё отлично когда «чисто».

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

UFO just landed and posted this here
UFO just landed and posted this here
>ФП — это когда цикл for заменяют на map-reduce.

А разве это не так? :) Честно говоря, я не вижу ничего плохого в замене циклов на map-reduce, при одном простом условии — что это делается осознанно, понимая, что мы приобретаем, и чем за это платим. При этом, заметьте, человек, способный проделать такую замену, уже не ограничен javascript и фронтендом, ему можно завтра дать в руки Hadoop — и он не потеряется там совсем.
А причём тут javascript и фронтенд? array_map и ко в PHP лет на 5 раньше чем в JS появился :)
javascript тут при том, что парадигма эта им не ограничена, а наоборот очень широко применяется в самых разных местах.
UFO just landed and posted this here
> «Но это же разные типы. Конечно, вы не можете применять метод класса person к стране!»
Что мешает объявить интерфейс IName и реализовать у классов для которых это имеет смысл?
UFO just landed and posted this here
Проверять реализацию интерфейса по наличию метода getName(), а не по наличию строчки типа implements IName :)
А если нет obj.name? Как чистая функция отработает :-)?
UFO just landed and posted this here
UFO just landed and posted this here
А здесь?
const getName = obj => obj.name;
const name = getName({ uid: '123', name1: 'Banksy' });
UFO just landed and posted this here
Ну это у автора надо бы спросить, он на javascript примеры приводит.
UFO just landed and posted this here
Зависит от языка. В шарпе приравнял к dynamic и вперед, вызывай, что хочешь. Только кому это нужно, когда скатываешься в итоге, как ниже заметили, к «пристальному вглядыванию».
Функциональное программирование — это парадигма использования чистых функций в качестве неделимых единиц композиции, с избеганием общего изменяемого состояния и побочных эффектов.
Чистая функция:
Получая одни и те же входные данные, всегда возвращает один и тот же результат


Как быть с random? Эта функция получает одинаковые данные (интервал чисел), но всегда возвращает случайное число из заданного интервала. Получается она не чистая и не может быть использована в ФП по определению выше.
UFO just landed and posted this here
Таковыми можно считать функции, использующие аппаратные средства типа шумов в аналоговых трактах или квантовые флуктуации.
UFO just landed and posted this here
Эм… но почему упоротые? Random = во внешнем мире произошло какое-то (псевдо)случайное событие, и его состояние изменилось. Что вас собственно смущает?
UFO just landed and posted this here
> абстракция «внешний мир»… — чистой воды костыль… вызванный… непременным желанием любой ценой подогнать суровую действительность

Нет. Это, если хотите, часть соглашения о чистых функциях: есть некое внешнее хранилище состояний World, к которому можно обращаться только через монаду IO. Если сильно упростить — то функция, для работы которой монада IO не нужна, которая сама по себе ничего во внешнем мире изменить не способна — считается чистой, и ее можно лениво выполнять, безопасно параллелить итд. Как видите, тут «внешний мир» — хорошая, годная, даже необходимая абстракция.

> считаю радикалов, которые не хотят признать, что ни ФП, ни ООП, ни молоток не являются наилучшими инструментами

А такие вообще встречаются в дикой природе?
UFO just landed and posted this here
«Внешний мир» — хорошая абстракция для любой (наверное) парадигмы программирования. Правила работы с ним могут быть разными, могут реализовываться на уровне синтаксиса или семантики ЯП, пользовательского кода или соглашений, но само наличие правил (с какими-то гарантиями их ненарушения, конечно) очень помогают при разработке сложных систем, упрощая контроль над тем, что делает та или иная программная единица без изучения её кода. Простое соглашение типа «используем глобальные переменные исключительно на глобальном уровне в пределах одного файла» очень упрощает жизнь.
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
random конечно не является чистой функцией. В лучшем (для чистоты) случае это функция со своим внутренним состоянием, в худшем — получает энтропию из физического мира.
UFO just landed and posted this here
Получается она не чистая
Она не чистая. Да и любая функция ввода/вывода или обращения к базе или читающая файл или пишущая в файл — не чистая.
Ну, почему же? Если состояние базы/файла — это часть и входных параметров, и возвращаемого значения (если она его меняет), то вполне может быть чистой. Как редьюсеры в Redux'е.
Нет, чистая функция работы с базой или файлом возвращает новую базу или файл, а не меняет переданные. :) Редьюсер в редакс не меняет стейт, он возвращает новый. Меняет стейт грязный dispatch, который передаёт редьюсеру текущий стейт, получает новый и заменяет текущий на новый в сторе.
Функция random возвращает число по одному и тому-же закону распределения. Сколько-бы вы ее раз не вызвали закон останется прежним.
UFO just landed and posted this here
Функции random, да и вообще любые подпрограммы в реальных вычсистемах детерминированы. Отличие чистых функций от нечистых в плане детерминированности на практике в том, может ли программист контролировать все параметры функций при её вызове или она под капотом имеет дополнительные, внешние и внутренние состояния.
UFO just landed and posted this here
Термин «закон распределения» в контексте функции random обычно относят к случайным величинам, но в подавляющем большинстве случаев функция random к ним отношения не имеет даже в теории.
Где вы видели random, возвращающий именно случайное число?

Хорошо, пусть это псевдослучайное число или даже другая функция, которая при каждом вызове возвращает следующее значение некой последовательности чисел (вспоминаем python, его yield, итераторы и генераторы). Передавая одни и те же входные данные, будем получать разные результаты. Правильно ли я понял, эти функции не чистые по определению выше и не могут быть использованы в ФП?
P.S. Немного промахнулся в ответе на сообщение s-kozlov
UFO just landed and posted this here
Предыдущая функция getName() работала с любым входящим объектом.

Такую функцию в указанном виде можно написать только при наличии структурного сабтайпинга (если не учитывать динамику). При чем тут функциональное программирование, если 99% функциональных языков такового не имеют?


Промисы — это монады.

На практике промисы и async/await НЕ реализованы как монады.


У автора каша в голове, смешались вместе кони, люди и все остальное..

У автора каша в голове, смешались вместе кони, люди и все остальное..
… "и залпы тысячи орудий" вбивают в головы людей
Монад неслыханных идей…
Sign up to leave a comment.