Comments 27
return square(x) * x
}
Иван, есть неточности в определениях:
1) Функция первого класса — из вашего определения следует, что это какие-то особые функции, обладающие особыми свойствами, что неверно:
во-первых, нет такого самостоятельного понятия — "функция первого класса" — это всегда свойство языка программирования, которым некоторые из них обладают: "функции являются объектами первого класса".
во-вторых, свойство "объект первого класса" в рамках языка может быть присуще или не присуще любому из типов сущностей языка целиком — переменным, константам, функциям, классам, интерфейсам, пространствам имен и тп; то есть, если в языке функции являются объектами первого класса, то сразу все, а не какие-то конкретно.
в-третьих, свойства объектов первого класса могут различаться от языка к языку; в частности, в чисто функциональных языках переменных нет, поэтому никакая сущность не может быть присвоена переменной — из вашего определения следует, что там функции не являются объектами первого класса; здесь стоит хотя бы сделать оговорку;
в-четвертых, пропущено одно из ключевых универсальных свойств объектов первого класса — такие объекты могут быть созданы во время выполнения программы
в-пятых, приведен некорректный пример, но это сложно понять из-за предыдущего пункта — в языке C функции не могут быть созданы во время выполнения программы — только во время компиляции, поэтому в C они не являются объектами первого класса
let sum = numbers.reduce(0, combine: { $0 + $1 }) // 55
Я бы на вашем месте лямбду в классическом виде написал. Не всем очевидна эта фича свифта что
{ $0 + $1 } равносильно записи (a, b) -> a + b
А то так можно вспомнить что в свифте вообще вот так можно написать:
let sum = numbers.reduce(0, combine: +)
1. Чистые функции называются «чистыми», потому что являются функциями в математическом смысле этого термина. А именно: функция — это отображение одного множества (декартова произведения множеств допустимых значений параметров) на другое (множество возможных значений функции). От «программистских» функций они отличаются тем, что а) детерминированы и б) не имеют побочных эффектов.
2. Детерминированность означает, что функция всегда возвращает одно и то же значение для заданного набора параметров. Примеры недетерминированных функций: random(), date(), read(). Если вы хотите сделать функцию с рандомом детерминированной, вам нужно передавать значение рандома как дополнительный параметр этой функции, а не вызывать random() внутри её тела.
3. Отсутствие побочных эффектов означает, что функция не изменяет состояние внешних объектов (своих параметров, глобальных переменных, контекста, состояния системы и т.д.). Т.е. ничего никуда не выводит, не пишет логи, не выделяет динамическую память, и не использует оператор присваивания к внешним объектам. Более того, при помощи макроопределений, рекурсии и какой-то матери можно вообще отменить оператор присваивания как таковой, который, собственно, и отличает математику от информатики. В каком-нибудь радикально функциональном языке выражение x = y + z будет означать не «записать в ячейку x значение суммы», а то, что x это сокращение для выражения y + z. Отсюда же, кстати, растут ноги про «неизменяемое состояние».
4. Чистые функции оторваны от физического времени. Это значит, что их можно вычислять в любой удобный момент времени между теми моментами, когда а) становятся известны значения всех параметров вызова; и б) реально потребуется значение результата. В частности:
— если значения всех параметров вызова заданы константами в коде программы, функцию можно вычислить на этапе компиляции, а в скомпилированном коде она соптимизируется до вида {return 42;}
— если значения параметров становятся известны заметно раньше, чем требуется результат, функцию можно запустить «сверхэнергично», т.е. заранее, например, на другом ядре.
— если функция вызывается с одними и теми же значениями параметров, её можно вычислить один раз и записать вычисленное значение в кэш. Затем возвращать значение из кэша.
— если неизвестно, когда точно потребуется результат функции, её можно вообще не вычислять, пока не припрёт (ленивые вычисления).
В сумме эти 4 метода имеют огромный потенциал для ускорения вычислений.
5. Именно широкие возможности оптимизации кода, а не то, что чистая функция «обычно, является показателем хорошо написанного кода», является их киллер-фичей.
Например, те, кто писал на Си, знает, насколько неприятно в этом языке работать со строками. Необходимо не просто разбивать все операции на элементарные (три строки вы одной операцией не сконкатенируете), а ещё постоянно следить за памятью. Ошибки переполнения буфера при строковых операциях в Си — безусловно самая дорогая в смысле ущерба от последствий. Тут и взломы всего интернета, и падения ракет, и вообще что угодно.
Так вот, во многих более новых языках строки объявлены неизменяемыми, либо, как минимум, библиотека спроектирована так, чтобы её можно было реализовать с неявно неизменяемыми строками (copy on write).
Что это даёт? Компилятор собирает все строковые константы в исходном тексте и ещё на этапе компиляции выполняет все операции, которые над ними потребуются (strlen, strconcat, strpos, substr и т.д. — они все как раз чистые). Но главное, он выделяет память под все строки ещё на этапе компиляции.
Вторая (концепция опциональности) — заставляет нас думать о возможных крайних случаях и ошибках, которые могут прийти извне, и обрабатывать их корректно.В Haskell Вы наоборот не думаете о крайних случаях и не определяете функций, которые принимают Maybe (если следуете концепциям языка). Это особенность как концепции опциональности, так и монад в целом… Вы пишете функции, которые принимают чистые значения и возвращают монадические, обо всём остальном заботится
>>=
, сам по себе или внутри do-блока.Ещё в определении sum есть лишняя строка:
sum (x:[]) = x -- one element
Каррирование — это когда функция принимает N аргументов, мы ей передаём M параметров и она превращается в функцию, принимающую N-M аргументов, где N >= M && M > 0. Swift скорее всего не поддерживает такое из коробки.
Возможно, Вы сможете определить свою функцию
carry(fn, arg)
, которая будет принимать произвольную функцию и первый параметр для неё, и возвращать её в каррированном виде.Каррирование – это когда функция принимает N аргументов, а мы из неё делаем функцию, которая принимает первый аргумент и возвращает функцию, которая принимает второй аргумент и возвращает функцию, которая … и возвращает функцию, которая принимает N-й аргумент и возвращает значение исходной функции.
> let bigRange = 1...1_000_000_000_000 // from one to one billion
Биллион по-русски называется миллиард, а объявлен у вас вообще триллион.
Но дело даже не в этом, а в том — зачем оно? Какая от него польза?
Этого я из статьи не понял.
Да, местами ясно, что где--то может помочь — но насколько это будет существенная помощь, чтобы «плавить мозги»?
Трудно понять, чем код вида:
1) Получим массив элементов.
2) Определим, есть ли «наш элемент» в данном массиве?
Лучше функционального подхода:
1) Получим функцию.
2) Определим, вызвав полученную функцию (с параметром — «нашим элементом»), есть ли «наш элемент» в каком-то множестве?
Ну, есть профит функциональщины:
— нас не интересуют внутренняя реализация функции (массив там будет или список или map).
— и мы всегда можем заменить функцию (взяв её из другой библиотеки или откуда угодно, хоть извне) не трогая код определения наличия «нашего элемента» в каком-то множестве.
Но насколько это так уж важно?
Если посмотреть на то, что человек, который явно понимает ФП, написал статью, но судя по коментам — не совсем правильно понимает базовые понятия ФП, то становится понятно что функциональное программирование — довольно «путанная штука».Swift и уж тем более C — не являются функциональным ЯП. Haskell автор не знает на достаточном уровне. Я его и сам не знаю толком, но в статье совсем уж детские грабли в Haskell-коде.
Что до путанности, посмотрите статьи про ООП и вообще офигеете. Абсолютно ни у кого нет чёткого понимания, что входит в это понятие, а что нет. И по каждому ассоциированному термину влёгкую можно начать холивар. ФП в этом плане отлично формализовано, что конечно не страхует от того, что кто-то может что-то напутать. Человеческий фактор не отменить, зато всегда можно глянуть точное определение.
1) Получим массив элементов.
2) Определим, есть ли «наш элемент» в данном массиве?
Это тоже функциональный подход.
Императивный подход:
1) Получим указатель на массив элементов и целевой элемент
2) Определим длину массива
3) В цикле от 0 до длина-1 будем сравнивать i-ый элемент массива с целевым.
4) Если элемент совпал, то return true
5) return false
Возможно и так, но обычно все «инстинктивно» ООП понимают, что есть объекты, имеющие свойства (видимые или скрытые) которые (объекты) либо обмениваются сообщениями, либо эти объекты можно «дёрнуть» за их (объектов) методы. — То есть — «всё как в жизни». Типа того. А ещё есть иерархия объектов по наследованию — опять таки — как в жизни.
Source > ФП в этом плане отлично формализовано, что конечно не страхует от того, что кто-то может что-то напутать. Человеческий фактор не отменить, зато всегда можно глянуть точное определение.
Я всегда считал (читал) что «функция высшего порядка» — это «функция возвращающая функцию». Но то ли не там читал, то ли плохо читал, но в данной статье написано иное определение «функции высшего порядка». Печально мне.
Source > Императивный подход:…
Это уж слишком «низкий» подход у вас.
1) Получим массив элементов.
2) Определим, есть ли «наш элемент» в данном массиве?
Иначе, немного пояснив с ООП:
1) Получим объект(!) типа Массив элементов.
2) Определим, есть ли «наш элемент» в данном объекте вызвав («дёрнув») метод этого объекта.
Так что это чистейший ООП. Имхо, конечно, имхо.
То есть — «всё как в жизни». Типа того. А ещё есть иерархия объектов по наследованию — опять таки — как в жизни.
Ага, именно "типа того". А по факту ничего общего с тем, как в жизни :-)
Да ещё и у каждого своё инстинктивное понимание. Некоторые вообще взаимоисключающие. Впрочем не будем про ООП, на Хабре уже есть много холиваров на эту тему, из которых понятно, что ничего не понятно и общепринятых определений нет :-)
Я всегда считал (читал) что «функция высшего порядка» — это «функция возвращающая функцию».
Там 2 критерия в определении:
- принимает одну или более функций в качестве аргумента
- возвращает функцию в качестве результата
Любого из этих критериев достаточно, чтобы функцию назвать функцией высшего порядка.
Это уж слишком «низкий» подход у вас.
Да нет, просто идеи ФП заимствуют для императивных мейнстрим-языков и они уже не совсем императивные. Те же ФВП теперь где только не встретишь..
1) Получим объект(!) типа Массив элементов.
2) Определим, есть ли «наш элемент» в данном объекте вызвав («дёрнув») метод этого объекта.
Ну если уж про ООП, то надо ещё про паттерн Iterator добавить. А то Вы подменяете реализацию алгоритма на её использование. А использование вообще не сильно отличается:
Процедурный стиль:
in_array(arr, obj)
ООП:
arr.include?(obj)
ФП:
Enum.member?(arr, obj)
6 концепций функционального программирования. Польза и примеры использования