Комментарии 113
Есть ещё https://www.ohaskell.guide/
Элементы функциональных языков. Здесь есть примеры и на Haskell, и на Scala, и на Erlang
Дополню, что у Bartosz Milewski помимо ТК, так же можно и по Haskell лекции посмотреть.
По-моему приверженцам "функционального" программирования чего-то в жизни не хватает.
Вот объясните мне (простому приверженцу принципа "работа должна быть сделана качественно и в сроки") — какую ценность несёт, хотя бы и подразделение функций на унарные, бинарные, тернарные и т. д.?
Сколько не читаю про так называемое ФП (функциональное программирование), так и не могу понять, что же в нём особенного? Функции используются практически в любом языке, практически в любой архитектуре. Нафига вводить еще один жаргон? Время девать некуда?
ФП помогает расширить интеллектуальный горизонт, например :) Как по мне, так хотя бы для этого оно нужно. Наверное.
Поэтому повторю свой основной вопрос: нафига? Время девать некуда?
И перенесу вопрос в другую плоскость: вот переписываемся мы с вами на великом и могучем. Я, честно признаюсь, давно уже забыл чем причастие отличается от деепричастия, что такое склонение, и многое другое, относящееся к терминам правил русского языка. Но это не мешает мне формулировать мысли, понимать вас, и вести беседу. Вопрос: как изменится моя речь, если я начну думать не о смысле сказанного, а о терминах?
"работа должна быть сделана качественно и в сроки"
Идеи функционального программирования как раз таки и способствуют достижения и этих целей тоже, иначе бы они не появлялись в мейнстриме.
И для получения от ФП профита уже сегодня, совершенно не нужно знать что такое функторы, монады, моноиды и другие термины из Теории Категорий. Не ужели вы не используете лямбда функции и хотя бы такие паттерны как map, reduce, filter ?
Большая часть терминов из этой статьи придумана математики в рамках разработки теории категорий, можете посоветовать им что именно они могли бы сделать полезного, заместо того что бы заниматься всякой ерундой на вроде:
разложение на категории ради разложения и присвоение терминов ради присвоения
Есть такое слово: парадигма. Судя по вики это: "совокупность фундаментальных научных установок, представлений и терминов". Т.е. нечто большое, что может повлечь за собой смену образа мышления. Скажем есть декларативность и императивность. Согласитесь, это далеко не одно и тоже. И там и там большая мат. база. И чтобы этим жить "в полный рост" нужно эту базу понимать и уметь применять.
Вот и с ФП такая же картинка. Если рассматривать отдельные кирпичики из него, возникнет недопонимание: зачем это может быть нужно? Всё так сложно и без явных преимуществ. Но кирпичики комбинируются, соединяются в причудливые схемы. И вот тогда программист пожинает profit. А так как область эта очень большая и развивается десятилетиями, да ещё и сугубо математична (а математика никогда не была особенно простой), то и нечему удивляться, что всё как-то абстрактно и сложно.
Всё же останусь при своём. А именно: считаю чрезмерное использование терминов излишней тратой времени и ресурсов.
Безусловно, всем, представленным в статье, парадигмам должны быть обучены все, без исключения, программисты. Но не на уровне терминов, а на подсознательном уровне. Ровно так же, как и с языками, на которых мы общаемся.
Но не на уровне терминов, а на подсознательном уровне
Можно ли понять диффуры без подготовки в виде многих лет изучения алгебры? Думаю да. Но какой ценой.
То есть разложение на категории ради разложения и присвоение терминов ради присвоения
Это зависит от точки зрения.
Можно воспринимать ФП (в контексте парадигмы) как бессмысленный набор стрелочек и символов и относиться к этому как к интеллектуальной мастурбации. Можно воспринимать его как онтологию и, соответственно, как радикальный и целостный подход к решению прикладных статических задач.
Первая точка зрения, в таком случае, автоматически (согласно поведенческой установке, лежащей в её базе) отсекает для носителя целый пласт интеллектуального творчества неглупых людей. Вторая же точка зрения позволяет взглянуть на достижение цели (т.е., сложную динамическую задачу) как на, прошу прощения за аналогию, «интеграл» статических задач по состоянию (стейту) и, соответственно, даёт возможность элегантно отделять декларативные (описательные) элементы достижения поставленной цели от императивных (последовательных и зависимых) элементов. Не думаю, что это плохая возможность.
Не секрет же, что метапрограммирование, проникшее во многие императивные языки, позволяет более эффективно решать задачи с точки зрения производительности разработчика. Это что, тоже плохо? А полностью овладеть метапрограммированием, без понимания функционального программирования, просто невозможно.
Вопрос: как изменится моя речь, если я начну думать не о смысле сказанного, а о терминах?
Никак. Ваша интеллектуальная деятельность уже впитала в себя парадигму русского языка как комплекса синтаксиса, морфологии, семантики и, соответственно, ментальности как онтологии русского языка. Но, без изучения синтаксиса, морфологии, семантики и, как следствие, восприятия ментальности, вы бы не смогли вести беседу на русском языке как русский. При этом, как вы правильно заметили, онтология, как абстракция, может существовать и без её структурного описания. Но, для её рождения это структурное описание нужно.
Аналогия с функциональным программированием как парадигмой ясна, я думаю?
P.S. Исправил разметку, но суть текста не менял.
P.S. жаль, что докапываться до истины всегда приходится сквозь шквал минусов и грубость
Человек, который разобрался, даст условия и покажет: как и когда этим пользоваться.
Посмотрите Мартина Фаулера Рефакторинг кода: все приемы он сопровождает примерами кода в которых это будет удобно, причем к каждому примеру делает контрпример.
Термины программировать не помогают, а вот концепции, которые называются этими терминами — очень даже.
Возьмём функтор. Например, есть значение какого-то типа, обёрнутое в какой-то другой тип (Optional
, List
, Array
… — во что угодно). И у вас уже есть функция fun1
, которую можно применить к тому типу данных, который находится внутри обёртки (назовём этот тип Int
). Но эту функцию нельзя применить к типу Int
, который находится в обёртке (скажем, эта обёртка Optional
). А вам это нужно. И вот вы пишете функцию fun1Optional
. Можете вы её написать, не зная термина "функтор"? Какие проблемы — конечно можете!
Но тут у вас возникает задача сделать так, чтобы функция fun2
, работающая со значениями типа Int
, тоже могла работать со значениями Optional Int
. И чтобы функция fun3
тоже так могла работать. И fun15
. И вот вы пишете функции fun2Optional
, fun3Optional
… fun15Optional
… Можете? Конечно, почему нет? Не зная ни о какой концепции, называемой термином "функтор".
Но если вы знаете о такой концепции, то вместо того, чтобы переписывать вручную кучу функций, работающих с типом Int
, чтобы они работали с типом Optional Int
, вы просто делаете обёрточный тип Optional
функтором, определяя для него всего одну функцию map
(или fmap
— это одно и то же), которая принимает в качестве первого аргумента функцию, работающую с типом Int
, в качестве второго аргумента — значение Optional Int
, и применяет эту функцию Int -> ВоЧтоТоЕщё
(которая уже имеется у вас!) к значению типа Int
внутри обёрточного типа Optional
. В результате вам не надо писать кучу функций fun1Optional
… fun15Optional
, а достаточно написать одну единственную функцию fmap
для обёрточного типа Optional
. И наличие у обёрточного типа Optional
функции fmap
делает его функтором.
Более того: наличие функции fmap
позволяет вам использовать уже имеющиеся у вас функции не только со значениями типа Optional Int
, но и с Optional Float
, и с Optional String
, и с Optional Bool
— и т.д. Просто потому, что вы сделали обёрточный тип Optional
функтором — путём определения для него всего одной функции.
Можно ли программировать без знания концепции функтора? Конечно можно! Просто у вас будет меньше code reuse, больше определений новых функций, а ваш код будет сложнее читаться, рефакториться и дебажиться. Помогает ли знание концепции "функтор" программировать? По-моему, да.
Концепции и терминологии упрощают, систематизируют и ускоряют процессы и структуры.
Конечно можно придумать обобщение, не зная концепции. Можно и все алгоритмы самому придумать. Но обычно умные люди изучают то, что уже придумали другие люди, чтобы не изобретать велосипед. Это просто эффективнее. А придуманным алгоритмам и концепциям обычно дают имя, чтобы можно было быстро сказать собеседнику, о чём вы говорите, так, чтобы он понял, о чём идёт речь.
Но тут у вас возникает задача сделать так, чтобы функция fun2, работающая со значениями типа Int, тоже могла работать со значениями Optional Int. И чтобы функция fun3 тоже так могла работать. И fun15. И вот вы пишете функции fun2Optional, fun3Optional… fun15Optional… Можете? Конечно, почему нет?
Это называется подтасовка, нет, я буду интуитивно использовать функтор, но не знать его определения. Мало того, использовать его или нет зависит от моих умственных способностей, а не от прочитанной книжки. Так что, в одиночку, формулировки не нужны. Вот для совместного программирования (> 1) помогают да, как и любые другие слова.
Знаете, я вот не понял, за что вас заминусовали. На самом деле, очень многие статьи по ФП страдают именно этим — из них нифига не ясно, в чем состоит полезность той или иной парадигмы. Причем ее в общем-то можно сформулировать в нескольких предложениях или максимум абзацах — но почему-то довольно редко получается это сделать. И вопрос ваш вполне имеет право на жизнь, более того — просто обязан быть задан.
Но в данном случае вы выбрали неудачный объект для претензий. Разделение функций конечно существует, но большого значения ему не придается, по одной простой причине — в большинстве полноценных ФП языков достаточно иметь унарные функции, которые являются сущностью первого порядка, чтобы свести все остальные к ним. И это, кстати, является одним из достоинств — в ФП есть совсем немного фундаментальных сущностей, которые пришли как правило из математики, при этом все остальные сущности можно из них эффективно выразить.
Вопрос только в том, что программы на 1 млн строк кода существуют на С++, Java, C#, PHP, скорее всего Python. Остальные языки, при всей нашей радости от того же эрланга, к сожалению вызывают сомнение в этом (в существовании таких больших программ).
Правда, по причинам своей эзотеричности и замкнутости автора, оно вроде не пошло.
Но у окамля есть ряд очень важных практических свойств: компилируемость и мутабельный доступ к структурам.
Позвольте, я попробую без спецтерминов.
Представьте себе функцию, выполняющую некоторые эффективные мутабельные вычисления. В общем случае, такая функция не будет чистой, что нехорошо. Однако мы можем ограничить всю мутабельность в этой функции некоторым локальным окружением, которое будет вынесено в её тип (примерно как в Rust в типы значений вносится область их жизни). А полиморфизм второго ранга позволит завернуть её в обёртку, которая, грубо говоря, создаёт необходимое окружение, прогоняет на нём вычисление и получает результат. И поскольку вне окружения гарантированно ничего не мутирует, итоговая обёртка будет чистой. В итоге получаем лучшее из двух миров – надёжная чисто-функциональная обёртка с эффективными императивными вычислениями внутри, которые при этом доказанно ничего не сломают.
ST (см. сообщение от 0xd34df00d выше). В State на самом деле ничего не мутирует, а только делает вид.
Несмотря на это, перевод стоит добавить в избранное как intelligence mock.
PureScript. Компилятор генерирует хороший человекочитаемый код.
Очень похож (практически клон) на Haskell, однако есть некоторые ключевые отличия.
Более глубоко можно ознакомиться в документе PureScript by Example.
Тогда можно еще и на Elm обратить внимание.
Советовать сварщику варить металл кисточкой для рисования, я думаю, глупо. Глупо и сварщику о таком спрашивать.
Я не квалифицирован достаточно, чтобы отвечать на ваш вопрос, но попробую просто в виде примера показать, как ФП может помочь решать задачи из реального мира.
Есть такая штука — SSIS. По жизни штуковина довольно сложная, и вызывающая множество проблем и боли у разработчиков, которые её пытаются использовать для каких-то прикладных задач (а она действительно бывает нужна, просто поверьте пока что на слово). Итак, разработчики плачут, колются, но продолжают пожирать кактус с полуимперативными действиями по деплойменту и настройке SSIS-пакетов, воюют с разными версиями IDE, серверов и прочей дребеденью.
И тут появляется Chimayo. С использованием сравнительно небольшого количества паттернов функционального языка она позволяет описывать эти SSIS-задачи, формировать их в функциональном стиле, манипулировать как значениями первого класса, ну и всё такое. В общем, позволяет избавиться от боли и начать программистам, наконец, программировать.
К чему я это сказал? Я думаю, что для вашей инициализации UEFI просто не написан (или пока что вам не встретился) подходящий декларативный инструмент, использующий современные достижения функционального программирования. Если б он был — поверьте, вам бы понравилось, как и нам понравилось работать с Chimayo после ада ручной настройки SSIS.
Ну вот опять рабочий класс послали, а так хотелось приобщиться к прекрасному! Не “стрелять себе а ногу», манипулировать обьектами и функциональностью, наслаждаться шаблонами и паттернами.
Увы и ах, ухожу плакать.
Радуйтесь тому, что у вас есть возможность невозбранно использовать архитектурно-специфичные конструкции, красивую арифметику указателей, радоваться плотной бинарной упаковке и наслаждаться битовыми масками. Хотя, вполне возможно, в современном мире UEFI и этой низкоуровневщины уже нет.
Напротив, вся работа железа на низком уровне построена именно на сайд-эффектах. Настолько, что это даже сносит крышу даже обычным С-компиляторам. Например как вам такое — вы пишете в область памяти одно число, а потом читаете оттуда другое? Или читаете два раза подряд из одной и тоже ячейки памяти и при этом получаете совершенно разные значения?
Это не глюк, так работает вся memory-mapped периферия. Приходится использовать хитрые конструкции чтобы сишные компиляторы не ломали работу с периферией своими оптимизациями. Более того, нынешние процессоры тоже шибко умные и выбрасывают лишние (по их мнениею) обращения к памяти. А что тогда говорить про языки более высокого уровня? Поэтому и нет применеия ФП в UEFI.
Зато, если мне надо будет превращать тысячи HTTP запросов в тысячи HTTP ответов — я вользу Эрланг. Потому что ключевое слово — «превращать». Отображать множество HTTP запросов на множество HTTP ответов.
С другой стороны есть куча задач, где вычисления как таковые не нужны. А нужно просто в нужное время выставлять нужные биты в нужных местах. И ещё — быстро реагировать на изменения битов в других местах. Это приблизительно то, чем занимается приснопамятный UEFI.
Хотя, если подняться чуточку выше — появляются и задачи для ФП. Мне тут подумалось, что можно например реализовать USB стек на ФП языках. Да и вообще задачи современного прошивкописания всё больше тяготеют к массовой параллельной обработке данных.
Пример Clojure показывает что наличием должной инфраструктуры, популяризации должной не добиться. Там через interop в java, доступны все богатства и мощь JVM мира. Но как видим, только небольшая прослойка ценителей ФП осторожно работают с ним или со Scala. Так что инерция мышления, самый главный враг ФП.
Но как видим, только небольшая прослойка ценителей ФП осторожно работают с ним или со Scala.Мне кажется, что тут дело в том, что большинство ценителей ФП на дух не переносят Java, включая JVM.
Это на самом деле странная была затея создать функциональный язык поверх императивной виртуальной машины, которая даже оптимизацию хвостовой рекурсии не поддерживает. Мало нам что-ли императивной процессорной архитектуры?
Просто нужно понимать, что без популярной платформы (JVM, JS) популярность clojure была бы в разы меньше. Как и затраты на реализацию языка. Авторам clojure "всего-лишь" пришлось имплементировать компилятор и стандартную библиотеку, все самое сложное взяла на себя JVM.
В итоге самый быстрый как вчера, так и сегодня это интел, а всякие VLIW-ы остались на откуп спец-задачам и академикам.
const add = (a) => (b) => a + b
Совершенно непонятная запись. Что здесь вообще написано?
Ну и вообще не люблю трехэтажные абстракции.
Сам я не фанат ни одной из парадигм.
Абстракции повышаю по мере надобности.
Мы зарабатываем деньги на разработке и продаже софта на эрланге, но мне ничего непонятно из этой статьи, я ощутил себя так же беспомощно, как при чтении книжки Душина и так же без понимания того: зачем вся эта терминология, какие проблемы решает и как.
Плюс это всё таки статья для тех, кто уже знает хаскель, а для них она мягко говоря бессмысленна, они уже итак всю терминологию знают.
Хотите сделать статью не для любителей хаскеля — используйте ну хотя бы яваскрипт что ли, но ни в коем случае не хаскелевский синтаксис.
В статье все примеры на javascript.
const liftA2 = (f) => (a, b) => a.map(f).ap(b)
Да, конечно
https://habrahabr.ru/company/plarium/blog/270353/
var liftA2 = function(f){
return function(a, b){
return a.map(f).map(function(a){
return a(b);
})
}
}
liftA2 = "haha";
var result = typeof(liftA2) === "string" ? liftA2 : liftA2([1,2],[3,4]);
Только у вас внутри a перекрывается, что делает код не очень наглядным.
А зачем вторая часть текста с заменой функции на строку я не понял.
И плюс опять возвращаемся назад: зачем этот вычурный кусок кода? что он делает и сколько сотен строк более простого кода он экономит?
1. Оптимизация компилятора. Вместо этой переменной всегда можно подставить ее значение, что на больших объемах дает существенный выигрыш.
2. Утиная типизация иногда удобна, иногда крайне опасна, к тому же требует вот таких проверок. const решает вопрос указанием типа при первоначальном и единственном присвоении значения.
Это экстраваганщина, потому что вы первый раз увидели стрелочку или обожаете return? Завтра привыкнете, не переживайте. Ведь к `map`, который появился совсем недавно, вы претензий не предъявляете и не пробегаете массив циклом.
Конкретно этот кусок делает функцию liftA2 независимой от функции f, которую мы передаем. Это дает возможность использовать ее многократно с разными функциями, облегчает тестирование в разы, а значит и ускоряет написание кода. Хотя, подозреваю, до TDD вы еще тоже не добрались. Тогда, предупреждая вопрос, скажу. TDD нужно не для тестирования и даже не для описания требований, это приятный побочный эффект, но достигается и другими вещами. TDD предполагает разбиение кода на мельчайшие детали, проработку каждой из них в отдельности и доказательство поставленных функций. Чаще всего, программа не запускается целиком вообще до описания всех unit'ов. Зато внесение изменений всегда предсказуемо и однозначно. Если код поменяли в этом unit'е, то раз его тест не проходит, его и чинить (все зависимости его замоканы).
Так вот, функциональное программирование в разы облегчает создание таких кусочков, чистых функций. Нужные зависимости передаются, как параметры или каррируются и т.д.
В статье продемонстрированы инструменты такого подхода, на примере JavaScript.
Например, запуская программу целиком в командной строке или в браузере после каждого изменения и любуясь на ее работу.
Или доказывать то, что сам JS намного более функциональный язык, чем императивный и не надо, с этим никто не спорит.
А может быть вы использовали Angular с его декларативными шаблонами и DI? Или React с pure-компонентами? Нет? Ну lambda-функции и функции первого порядка в чистом виде ничем не хуже.
Вот в QBasic с этим были сложности, конечно.
Размер же проекта и необходимость его поддержки годами тоже определяется качественным кодом, иногда достаточно зайти на github раз в год и обновить dependencies у библиотек. А полезного, бизнес-кода настолько мало, насколько это возможно и его поддержка легка.
А иногда целыми днями сидишь и пилишь монолит на Swing, Laravel, Rails, когда регрессионные тесты вручную — основный вид деятельности.
Частичное применение производится на каррированной функции. Если передать передать лишь первую группу аргументов, то на выходе будет новая функция (которая благодаря замыканию, будет использовать переданные ранее значения). Это не так часто применимо как об этом пишут, но когда потребуется, позволяет сильно сократить код, повысить читаемость (наверно потому так часто и упоминают, что польза большая, а минусов нет). Если применять везде, то читаемость только ухудшится, потому что сигнатуры не будут нести логического обоснования и в большом нагромождении принесут путаницу.
Каррирование и частичное применение связаны непосредственно друг с другом, поэтому это нормально, что не видно сначала разницы.
Каррирование – способ представить функцию многих переменных в виде набора унарных. Частичное применение – способ получить функцию N переменных из функции N+K переменных, «зафиксировав» значения этих K. Они связаны только в том плане, что каррированные функции очень легко частично применять (передали только один аргумент – вуаля, у нас новая функция); но это вовсе не обязательное требование – см. C++-ный std::bind, например.
Я ещё никогда не чувствовал себя настолько дезориентированным.
Как я пользовался JS все эти годы без этих определений?
Или вопрос по-другому: зачем вы напомнили мне опять про ВУЗ и академические знания? :)
Option (опцион)
Опцион, вы серьезно?
Жаргон функционального программирования