Функциональное программирование пронизывает большую часть основного мира программирования — экосистема JavaScript, Linq для C#, даже функции высокого порядка в Java. Так выглядит Java в 2018-м:

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)


  1. Alozar
    07.05.2018 18:14
    +16

    Это шутка такая?
    Может хватит уже вести вечный холивар на тему «ООП отстой! Нет ФП отстой!», а понять наконец что для своей задачи свой инструмент. Доведение до абсурда ещё ни кого не приводило к хорошему результату.


    1. AnutaU
      07.05.2018 19:58
      +3

      Доведение до абсурда в этой статье явно намеренное.


      1. maxzh83
        07.05.2018 22:16

        We need to go deeper… Где в статье про линзы? Без них никуда в современном ФП


        1. 0xd34df00d
          08.05.2018 00:48

          Про кубическую теорию типов.



  1. awesomer
    07.05.2018 18:21
    -1

    даже функции высокого порядка в Java. Так выглядит Java в 2018-м… Функциональное программирование настолько полезно и удобно, что, насколько я вижу, проникло во все современные распространённые языки.


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

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


    1. sshikov
      07.05.2018 20:04

      users.stream().map(user -> user.getUserName()) — какого тут типа результат? А теперь все тоже самое про C и Pascal расскажите. Не было полвека тому назад никакого Хиндли-Милнера в общедоступных языках. И в паскале его не было, и в C тоже. Вот в этом и разница.


      1. 0xd34df00d
        08.05.2018 00:53
        +1

        users.stream().map(user -> user.getUserName()) — какого тут типа результат? А теперь все тоже самое про C и Pascal расскажите.

        Я это вполне могу написать на плюсах, какое-нибудь usersRange.map([](const auto& user) { return user.getUserName(); }), которое абсолютно синтаксической заменой дешугарится в структуру с шаблонным оператором-скобочки и становится валидным C++98. Зачем тут вообще Хиндли-Милнер?

        Не было полвека тому назад никакого Хиндли-Милнера в общедоступных языках.

        ML появился в 73-м году (через год после С, ага), так что всего через 5 лет это утверждение станет неверным даже формально.


        1. sshikov
          09.05.2018 09:04

          Тем не менее, в упомянутых конкретных C и паскале его нет и сегодня. А C++ не упоминали. Да, возможно я тут формалист, но говорить что с тех пор ничего не изменилось тоже неправильно.


          1. 0xd34df00d
            09.05.2018 18:33

            Так его (если вы о Х-М) и в С++ нет, а на нём аналогичные по смыслу, внешнему виду и так далее конструкции вполне себе пишутся, как демонстрирует пример выше.

            Как формалист формалисту, я опровергаю другое утверждение.


            1. 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++ все еще будет работать?


              1. 0xd34df00d
                09.05.2018 21:54

                какого типа будет?

                Статически известного. Какого — зависит от реализации map (как и с Х-М, впрочем). Может, std::vector<std::string>, может, тоже рейндж какой со строками.

                Нужно ли его будет явно записать, или его нам выведут?

                Напишете auto — выведут.

                Если вы поменяете устройство usersRange, вся эта конструкция в C++ все еще будет работать?

                Смотря как поменяю. Если совместимым образом, и если особенно вы нигде явно не требуете никаких типов, а полагаетесь на их вывод, то да.


        1. Druu
          10.05.2018 01:13

          Я это вполне могу написать на плюсах, какое-нибудь usersRange.map([](const auto& user) { return user.getUserName(); })

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


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

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


    1. aamonster
      07.05.2018 22:59

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


      1. Gryphon88
        08.05.2018 15:12

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


        1. aamonster
          08.05.2018 15:30

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


          1. Gryphon88
            08.05.2018 15:45

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


  1. 0xd34df00d
    07.05.2018 18:50
    +4

    Примечание для Java-программистов: речь не идёт о статической типизации. Некоторые ФП-языки имеют прекрасные системы статических типов, но всё же пользуются преимуществами структурированных типов и/или HKT(higher-kinded types).

    Я не очень понял противопоставление в этом предложении. Структурированные типы (а что это такое, кстати? ADT?) и HKT — это вполне себе элементы статической типизации, как и тайпклассы, как и ad-hoc polymorphism.


    1. Druu
      10.05.2018 01:19

      Структурированные типы (а что это такое, кстати? ADT?)

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


  1. amaksr
    07.05.2018 18:59
    -6

    Вообще забавно наблюдать как хайпят ФП, промисы и прочие сомнительные фичи джаваскрипта, вызванные, в основном, ограничениями JS движка в плане асинхронных операций и многопоточности.
    Помню как студентами мы делал лабы на ассемблере для MS DOS и аналогов IBM 360 с низкоуровневым доступом к периферийным устройстам. Так вот эта концепция промисов там уже была: при отправке в порты устройства команд, надо было оставить где-нибудь адрес колбека, который вызвался по завершении. Запрограммировать циклы таких операций можно было функциональным подходом и рекурсией, но было жутко неудобно. Наверное поэтому в любых ОС есть более высокоуровневые интерфейсы, при вызове которых процесс может приостанавливаеться до завершения, что позволяет программировать логику «в лоб».
    Но ОС проектируют годы, а джаваскрипт за 10 дней.


    1. Chamie
      07.05.2018 21:56
      +3

      А причём тут вообще JS?

      Вообще забавно наблюдать как хайпят ФП… и прочие сомнительные фичи джаваскрипта
      А причём тут джаваскрипт?
      Так вот эта концепция промисов там уже была: при отправке в порты устройства команд, надо было оставить где-нибудь адрес колбека
      А причём тут промисы? Промисы как раз и сделаны, чтобы вручную коллбэки не передавать.
      Запрограммировать циклы таких операций можно было функциональным подходом и рекурсией, но было жутко неудобно.
      Так это и в JS не приходится рекурсией решать.
      Наверное поэтому в любых ОС есть более высокоуровневые интерфейсы, при вызове которых процесс может приостанавливаеться до завершения, что позволяет программировать логику «в лоб».
      А если не нужно, чтобы процесс «спал»? А если нужно отправит 10 запросов, дождаться, пока вернётся хотя бы три, и что-то делать дальше? А если таких задач тысячи, то спавним тысячи процессов?


      1. amaksr
        08.05.2018 00:29
        +2

        ФП-языки известны давно, но широко никогда не применялись. ФП-стиль можно использовать со многими языками, и это делалось, по мере целесообразности. Настоящий толчек в массы для ФП дал JS, но не потому что ФП это прямо всегда самый лучший подход, а потому что в JS иначе трудно писать сложную логику с последовательную вызовами IO. Реальная альтернатива в виде генераторов, и позже async/await на их основе появилась когда языку было уже 20 лет.
        Я в принципе не против ни одного из этих подходов, в каждом есть что-то полезное. Но вот конкретно с JS уже не первый раз, когда проблемы дизайна языка прикрывают хайпом. Например коллбеки — хорошая вещь, но когда это единственно возможный способ работы с медленными функциями, то это проблема дизайна.
        А чтобы не спавнить процессы на каждый из 10 запросов уже десятки лет как существуют функции типа posix select или poll, и по крайней мере есть выбор, спавнить или нет.


        1. 0xd34df00d
          08.05.2018 00:56

          Настоящий толчек в массы для ФП дал JS, но не потому что ФП это прямо всегда самый лучший подход, а потому что в JS иначе трудно писать сложную логику с последовательную вызовами IO.

          Я не пишу на JS (да и не знаю его толком), но я не могу сказать, что JS дал толчок ФП в массы.

          Примерно с тем же успехом толчок ФП в массы дал какой-нибудь питон несколько лет назад, когда некоторые люди внезапно решили, что map, fold и лямбды — это прям квинтэссенция ФП.


        1. Chamie
          09.05.2018 13:43

          Вы как-то хитро сравниваете язык (JS) с библиотеками и ОС, при этом почему-то записывая ограничения браузерного IO API в минусы самого языка. В JS вам тоже никто не мешает писать синхронные функции. В NodeJS даже блокирующий IO из коробки есть.


        1. wert_lex
          10.05.2018 06:18

          Настоящий толчек в массы для ФП дал JS

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


  1. Akon32
    07.05.2018 19:16

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


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


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


    1. 0xd34df00d
      08.05.2018 01:00
      +2

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

      Но лямбды — это не ФП, как и ФП — не лямбды. ФП — это про чистые функции, алгебру типов и всякое такое. Можно сказать, что плюсовые темплейты ближе к ФП, чем эти несчастные лямбды/мап/фолд в JS, Java и так далее.

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

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

      Я бы посмотрел на переписывание какого-нибудь god object на чистых функциях с минимумом эффектов.


      1. sharikov_d
        08.05.2018 08:47

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


      1. sshikov
        09.05.2018 22:30

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

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

        Но это все не значит, что мы не можем думать о программах на js или java таким же способом, как делали бы это в идеальном ФП языке. И мне кажется, что ФП это больше про способ думать о коде, о композиции его из частей, о том, какие это должны быть части. И «эти несчастные лямбды/мап/фолд в JS, Java и так далее.» — ничто иное, как именно способ думать о коде иначе. Не иначе чем в плюсовых темплейтах, а иначе чем мы делали в этом же языке ранее.


        1. 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();, и так далее.


  1. Comdiv
    07.05.2018 19:42
    +1

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

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

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

    А программировать надо так, как навязывает язык и его расширения-библиотеки, иначе всё время придётся бороться с ветряными мельницами. Только космических выводов из этого делать, пожалуй, не стоит. Большинство ЯВУ, начиная с Фортрана, были синтетическими.


    1. 0xd34df00d
      08.05.2018 01:03

      Если же, все-таки, изменяемое состояние есть, то речь идёт о смеси функционального и императивного прогаммирования.

      В правильном™ ФП нет никакого изменяемого состояния, и даже все эти монады ST и IO — это просто возможность программисту оперировать чистыми функциями для построения чистой композиции этих самых функций, оперирующих нечистым миром (или нечистым состоянием) при помощи рантайма. В конце концов, IO — это всего лишь State, тегированный #RealWorld.

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


  1. viklequick
    07.05.2018 20:03

    «На любом языке программирования можно писать на Фортране» (С) ;-)

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


    (идет за чипсами и колой)


  1. BasicWolf
    07.05.2018 20:40
    +3

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


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

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


    1. k12th
      07.05.2018 22:27

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


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


      приводит к другому распространённому заблуждению — рассмотрению инкапсуляции неотрывно от сокрытия. В частности, в сообществе С++ или Java принято рассматривать инкапсуляцию без сокрытия как неполноценную. Однако, некоторые языки (например, Smalltalk, Python) реализуют инкапсуляцию в полной мере, но не предусматривают возможности скрытия в принципе.


      1. s-kozlov
        08.05.2018 06:07
        +2

        Существует еще мнение, что публичное поле — это всегда нарушение инкапсуляции, а приватное поле с тривиальными геттерами и сеттерами — нет. Карго-культы — они такие.


        1. 0xd34df00d
          08.05.2018 07:24

          В случае С++ ещё круче приватное поле с геттерами и сеттерами вида

          const T& getFoo() const;
          T& setFoo();
          


          Бессмысленно чуть более чем. Разве что, факт обращений к полю логгировать.


          1. s-kozlov
            08.05.2018 11:00

            Смысл как раз есть (да, то же логирование в будущем захотим добавить), но инкапсуляция может быть нарушена.


            1. 0xd34df00d
              08.05.2018 15:05

              Но то же устанавливаемое значение вы уже залогировать не сможете.

              Равно как и не сможете завернуть поле в мьютекс. Или вообще генерировать его лениво из закешированных данных, например.


          1. DeadKnight
            08.05.2018 18:04

            Прелесть геттеров и сеттеров программист начинает ценить, когда 10-й раз переделывает интерфейс из-за того, что возникает необходимость изменить поведение объекта на установку или запрос какого-то поля.


            1. 0xd34df00d
              08.05.2018 18:32

              В случае процитированного кода вам интерфейс всё равно придётся менять.


              1. DeadKnight
                08.05.2018 21:06

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


                1. 0xd34df00d
                  08.05.2018 21:29

                  Там ссылки возвращаются. Использование таких геттеров выглядит как

                  bar.setFoo() = 10;
                  


                  Негде тут в БД ничего писать без модифицирования интерфейса.


            1. VolCh
              10.05.2018 06:32

              Ужас геттеров и сеттеров программист начинает «ценить», когда 10-й раз обнаруживает, что на запрос каких-то свойств, происходит мутация других свойств, а на установку свойства его установка не происходит, умтановка происходит в другое значение или происходит установка и других свойств. Геттеры и сеттеры с тупым поведением практически практически (два «практически» не ошибка) бесполезны, а со сложным — нарушают ожидаемый контракт. Единственное разумное для них ожидаемое поведение кроме непосредственной работы со свойствами — генерация ошибок при невалидном состоянии свойств или попытке их привести в таковое.


      1. evseev
        09.05.2018 06:52

        Я не знаю как Smalltalk, а Python предусматривает возможность сокрытия полей. Нужно добавить "__" перед именем.


        1. k12th
          09.05.2018 10:57

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


  1. fairwind
    07.05.2018 20:40

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

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


    1. s-kozlov
      08.05.2018 06:12

      При чем тут городская баня фамилия страны и корни из любого поля?
      Речь идет об обычном duck typing:

      def quacker(duck: {def quack(value: String): String}) {
        println (duck.quack("Quack"))
      }
      


      Это Scala, тут строгая статическая типизация, но не нужно городить trait ради одного случая (особенно если это вообще чужие классы и trait там не навешаешь чисто физически).


      1. fairwind
        08.05.2018 10:40

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


        1. s-kozlov
          08.05.2018 11:09

          У автора getName возвращает свойство name у объекта. У страны может быть name (строка), у человека — тоже. У нас есть код, который может работать с «чем-то, у чего есть имя». Problems?


    1. fukkit
      08.05.2018 15:56

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


      1. 0xd34df00d
        08.05.2018 18:50

        Это решается тайпклассом, даже монад никаких не нужно и соплей.


  1. SirEdvin
    07.05.2018 20:52

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


    1. taujavarob
      07.05.2018 21:27

      Верно.

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

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


    1. s-kozlov
      08.05.2018 06:14

      Когда это вдруг реактивное программирование перестало быть функциональным?
      Может, значительно отличается от функционального то, что в ангуляре называют реактивным?


  1. IvanNochnoy
    07.05.2018 21:54

    Главная проблема функционального программирования, что его адепты не могут отличить монаду от моноида. Из за этого ФП туго идёт в массы, так как теперь все считают, что ФП — это когда цикл for заменяют на map-reduce.


    1. sshikov
      07.05.2018 22:30

      >ФП — это когда цикл for заменяют на map-reduce.

      А разве это не так? :) Честно говоря, я не вижу ничего плохого в замене циклов на map-reduce, при одном простом условии — что это делается осознанно, понимая, что мы приобретаем, и чем за это платим. При этом, заметьте, человек, способный проделать такую замену, уже не ограничен javascript и фронтендом, ему можно завтра дать в руки Hadoop — и он не потеряется там совсем.


      1. VolCh
        08.05.2018 06:45

        А причём тут javascript и фронтенд? array_map и ко в PHP лет на 5 раньше чем в JS появился :)


        1. sshikov
          09.05.2018 09:08

          javascript тут при том, что парадигма эта им не ограничена, а наоборот очень широко применяется в самых разных местах.


  1. IvanNochnoy
    07.05.2018 22:03
    +2

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


  1. slavap
    08.05.2018 05:35

    > «Но это же разные типы. Конечно, вы не можете применять метод класса person к стране!»
    Что мешает объявить интерфейс IName и реализовать у классов для которых это имеет смысл?


    1. s-kozlov
      08.05.2018 06:16

      А если это чужие классы?


      1. VolCh
        08.05.2018 06:47

        Проверять реализацию интерфейса по наличию метода getName(), а не по наличию строчки типа implements IName :)


      1. slavap
        08.05.2018 07:01

        А если нет obj.name? Как чистая функция отработает :-)?


        1. 0xd34df00d
          08.05.2018 07:25
          +1

          Не скомпилируется.


          1. slavap
            08.05.2018 07:28

            на каком языке?


            1. 0xd34df00d
              08.05.2018 07:33

              На функциональном.


              1. slavap
                08.05.2018 07:36

                А здесь?
                const getName = obj => obj.name;
                const name = getName({ uid: '123', name1: 'Banksy' });


                1. 0xd34df00d
                  08.05.2018 07:38

                  А здесь я не знаю, потому что я не знаю, что это за язык. Но явно не ML-подобный.

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


                  1. slavap
                    08.05.2018 07:57

                    Ну это у автора надо бы спросить, он на javascript примеры приводит.


      1. 0xd34df00d
        08.05.2018 07:26

        В случае ФП с Хиндли-Милнером никто вам не мешает определить тайпкласс в вашем модуле.


      1. donRumatta
        08.05.2018 17:38

        Зависит от языка. В шарпе приравнял к dynamic и вперед, вызывай, что хочешь. Только кому это нужно, когда скатываешься в итоге, как ниже заметили, к «пристальному вглядыванию».


  1. theonlymirage
    08.05.2018 05:49
    +1

    Функциональное программирование — это парадигма использования чистых функций в качестве неделимых единиц композиции, с избеганием общего изменяемого состояния и побочных эффектов.
    Чистая функция:
    Получая одни и те же входные данные, всегда возвращает один и тот же результат


    Как быть с random? Эта функция получает одинаковые данные (интервал чисел), но всегда возвращает случайное число из заданного интервала. Получается она не чистая и не может быть использована в ФП по определению выше.


    1. s-kozlov
      08.05.2018 06:17
      -1

      Где вы видели random, возвращающий именно случайное число?


      1. VolCh
        08.05.2018 06:59
        +1

        Таковыми можно считать функции, использующие аппаратные средства типа шумов в аналоговых трактах или квантовые флуктуации.


        1. s-kozlov
          08.05.2018 11:21
          -1

          В таких случаях упоротые ФПшники передают состояние «мира» в функцию и возвращают новое состояние.


          1. Jedi_PHP
            08.05.2018 14:18
            +1

            Эм… но почему упоротые? Random = во внешнем мире произошло какое-то (псевдо)случайное событие, и его состояние изменилось. Что вас собственно смущает?


            1. s-kozlov
              08.05.2018 17:03

              Меня смущает, что абстракция «внешний мир», а также передача соответствующего инстанса туда-сюда — чистой воды костыль, вызванный не какой-то реальной необходимостью, а непременным желанием любой ценой подогнать суровую действительность под чистое ФП.
              Упоротыми я считаю радикалов, которые не хотят признать, что ни ФП, ни ООП, ни молоток не являются наилучшими инструментами во всех ситуациях.


              1. Jedi_PHP
                08.05.2018 17:44
                +1

                > абстракция «внешний мир»… — чистой воды костыль… вызванный… непременным желанием любой ценой подогнать суровую действительность

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

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

                А такие вообще встречаются в дикой природе?


                1. s-kozlov
                  09.05.2018 19:14

                  Как видите, тут «внешний мир» — хорошая, годная, даже необходимая абстракция.


                  «Тут» — это в рамках ФП всё-таки.

                  А такие вообще встречаются в дикой природе?


                  К сожалению, довольно часто.


                  1. VolCh
                    10.05.2018 06:43

                    «Внешний мир» — хорошая абстракция для любой (наверное) парадигмы программирования. Правила работы с ним могут быть разными, могут реализовываться на уровне синтаксиса или семантики ЯП, пользовательского кода или соглашений, но само наличие правил (с какими-то гарантиями их ненарушения, конечно) очень помогают при разработке сложных систем, упрощая контроль над тем, что делает та или иная программная единица без изучения её кода. Простое соглашение типа «используем глобальные переменные исключительно на глобальном уровне в пределах одного файла» очень упрощает жизнь.


              1. 0xd34df00d
                08.05.2018 18:52

                Это не костыль, да и передача не явная и не торчит везде.

                Просто это правда удобно, когда вы можете посмотреть на один лишь тип функции и сразу сделать выводы, что она не полезет на сервер, не начнёт читать файлы, не сохранит что-то в БД, не будет использовать источники недетерминированности для своего результата.

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


                1. s-kozlov
                  09.05.2018 19:17

                  1. 0xd34df00d
                    09.05.2018 21:58

                    Я не согласен с цитатой (и с окружающим её контекстом).

                    Да, научиться программировать на ФП с монадками и этим всем действительно требует больших усилий, у них крутая кривая обучения. Но после того, как вы научились, мозг можно включать всё реже, и это приятно. А что мозг практически не нужно включать для отладки, потому что отладка нужна очень нечасто — приятно вдвойне.

                    Не рассказывайте только авторам таких статей про зависимые типы, они ж совсем изойдут.


                    1. s-kozlov
                      10.05.2018 05:58

                      А вы всю статью прочитайте, не поленитесь. Там как раз ниже того куска следующее:

                      Иронично, но Amr Sabry (мой консультант в PhD) является со-автором одной из таких статей. Он пытался реализовать семейство языков miniKanren Дэна Фридмена, но так и не смог понять, каким образом сконструировать монады. Он попросил помощи у Олега Киселева, человека, который возможно лучше всех разбирается в системе типов Haskell. И, если вы не знаете, Amr Sabry, вероятно, самый осведомленный в мире человек по теме чистых функциональных языков программирования. Они стали соавторами целой статьи после того, как с помощью Олега, им все-таки удалось реализовать желаемое. Ирония в том, что у Дена Фридмана не возникло вообще никаких проблем с реализацией этого куска кода на Scheme.


      1. 0xd34df00d
        08.05.2018 07:26

        В некоторых реализациях этого.


    1. VolCh
      08.05.2018 06:53

      random конечно не является чистой функцией. В лучшем (для чистоты) случае это функция со своим внутренним состоянием, в худшем — получает энтропию из физического мира.


    1. 0xd34df00d
      08.05.2018 07:32
      +3

      Функциональщики в любой непонятной ситуации, особенно когда дело нечисто, заворачиваются в монаду.

      Их с десяток было, как пример, вот или вот. random-fu вообще клёвое и большое.


    1. taujavarob
      08.05.2018 21:27

      Получается она не чистая
      Она не чистая. Да и любая функция ввода/вывода или обращения к базе или читающая файл или пишущая в файл — не чистая.


      1. Chamie
        09.05.2018 13:58

        Ну, почему же? Если состояние базы/файла — это часть и входных параметров, и возвращаемого значения (если она его меняет), то вполне может быть чистой. Как редьюсеры в Redux'е.


        1. VolCh
          10.05.2018 06:47

          Нет, чистая функция работы с базой или файлом возвращает новую базу или файл, а не меняет переданные. :) Редьюсер в редакс не меняет стейт, он возвращает новый. Меняет стейт грязный dispatch, который передаёт редьюсеру текущий стейт, получает новый и заменяет текущий на новый в сторе.


    1. evseev
      09.05.2018 07:09

      Функция random возвращает число по одному и тому-же закону распределения. Сколько-бы вы ее раз не вызвали закон останется прежним.


      1. s-kozlov
        09.05.2018 19:18

        Чистая функция должна быть детерминированной.


        1. VolCh
          10.05.2018 06:59

          Функции random, да и вообще любые подпрограммы в реальных вычсистемах детерминированы. Отличие чистых функций от нечистых в плане детерминированности на практике в том, может ли программист контролировать все параметры функций при её вызове или она под капотом имеет дополнительные, внешние и внутренние состояния.


          1. s-kozlov
            10.05.2018 09:47

            Детерминированность — необходимое, но не достаточное свойство чистой функции.


      1. VolCh
        10.05.2018 06:55

        Термин «закон распределения» в контексте функции random обычно относят к случайным величинам, но в подавляющем большинстве случаев функция random к ним отношения не имеет даже в теории.


  1. theonlymirage
    08.05.2018 06:24

    Где вы видели random, возвращающий именно случайное число?

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


    1. 0xd34df00d
      08.05.2018 07:35
      +1

      вспоминаем python, его yield, итераторы и генераторы

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

      Собственно, если к спискам относиться не как к структуре данных, а как к управляющей структуре, то всё встаёт на свои места.


  1. Druu
    10.05.2018 01:08

    Предыдущая функция getName() работала с любым входящим объектом.

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


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

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


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


    1. taujavarob
      10.05.2018 10:17

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