Комментарии 50
Пункт 7
Можно добавить default для того чтобы компилятор сказал, что есть необработанный case:
// здесь будет ошибка компиляции, если не все case описаны
default:
const _exhaustiveCheck: never = shape;
throw new Error(`Unhandled shape kind`);Да, это рабочий паттерн и важное уточнение, спасибо — мне стоило про это упомянуть. Но то, что я сам про это забыл, лишний раз доказывает тезис: язык мне здесь не помогает, это просто соглашение, которое нужно держать в голове и применять в каждом switch вручную.
Ну и проблему открытой иерархии это, к сожалению, не решает.
Можно использовать следующий eslint plugin https://typescript-eslint.io/rules/switch-exhaustiveness-check/ для того чтобы не держать это в голове
TS проверит exhaustiveness — но только внутри файла. Ничто не помешает кому-то в другом файле расширить тип:
// Другой файл, другой разработчик, три месяца спустя
type Shape =
| { kind: "circle"; radius: number }
| { kind: "rectangle"; width: number; height: number }
| { kind: "triangle"; base: number; height: number } // Добавили!
// Функция area() теперь неправильна — но компилятор молчит.
// Потому что иерархия открытая.Не понятно, как локальный тип может влиять на функцию area? При импорте Shape TS скажет, что конфликт с оригинальным: `Import declaration conflicts with local declaration of 'Shape'`. Мердж типов работает только для interface , что бывает полезно при разработке либ.
По Haskell абсолютно справедливое замечание, стоило быть точнее. С одним лишь важным нюансом: насколько я знаю, Haskell "культурно поощряет" именно стиль с использованием Either / Maybe монад.
По ОCaml и HKT - супер важное уточнение. В таком случае отсутствие HKT - это больше ограничение выразительности при написании обобщённых абстракций, а не маркер "функциональности" языка как таковой. Поправлю формулировку, спасибо!
Вы буквально сформулировали в этой статье всё то, что уже год живет в моей голове, почти слово в слово! Всегда удивлялся, когда смотрел записи собеседований на ютубе, что даже очень опытные программисты на JS претендующие на огромные зарплаты очень своеобразно отвечают на вопросы про фп, в особенности когда их спрашивают о недостатках парадигмы. Скажите, есть ли у вас опыт использования fp-ts или effect-ts в продакшене?
Effect-ts в продакшене попробовать не довелось, а вот с fp-ts поработал на двух проектах.
На мой взгляд, библиотека очень мощная, но с онбордингом возникали определенные сложности. Если люди прежде не писали на функциональных языках, код на fp-ts для них выглядит тяжеловесно. И местами, надо признать, это ощущение вполне заслуженно — порог входа высокий и код визуально кажется совсем "чужим", не похожим на привычный TS.
Кроме того, есть сугубо практический нюанс интеграции. Допустим, мы пишем React-приложение. Подключаем React Query, которая, естественно, ничего не знает про монады Task / TaskEither. Приходится писать адаптеры, превращающие ТЕ обратно в обычные Promise. Да, это пара строк кода, но React Query — не единственная такая библиотека. Любой хук форм или роутер заставляет держать в голове эти пограничные прослойки.
В итоге появляется постоянный оверхед (пусть и небольшой), оправданность которого в команде — вопрос дискуссионный. Лично я считал, что строгость типов и обработка ошибок того стоят, кто-то другой скажет, что нет. Собственно, это прямое продолжение того, что я в статье назвал «гравитацией языка» — экосистема постоянно тянет тебя обратно в императивный мир.
Впервые слышу, чтобы JS называли функциональным)) Ну вы можете конечно обмазаться rx.js, immutable.js. На чистом JS, никто не пытался прикинуться функциональщиком) Еще и без длинных носков
Но они — костыли поверх языка, спроектированного с мутабельностью в голове. Решения об использовании подобных библиотек полностью лежит на конкретной команде и никак не "форсируется".
То поверху, то понизу - если вы хотите ФП на JS, по каким либо причинам вместо clojurescript или другого вообще стека, то это нормальные либы. Костыли или нет, это уже, что-то на снобском
Каждое из ограничений — не баг и не просчёт.
Да и хватит писать посты с ллмками, отвратительно.
Впервые слышу, чтобы JS называли функциональным
Этот тезис «кочует» по страницам книг, звучит в публичных выступлениях, есть даже целые курсы, в т.ч. от крупных и известных платформ вроде «frontend masters» где это мнение присутствует, так же я сослался на свой личный опыт проведения интервью. Я к тому, что у нас с вами опыт может сильно отличаться и это вполне нормально. В то же время то, что вы не слышали тезис «JS - функциональный язык» не означает, что этого тезиса не существует.
То поверху, то понизу - если вы хотите ФП на JS, по каким либо причинам вместо clojurescript или другого вообще стека, то это нормальные либы. Костыли или нет, это уже, что-то на снобском
Всё это - ваше личное оценочное суждение, не более. Никакого снобизма тут не было: я лишь констатировал факт того, что «из коробки» в языке решений не существует, требуется использование дополнительных инструментов. Некоторые из них абсолютно гениальные(effect ts, fp-ts), некоторые, на мой взгляд, менее удачные.
фундаментальный вопрос: что считать «типом» для сопоставления в динамическом языке без sealed traits
По мне так то, как в erlang реализован pattern matching – вполне себе распрекрасный вариант, для js в общем есть даже всякие подобия типа https://github.com/natefaubion/matches.js/.
Неплохо.
Не согласен с заявлением, что functor - это high-kinded type, это typeclass, нет никаких высших порядков.
И вот это позабавило:
Язык формирует стиль кода и культуру — не через запреты, а через то, что в нём естественно.
Поэтому, когда вы открываете чужой код на этих языках, вы видите FP-паттерны не потому что автор был дисциплинирован, а потому что язык просто не давал писать иначе.
Я не утверждал, что функтор - это HKT. Я пытался на простом примере показать механизм системы типов, который позволяет выразить тайпкласс (т. е. что функтор параметризован HKT)
Не согласен с заявлением, что functor - это high-kinded type, это typeclass
А typeclass — это что такое?
В хаскеле это
λ> :k Functor
Functor :: (* -> *) -> ConstraintШтука, принимающая * -> * — HKT по определению (насколько я знаю — в привычных мне теориях типы просто могут зависеть от других типов, и замыкание всего этого автоматически даёт HKT и прочие штуки).
Тут вообще нужно смотреть с другой стороны. JS изначально даже не классический ООП язык. То что к нему впоследствии приделали классы сделало еще хуже. Получилась еще более кривая и проблемная пародия на Java. И вот из одной крайности, сообщество повело в другую. В ФП. Почему? Потому что это закрывает многие базовые проблемы JS. Например, функции как единицы построения программы подходят куда лучше кривых и проблемных объектов и классов в JS. Еще более важным моментом является использование концепции иммутабельности данных для закрытия проблем с работой в асинхронной среде.
По поводу отсутствия хвостовой рекурсии, типов высших порядков, отсутствия паттерн матчинга - это все фигня и вообще не показатель функционального языка. Это скорей критерии чистого Haskell-подобного языка. Взять Scala, там исключений нет или есть мутабельность по умолчанию? Да там половина языка Java подобна, о чем говорить. В том же Clojure исключения есть, нет никакиких pattern matching, никаких игр с монадами и Higher-Kinded Types.
По мне главные критерии ФП языка - это все-таки неизменяемость из коробки и логика языка в которой программа строиться вокруг функции как основного юнита.
Главная проблема в сообществе JS - это попытка играть во "взрослый/чистый" ФП подобный Haskell не имея никаких средств поддержки языка. Отсюда выходит весь ад. Попытки играть в каррирование, в монады превращает код в адское нечитаемое месиво. Но это не особенность JS. На одном из проектов встретил точно такой же подход в Java, где начинались модных ФП книг и пошли эмулировать монады и каррирование, как итог - еще хуже чем то что я видел в JS.
Понятно что JS - это не ФП язык. Но не нужно все мерить по Haskell. JS/TS в такой ситуации, что разумная доля ФП подхода без перебора пока что дает сильно больше преимуществ чем другие подходы. Вопрос лишь в адекватных рамках этого.
Я очень со многим из того, что вы пишите согласен.
По мне главные критерии ФП языка - это все-таки неизменяемость из коробки и логика языка в которой программа строиться вокруг функции как основного юнита.
В одной из книг про Cats Effect читал такое определение:
ФП - это локальное рассуждение(local reasoning) и композиция, всё остальное есть производные этих двух.
Вспомнил его, когда читал ваш пост, показалось довольно созвучным.
Взять Scala, там исключений нет или есть мутабельность по умолчанию? Да там половина языка Java подобна, о чем говорить.
Согласен, но с одним на мой взгляд важным уточнением: в Scala последние несколько лет укоренилась тенденция создания эффектных систем (Cats Effect, Zio), что очень сильно "качнуло" маятник в сторону чистого функционального программирования.Т.е. да, в языке действительно ярко выраженная мультипарадигмальность, но на практике эта дихотомия сегодня ощутимо менее выражена, если сравнивать с периодом популярности Akka, например.
Понятно что JS - это не ФП язык. Но не нужно все мерить по Haskell. JS/TS в такой ситуации, что разумная доля ФП подхода без перебора пока что дает сильно больше преимуществ чем другие подходы. Вопрос лишь в адекватных рамках этого.
Вот это, и то, что вы пишите касательно Haskell-like языков - довольно интересный тейк в том смысле, что ведь действительно в мире программирования есть такие... "Маппинги" вроде "Мы говорим ООП, подразумеваем - Java", мы говорим "FP, подразумеваем - Haskell", но довольно серьезный вопрос в том, насколько корректны подобные "маппинги" в принципе.
Недавно завезли немного сахара для итераторов: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Iterator/map Через пару лет может и до asynciterator дойдут.
Вы из какого года пишете?
Через пару лет может и до asynciterator дойдут
Завезли почти десят лет назад
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/AsyncIterator
А для чего это может быть нужно? Что с ними делать?
Что вы имеете ввиду, есть пример из других яп?
Имеется ввиду что map, filter и т.д. возвращают промисы? Это же принесет больше проблем, нет? Что если там девять элементов, и на обработке шестого произошла ошибка? Или например map отработал, а на reduce в третьем элементе произошла ошибка?
Статья очень интересная, сам хотел буквально пару месяцев назад писать что-то подобное, только про проявления и развитие парадигмы ФП в JS. JS изначально был попыткой перенести функциональный Sheme за 10 дней в браузеры. А после, чтобы другие браузеры могли его использовать завели уже стандарт EcmaScript, но уже как ООП (чтобы новый стандарт приняли благосклонно), ну и в последствии развитие шло в разных направлениях. Что-то добавляли от ФП, например стрелочные (лямбда) функции, что-то от ООП - классы.
В целом, мультипарадигменность - это как раз и не ФП и не ООП.
обрабатывать «исключительные» ситуации без ручных проверок повсюду
Фундаментальная проблема с исключениями в том, что вы не можете дать программистам механизм обработки ошибок и ожидать, что они не будут им пользоваться. Вот по этой схеме JSON.parse() и бросает исключения, хотя в парсинге json’а нет ничего исключительного, это совершенно нормальный сценарий выполнения.
Далее повсюду появляются catch’и, потому что они НУЖНЫ всюду, и ситуация «без ручных проверок повсюду» превращается в «ручные проверки повсюду, но вместо универсального if для ошибок используется новый, ничем не обусловленный синтаксис». И хорошо ещё, когда в стандартной библиотеке сделан .TryParse(). Впрочем, он не спасает, а только ещё больше всё запутывает.
А по теме статьи: берите тех, кто говорит про .map(). Это люди, которые схватывают суть на лету и приносят максимальную пользу. Аналогично, когда на вопрос про ООП люди отвечают «это гарантированный автокомплит после нажатия точки», такой критерий выглядит высокоуровневым и смешноватым, но если человек до него дошёл, он практический философ, который способен выкупить суть и изложить её простыми словами (подумайте сами, как этот критерий задаёт набор низкоуровневых требований). Пользы от такого человека обычно гораздо больше, чем от тех, кто часами трындычит про «три кита — энкапсуляцию, полиморфизм и наследование» (при том, что в некоторых местах я видел строгое полиси не использовать наследование или хотя бы писать объяснительную). Так же и .map() — это выраженная в ёмкой форме гарантия декомпозиции схем преобразования, поддержки встраивания запросов в язык без необходимости таскать DSL и ещё сорока бочек всякого разного. А то, что большие коллекции жрут память — так не надо грузить в браузер большие коллекции (на сервере же с этим мирятся, платя памятью за обобществлённость виртуальной машины, которая никому не принадлежит).
При всём уважении - похоже, у вас у самого какая-то “каша в голове” :(
Имманентный свойством ФЯП (любого - без каких-либо исключений) является исключительно система типов, которая умеет в HOF (а у вас про них вообще нет ничего). Другими словами - без наличия в системе типов функций высшего порядка - ФЯП не бывает.
А всё остальное - в том числе, и всё вами перечисленное - либо ортогонально, собственно, “функциональности”, и является следствием других категорий ЯП. Либо является следствием наличия HOF в системе типов.
Ну и - скажем так, на всякий - про “странности”…
Сама по себе иммутабельность - не есть гарантия ссылочной прозрачности. А ссылочная прозрачность - это про декларативность, а не про функциональность. Имхо, это важно понимать, и различать.
Аналогично с TCO. Оно важно - исключительно - с точки зрения “энергичной” evaluation strategy. Причем тут функциональность?! Зачем вам TCO, если у вас везде, например, call-by-need?
“Ленивость” - aka call-by-need - мало того, что “мимо” (в смысле, это не про функциональность), но нужно же понимать, что оно “конфликтует” с TCO. А вы их в один список ставите. Зачем?!
try/catch — это инструкция, а не выражение, её нельзя композировать …
Какой-нибудь Erlang смотрит на вас с недоумением :-)
А то, что вы пишите “про монады” - это - по большому счёту - про наличие/отсутствие, скажем так, “оператора композиции”. И - опять же - к функциональности, как таковой, оно отношения не имеет.
Про HKT и PM - вы, похоже, не понимаете место этих концепций в ЯП (любом).
Ну и про “гравитацию”…
В JS функциональный стиль — это осознанный выбор, который нужно делать каждый раз заново. Default — императивный подход, и он постоянно притягивает к себе.
Это, имхо, весьма показательно :-)
Сама по себе иммутабельность - не есть гарантия ссылочной прозрачности. А ссылочная прозрачность - это про декларативность, а не про функциональность. Имхо, это важно понимать, и различать.
Я утверждал обратное — что мутабельность по умолчанию ссылочную прозрачность нарушает, а не что иммутабельность её гарантирует, это разные тезисы.
Про конфликт TCO и ленивости — не очень понял тезис. Haskell ленивый, но TCO там есть и используется. Проблема стека трансформируется в space leaks, но не исчезает совсем. Если имеется в виду что они решают разные проблемы — согласен, но это скорее ортогональность, чем конфликт.
Касательно try/catch и Erlang: конкретно тут он ни при чём, в том блоке я разбирал try/catch в JS, и там это является инструкцией, а не выражением. И это не моё мнение, а факт.
Имманентный свойством ФЯП (любого - без каких-либо исключений) является исключительно система типов, которая умеет в HOF (а у вас про них вообще нет ничего). Другими словами - без наличия в системе типов функций высшего порядка - ФЯП не бывает.
Интересное определение, но оно ломается на примере, который вы сами привели — Erlang динамически типизирован, никакой системы типов с HOF там нет. Тем не менее функциональным языком он считается.
Если вы имели в виду просто сам факт того, что для ФП необходимо иметь возможность писать HOF - то с этим глупо спорить, всё так. Моя проблема с формулировкой в том, что Если HOF — единственный(или основной) критерий, то JS функциональный язык, it's simple as that.
Про HKT и PM - вы, похоже, не понимаете место этих концепций в ЯП (любом).
Звучит как ваше оценочное суждение без каких-либо оснований.
Я утверждал обратное — что мутабельность по умолчанию ссылочную прозрачность нарушает, а не что иммутабельность её гарантирует, это разные тезисы.
Мой поинт-то в том, что не понятно, какое отношение наличие/отсутствие ссылочной прозрачности имеет к функциональности ЯП?
Haskell ленивый, но TCO там есть и используется.
Ну если под TCO понимать само наличие call-by-need - то да :-) Но call-by-need - это не TCO. И GR - не TCO. Где вы TCO в haskell нашли? Или - а что вы вообще под TCO тогда понимаете?
Интересное определение, но оно ломается на примере, который вы сами привели — Erlang динамически типизирован, никакой системы типов с HOF там нет.
?! В Erlang замечательная система типов. С чего вы взяли, что её (системы типов) там нет?!
… то JS функциональный язык, it’s simple as that.
Ну так и есть. JS функционален ровно в той же степени, в которой функциональна, например, scala. Ибо функциональность - это не про модель вычислений (декларативность/императивность), не про стратегию вычислений (упрощая, ленивость/энергичность) и не про ещё кучу аспектов.
Функциональность - это про вполне определенное свойство системы типов.
Другое дело, что можно начать рассуждать о - так сказать - “чистоте” этой функциональности… :-)
Звучит как ваше оценочное суждение без каких-либо оснований
Ну потому что, то что вы пишете в этих разделах - “делать мне больно мозг” :-) А “приведение оснований” - это будет долго и оффтопик.
Про HKT вы там и сами уже пришли к тому, что “оно лишнее”. С моей точки зрения - странным путем, но пришли. И к тому, что PM - из такой же категории - рано или поздно - придете.
В конце концов, какой-нибудь, common lisp вполне себе живет же без PM на уровне языка :-) Да и само по себе требование exhaustiveness для PM - в разрезе наличия динамической типизации, например - выглядит “странновато”, имхо. Ну или надо будет какое-нибудь Scheme с Clojure и прочими Erlang’ами “лишать звания” :-)
Ну так и есть. JS функционален ровно в той же степени, в которой функциональна, например, scala. Ибо функциональность - это не про модель вычислений (декларативность/императивность), не про стратегию вычислений (упрощая, ленивость/энергичность) и не про ещё кучу аспектов.
Функциональность - это про вполне определенное свойство системы типов.
Другое дело, что можно начать рассуждать о - так сказать - “чистоте” этой функциональности… :-)
На мой взгляд вы очень сильно упрощаете. И ваша последняя строчка, где вы как бы "забираете" часть своего аргумента назад - прямое тому подтверждение. JS даже не рядом со Scala в вопросах "функциональности", ни на каком из уровней. Плотность и эргономика функциональных фич в Scala на порядки выше, а систему типов и сравнивать смешно.
?! В Erlang замечательная система типов. С чего вы взяли, что её (системы типов) там нет?!
Вы либо передёргиваете, либо мы изначально говорим о разном. Если вы говорите про спецификацию типов, тогда в вашем первом посту я неверно вас понял.
Мой поинт-то в том, что не понятно, какое отношение наличие/отсутствие ссылочной прозрачности имеет к функциональности ЯП?
В узком смысле слова вы правы: СП - это свойство выражения, а не языка. Но проблема в том, что то, что не ссылочно-прозрачно крайне плохо композируется. Если нет функциональной композции и / или она ограниченна, то писать функциональный код становится проблематично и больно.
Можно долго спорить о терминологии, но факт в том, что культура написания кода на разных языках формируется не только на основании спецификаций и систем типов, но еще и в практическо-прагматической среде. Приживается то, что более естественно для конкретного языка, что более удобно для решения задач, под которые этот язык подходит. И на мой взгляд это куда важнее, чем то, о чём вы говорите выше. Я к тому, что ставить == между языками с точки зрения "функциональности" основываясь только на том, что система типов знает про HOF - это очень сильное упрощение, которое полностью оторванно от реальной практики применения языков: сколько вы лично видели проектов в продакшене на JS, где активно используется карирование, частичное применение, монады, изоляция эффектов(то, что для вас всё это не является критерием фп - и я хочу это подчеркнуть двумя толстыми линиями - это ваше личное оценочное суждение а не научный консенсус)? В то же время чисто функциональную Scala я вижу регулярно, в особенности после роста популярности Cats Effect и Zio
На мой взгляд вы очень сильно упрощаете. И ваша последняя строчка, где вы как бы “забираете” часть своего аргумента назад - прямое тому подтверждение.
Ничего я не “забираю”. Я лишь намекаю, что вы можете взять “функциональную чистоту” JS, и сравнить её с аналогичной “чистотой”, например Scala… чисто “для смеха” :-)
Плотность и эргономика функциональных фич в Scala на порядки выше, …
Это каких, например? Наличие “оператора композиции”? Или вы о чём-то другом?
… а систему типов и сравнивать смешно.
А причем тут “функциональная чистота”, и система типов?! “Чистота”, она как раз в гораздо большей степени про модель вычислений, чем про что-то другое. Нет?
Но проблема в том, что то, что не ссылочно-прозрачно крайне плохо композируется.
С какого перепугу-то?!
… сколько вы лично видели проектов в продакшене на JS, где активно используется карирование, частичное применение, монады, изоляция эффектов(то, что для вас всё это не является критерием фп - и я хочу это подчеркнуть двумя толстыми линиями - это ваше личное оценочное суждение а не научный консенсус)?
:-) Ну я весьма значительную часть своей карьеры провел за Erlang’ом… где - внезапно - из всего вами перечисленного - “из коробки” - есть только “изоляция эффектов”. И? Erlang - как ФЯП - недосягаем что для JS, что для Scala. Дальше-то что? Порассуждаем за “а насколько функционален Erlang?”, если в нем “всего этого” нет? :-)
В то же время чисто функциональную Scala я вижу регулярно, в особенности после роста популярности Cats Effect и Zio
Скажем так… есть некоторая разница (с), между функциональным программированием, и программированием в функциональном стиле. Сдается мне - вы говорите про второе, и - местами - путаете “это” с первым.
Это каких, например? Наличие “оператора композиции”? Или вы о чём-то другом?
Я отразил своё мнение по этому поводу в статье, предметно, с примерами кода как на Scala так и на JS. То, что лично вас это не убеждает полностью ортогонально правдивости или ложности моей аргументации.
:-) Ну я весьма значительную часть своей карьеры провел за Erlang’ом… где - внезапно - из всего вами перечисленного - “из коробки” - есть только “изоляция эффектов”. И? Erlang - как ФЯП - недосягаем что для JS, что для Scala. Дальше-то что? Порассуждаем за “а насколько функционален Erlang?”, если в нем “всего этого” нет? :-)
Вы не отвечаете на мой вопрос: я не спрашивал вас о вашем карьерном пути в Erlang'e, я спрашивал: "как часто вы видите применение паттернов ФП (и, если вам угодно, "программирование в функциональном стиле") в проектах написанных на JavaScript?" В ответ на это вы приводите мне Erlang в качестве примера с аргументацией "там ничего из того, что вы перечислили - нет". И что это доказывает? Я вообще не могу понять, к чему вы это написали: естественно Эрланг функциональный язык, да, в нём отсутствуют из коробки некоторые перечисленные мною фичи, а еще там нет переменных, циклов и есть иммутабельность (и это, в том числе те признаки, что я указал в статье). Я нигде не писал, что в ФЯП должны присутствовать все пункты одновременно, более того: речь вообще шла исключительно о JS/TS. Вы упомянули, что в статье ничего не сказано про HOF. В ней ничего нет об этом потому, что в JS HOF есть, проблема в том, что на практике это никак не помогает, т.к. язык и его экосистема сподвигает вас писать циклы, сподвигает вас писать мутабельный код, сподвигает вас использовать что угодно кроме тайпклассов, сподвигает вас писать try/catch c мутациями внутри, сподвигает писать кругом проверки на null & undefined, и так далее, и всё это потому, что в этом языке так писать удобнее, эргономичнее и проще, а еще именно такие инструменты у вас имеются из коробки, а код в большинстве случаев хоть и менее безопасный но более читабельный и привычный для комьюнити и разработчиков. Да, в Эрланг у вас может не быть HKT, но и цикл вы не напишите.
А причем тут “функциональная чистота”, и система типов?! “Чистота”, она как раз в гораздо большей степени про модель вычислений, чем про что-то другое. Нет?
Я нигде и не утверждал обратного, вы за меня додумываете. Если бы у вас был опыт написания строготипизированного кода в функциональном стиле на TS, то вы бы знали, какой эквилибристикой порой приходится заниматься, чтобы ваши типы "сошлись" и чтобы TS вас верно понял. Особенно когда речь касается типизации функций высшего порядка. Откройте source fp-ts и посмотрите, как они определяют функцию pipe, например.
С какого перепугу-то?!
С такого. У вас нарушается принцип "подстановки" (substitution) и это ломает композицию. Как минимум, вы получаете строгую зависимость от порядка вычислений.
Пока что из всей дискуссии я так и не понял, что конкретно делает Erlang "недосягаемым ФЯП" в рамках вашего определения через HOF в системе типов. А в местах, где должен идти аргумент вы просто вставляете ":-)". Не говоря о том, что часть вашего текста - это ваше оценочные суждения поданные в императивной и назидательной нотации без какого-либо раскрытия и доказательства. Вы либо приводите аргумент полностью, от и до, без намёков и пространных суждений, либо не приводите его вовсе, а то мне приходится заниматься дешифровкой ваших сообщений.
Вы не отвечаете на мой вопрос: я не спрашивал вас о вашем карьерном пути в Erlang’e, я спрашивал: "как часто вы видите применение паттернов ФП (и, если вам угодно, “программирование в функциональном стиле”) в проектах написанных на JavaScript?
Хорошо, давайте “на пальцах”…
Для меня - как для адепта функционального программирования - есть один единственный “паттерн ФП” - это прием ф-ции, как аргумента и/или получение ф-ции, как результата вычисления.
И с этой точки зрения - да, я постоянно вижу “применение паттернов ФП”, в том числе и в JS.
небольшой оффтопик про паттерны ФП

Эта картинка из замечательного доклада Скотта Влашина (Scott Wlashin). Делал он его уж больше десяти лет назад, но оно по прежнему - имхо - актуально.
А вот всё остальное (в том числе, и большинство из перечисленных вами “функциональных фич”) - это именно что стиль - который напрямую к, собственно, функциональному программированию не имеет абсолютно никакого отношения.
И стиль этот - по большому счету - определяется синтаксисом конкретного языка. Какие-то “штуки” - так или иначе - системой типов. Только и всего.
Но - и это важно понимать - что наличие этих “фич”, что их отсутствие - абсолютно никак не сказывается на, собственно, функциональности ЯП. Сказывается это всё - в лучшем случае - на удобстве. И с вот этим вот - лично я, например - не спорю.
Но вы назвали свою публикацию вполне определенно, а не, например, «Почему JS/TS — это боль при программировании в функциональном стиле» :-)
… а еще там нет переменных, циклов и есть иммутабельность (и это, в том числе те признаки, что я указал в статье)
Вот опять. Вы вроде начинаете про одно, а заканчиваете другим, и сами себя запутываете.
Смотрите… важно то, что там нет переменных. А о иммутабельности, я извиняюсь, чего вы писали? Это разве “те признаки”? При том, что мутабельность - и формальном, и в практическом смыслах - там вполне себе есть.
И с циклами - похожая картина. То, что они там просто невозможны, в силу отсутствия и переменных, и присваивания - это одно. Но вы-то о циклах, емнип, упоминаете исключительно в разрезе “ленивости”. Это разве “те признаки”? Для меня - очевидно, что вы “про другое”. Надеюсь, и для вас это теперь очевидно.
т.к. язык и его экосистема сподвигает вас писать циклы …
Я сильно надеюсь, что из того, что я написал выше, вы уже поняли, что всё вами тут перечисленное никакого отношения у функциональности (или не функциональности) ЯП не имеет.
Да, в Эрланг у вас может не быть HKT, но и цикл вы не напишите.
Дались вам эти “циклы”… ну загляните уже “под капот”, например, итераторам в scala, и убедитесь, что “чудес не бывает” ™, и “циклы” там вполне себе есть. Просто, для того, чтоб их “прям ваще” нигде не было, надо несколько большее, чем какая-либо система типов.
Откройте source fp-ts и посмотрите, как они определяют функцию pipe, например.
И? Что такого “ужасного” я должен там увидеть? Собственно, чем “это” принципиально отличается от наличия 100500 каких-нибудь FunctionN в той же Scala? Не… “сутевое” различие-то я понимаю, но у вас - насколько я понял - претензии не к сути, но к форме. Или о чём речь?
У вас нарушается принцип “подстановки” (substitution) и это ломает композицию.
Вот мы дошли до “сладкого”. На всякий случай, уточню - мыж оба говорим об функциональной композиции? Так?
Если таки да - можно для “особо одаренных” (типа меня) раскрыть, что это за принцип такой? Что именно и куда вы “поставляете” при композиции?! Мыж не вычисляем ф-ции, которые “соединяем”. Мы вычисляем именно что их композицию. И результатом этого вычисления является третья ф-ция. Которую мы - пока ещё - не вычисляем. Нет?
Как минимум, вы получаете строгую зависимость от порядка вычислений.
Я либо вообще не понимаю “о чём вы”, либо я сейчас вам открою страшную тайну.
Чтобы не было “строгой зависимости от порядка вычислений” (и соответственно, прям нужна была СП) необходимо call-by-need. А тут (при вычислении композиции) его и рядом нет. Т.е. - во-первых - наличие/отсутствие СП абсолютно не важно. А во-вторых, функциональная композиция - таки задает порядок вычисления результирующей ф-ции.
Если же речь не о функциональной композиции, а о чём-то другом, то лучше таки пояснить.
Пока что из всей дискуссии я так и не понял, что конкретно делает Erlang “недосягаемым ФЯП” в рамках вашего определения через HOF в системе типов.
В рамках “моего определения через HOF” - ничего. Наличие в системе типов HOF - это просто необходимое и достаточное условие, чтоб считать ЯП функциональным.
А “недосягаемым” (для JS и для Scala) его делает модель вычислений, которая позволяет легко и просто получить такую множественную диспетчеризацию, которая на этих языках невозможна в принципе.
JS - мультипарадигменный, истинное искусство, правильно применять те или иные подходы, и неизменяемость, и pure-functions. А отсутствие поддержки в языке решается на ревью и линтерами.
На практике не особо нужно фп в полной мере, а лишь некоторые подходы.
ТСО не добавляют в js, потому что смысла в этом нет. Если функция подходит для ТСО, то её проще и правильнее переписать на цикл. Тот же "хвостовой" факториал выглядит искусственно в сравнении с обычным: "технический" второй параметр, запутанная логика.
удалено
Ничто не помешает кому-то в другом файле расширить тип
// Другой файл, другой разработчик, три месяца спустя
Вообще так не работает. Если у вас есть тип `type Shape` в одном файле, то создание одноименного типа в другом файле (другим разработчиком через 3 месяца) не имеет никакого отношения ни к первому типу, ни к функции, которая тот первый тип использовала. Ремарка про "открытую иерархию" в контексте этого примера совершенно непонятна.
Спасибо вам огромное, что заметили эту жесть)) Писал этот кусок глубокой ночью, то ли в каких-то более старых версиях ТS была эта проблема (но даже если и была, то выглядела совсем не так, как я ее описал), то ли я ее сам "выдумал". Весь этот кусок удалил, ваше замечание полностью по делу, спасибо еще раз!
Declaration merging есть у interface, и его действительно можно расширить в одном файле или даже из другого файла, используя declare module и расширение интерфейса внутри. Раньше, когда проекты были преимущественно со скриптами и без ES модулей (`"moduleDetection": "legacy"`), interface с одним именем в двух разных файлах (скриптах) имел declaration merging
Почему JS/TS — не функциональный язык (и почему это важно понимать)
Потомучто он процедурный?
Все же унаследованная от scheme иммутабельность нативная в js имеется - это строки, по факту иммутабельный массив uint16. А на их мощном фундаменте при желании можно легко построить всю эту фп халабуду в чистом виде. Так что дело не в языке, а в умении на нем разговаривать.
Согласно легенде, Айк сперва хотел использовать Scheme (который вполне себе функциональный, как и любой Lisp).
Но начальство решило, что не взлетит, и тогда он сделал JS - ооочень сильно вдохновляясь Scheme. Если с JS отковырнуть синтаксический сахар, то увидим начинку из Лиспа.
Так что ФП в JS было с самого рождения. Просто само ФП с тех пор претерпело изменения.
Хорошо, от нас что требуется? Вам писать академический код, нам корпоративную лабуду. Лучше из языков старое выпиливали, чем вот это все завозили.
Будет ли C++ тогда функциональным языком?
В C++ есть std::ranges::views, которые позволяют лениво обрабатывать это всё. В C++ в компиляторах есть оптимизации хвостовой рекурсии, пусть и не гарантированные. В стандартной библиотеке есть std::expected и std::optional.
Есть и вот такое:
template <template <typename...> typename>
struct a {};
template <typename...>
struct b {};
using c = b<a>;Типы все неизменяемы, а метапрограммирование с типами фактически обязано быть в ФП.
Есть нормальный const.
Хотя исключения из плюсов особо не вырезать (хотя некоторые проекты и работают без исключений, это не полный язык). Но такая ли проблемы исключения? Они позволяют нормально композировать функции, тогда как с ошибками через монады бы так не вышло. Взять те же std::ranges как пример библиотеки. Если использовать std::expected, то эта библиотека фактически обязана была бы везде брать std::expected, а после автоматически их соединять. Таким образом, на границе интерфейса библиотек всегда нужно было бы прописывать std::expected апи, везде прописывать не самый удобный апи для работы с ним.
К тому же, исключения можно сделать и через алгебраические эффекты, но не в C++.
Про синтаксическую поддержку монад. Из коробки есть разве что корутины, которые позволяют выразить некоторые монады вроде Either или Maybe, но возможности всё равно достаточно ограничены. Но есть и сторонние библиотеки для создания синтаксической поддержки. У меня про неё написаны 2 статьи.
Паттерн матчинга и правда нет. Но есть std::variant с паттерном overloaded. Что позволяет использовать перегрузки (родное средство языка, между прочем) для "матчинга" по std::variant.
не вдаваясь в то, как именно ООП реализовано в JavaScript.
Если честно для ООП он тоже мало подходит. Тот же ts по сути обёртка, которая не спасает от проблем в рантайме, ведь код компилируется в обычный js. Поэтому хоть сколько-то адекватный код приходится писать с оглядкой на это.
критика — конкретная и повторяющаяся: «иммутабельность дорогая по памяти», «рекурсия небезопасна из-за стека».
Вроде же автор, сам в статье расскрыл эти проблемы, не совсем понимаю в чём претензия к кандидатам на собесах?) То что они не прочитали лекцию, чем именно js плох для чистого фп?)
Но функциональным языком он не является. Не потому что в нём нельзя писать функционально, а потому что он не был спроектирован для этого.
Он не был скорректирован и для ООП, но очень многие разработчики пишут на нем код именно в ООП стиле. Тоже самое и с ФП. Зачасую для js разработчиков на практике даже рекурсия не нужна. Всё фп заканчивается написанием функций под event loop и промисы. Большинство js разработчиков не приведет пример монады, функтора или апликатива. Даже не так, большинство разработчиков на популярных яп, это вряд-ли сделают, чтобы js разработчики, не думали, что я их как-то принижаю. И это нормально, в js часто используют фп и ооп в связке, при этом ни фп, ни ооп не используют "на полную".

Почему JS/TS — не функциональный язык (и почему это важно понимать)