Функциональное программирование — это парадигма программирования, которая акцентируется на вычислении через функции в математическом стиле, неизменяемость, выразительность и уменьшение использования переменных и состояний (ссылка).
Существует 6 основных концепций:
- концепция первого класса и функций высшего порядка
- концепция чистых функций
- концепция неизменяемого состояния
- концепция опциональности и сопоставления с образом
- концепция ленивости и бесконечных структур данных
- концепция лямбда-исчислений
Функция первого класса
Что это
Это сущность, которая поддерживает операции, обычно доступные для других сущностей. Эти операции, как правило. включают в себя: передачу сущности как аргумента, возвращение сущности из функции и присваивание оной в переменную.
Чем полезна
Упрощает работу с функциями, давая больше возможностей и способов для их использования.
Примеры использования
typedef void (*callback_func_t) (char*);
void list_files(char* path, callback_func_t callback) {
// recursive read directory structure ...
}
void print_file_path(char* file_path) {
printf("%s\n", file_path);
}
callback_func_t get_print_func() {
callback_func_t callback_var = &print_file_path;
return callback_var;
}
list_files("/tmp/", get_print_func());
В приведенном примере функция get_print_func создает переменную, в которой хранит ссылку на функцию, а затем возвращает ее. А ниже по коду мы передаем результат, возвращенный нам функцией get_print_func в другую функцию. Это и есть операции, доступные нам благодаря функциям первого класса.
Функция высшего порядка
Что это
Это функция, которая оперирует другими функциями. Оперирует, получая их в качестве параметра или возвращая их.
Чем полезна
Как и в случае функций первого порядка, эта концепция дает нам больше возможностей для работы с функциями. Также эта концепция открывает нам возможность использования функций как обработчиков событий, о которых система или какая-либо библиотека может нам сообщать посредством вызова переданной ей функции первого класса.
Примеры использования
Cм. предыдущий пример. Здесь есть функция, которая читает директорию. И рекурсивно читает все подпапки. Для каждого найденного файла вызывает функцию, которую мы передали — callback.
Пример на С показывает, что еще в 70-ых годах прошлого века можно было оперировать функциями первого класса и высшего порядка. В Objective-C появились блоки. В отличие от функций они могут захватывать переменные и какое-то состояние. В Swift появились Closure. По сути, это то же самое, что блоки на Objective-C.
Чистая функция
Что это
Это функция, которая выполняет два условия. Функция всегда возвращает один и тот же результат при одних и тех же входных параметрах. И вычисление результата не вызывает видимых семантических побочных эффектов или вывода во вне.
Чем полезна
Чистая функция, обычно, является показателем хорошо написанного кода, так как такие функции легко покрывать тестами, их можно легко переносить и повторно использовать.
Примеры использования
В приведенном ниже фрагменте показаны примеры чистых функций (они помечены комментарием “pure”).
func quad1(x: Int) -> Int { // pure
func square() -> Int {
return x * x
}
return square() * square()
}
func quad2(x: Int) -> Int { // pure
func square(y: Int) -> Int { // pure
return y * y
}
return square(x) * square(x)
}
func square(x: Int) -> Int { // pure
return x * x
}
func cube(x: Int) -> Int {
return square(x) * x
}
func printSquareOf(x: Int) {
print(square(x))
}
let screenScale = 2.0
func pixelsToScreenPixels(pixels: Int) -> Int {
return pixels * Int(screenScale)
}
Используются повсеместно. Например, стандартные математические библиотеки почти всех языков программирования содержат в основном только чистые функции.
Неизменяемое состояние
Что это
Неизменяемое состояние — состояние объекта, которое не может быть изменено после того, как объект был создан. Под состоянием объекта здесь, подразумевается набор значений его свойств.
Чем полезено
Так как неизменяемые объекты гарантируют нам, что на протяжении своего жизненного цикла они не могут менять свое состояние, то мы можем быть уверены, что использование или передача таких объектов в другие места программы не приведет к каким либо непредвиденным последствиям. Это особенно важно при работе в многопоточном окружении.
В языке C “из коробки” нет возможности создавать неизменяемые объекты. Ключевое слово const запрещает изменять значение только в текущем контексте, однако, если мы передаем ссылку на это значение в функцию, то эта функция сможет изменить данные, находящиеся по этой ссылке. Можно решить эту проблему через инкапсуляцию (через публичные и приватные заголовочные файлы). Однако, в этом случае мы должны самостоятельно реализовать механизм “защиты” данных от изменений.
В Objective-C тоже ничего нового не пришло. Добавились только базовые классы, которые не дают изменять свое внутреннее состояние и их изменяемые (мутабельные) аналоги.
В Swift у нас появилось ключевое слово let, которое гарантирует нам, что переменная или структура не может быть изменена после создания.
Примеры использования
Пример использования неизменяемых значений в Swift:
let one = 1
one = 2 // compile error
let hello = "hello"
hello = "bye" // compile error
let argv = ["uptime", "--help"]
argv = ["man", "reboot"] // compile error
argv[0] = "man" // compile error
Опциональный тип
Что это
Опциональный тип — обобщенный (generic) тип, который представляет инкапсуляцию опционального значения. Такой тип содержит в себе либо определенное значение, либо пустое (null) значение.
Чем полезен
Выносит понятие о нулевом (null) значении на более высокий уровень. Позволяет работать с опциональными значениями при помощи синтаксических конструкций языка.
Примеры использования
Практически во всех современных, особенно молодых, языках присутствует понятие опционального типа и синтаксические конструкции для работы с ним. В Swift это конструкция if let, либо switch case:
let some: String? = nil
switch (some) {
case .None:
print("no string")
case .Some(let str):
print("string is: \(str)")
}
Pattern Matching
Pattern Matching — акт проверки последовательности токенов на совпадение с определенным шаблоном.
Чем полезен
Позволяет нам писать более краткий, сосредоточенный на решении проблемы код.
Примеры использования
Вот пример на Haskell. На мой взгляд, самый лучший пример Pattern Matching.
sum :: (Num a) => [a] -> a
sum [] = 0 -- no elements
sum (x:[]) = x -- one element
sum (x:xs) = x + sum xs -- many elements
Функция sum принимает на вход массив объектов. Если функция sum получает пустой массив, сумма элементов будет 0. Если массив содержит один объект, просто получаем этот объект Если больше объектов, то складываем первый объект и хвост массива, затем операцию повторяем рекурсивно пока у нас есть элементы в хвосте массива. Эту функцию мы описали как паттерн. Это значит, что мы описываем все возможные (либо необходимые нам в данный момент) варианты работы этой функции в зависимости от входных значений. Без if и прочих условных операторов.
addOne :: Maybe Int -> Maybe Int
addOne (Just a) = Just (a + 1) -- not empty value
addOne Nothing = Nothing -- empty value
Функция addOne добавляет к числу единицу. На вход она принимает аргумент типа Maybe Int и на выходе возвращает значение аналогичного типа. Maybe — это монада, которая содержит в себе либо значение (Just a), либо ничего (Nothing). Функция addOne работает следующим образом: если в аргументе функции есть значение, (Just a) то добавляем единицу и возвращаем аргумент, если ничего нет (Nothing), то возвращаем ничего (Nothing).
В Swift pattern-matching выглядит так:
let somePoint = (1, 1)
switch somePoint {
case (0, 0):
print("point at the origin")
case (_, 0):
print("(point on the x-axis")
case (0, _):
print("point on the y-axis")
case (-2...2, -2...2):
print("point inside the box")
default:
print("point outside of the box")
}
Pattern Matching, на мой взгляд, довольно ограничен в Swift, можно только проверить кейсы в операторе switch, однако, делать это можно довольно гибко.
Ленивость или ленивые вычисления
Что это
Ленивое вычисление — стратегия вычисления, которая откладывает вычисления выражения до момента, когда значение этого выражения будет необходимо.
Чем полезно
Позволяет отложить вычисление некоторого кода до определенного или заранее неопределенного момента времени.
Пример использования
let dateFormatter = NSDateFormatter()
struct Post {
let id: Int
let title: String
let creationDate: String
lazy var createdAt: NSDate? = {
return dateFormatter.dateFromString(self.creationDate)
}()
}
Может применяться для инициализации полей у класса, после его инициализации. Этот прием позволяет избежать дублирования кода инициализации поля в нескольких конструкторах класса и отложить инициализацию этого поля до момента, когда в нем возникнет необходимость. В приведенном выше примере значение поля createdAt вычисляется в момент первого обращения к нему.
Бесконечная структура данных
Бесконечная структура данных — структура, чье определение дано в терминах бесконечных диапазонов или непрекращающейся рекурсии, но реальные значения вычисляются только в момент, когда они необходимы.
Чем полезна
Позволяет нам определять структуры данных бесконечной или огромной величины, не затрачивая ресурсы на вычисление значений этой структуры.
Примеры использования
Вот пример на Swift. Берем Range от одного до триллиона. Делаем map на этот Range — превращаем биллион значений в строки. Такое количество строк едва ли уместится в оперативной памяти персонального компьютера. Но, тем не менее мы можем спокойно это сделать и взять нужные значения. В примере лямбда, переданная в функцию map, вызывается только два раза. Все выполняется очень лениво.
let bigRange = 1...1_000_000_000_000 // from one to one trillion
let lazyResult = bigRange.lazy.map { "Number \($0)" } // called 3 times
let fourHundredItem = lazyResult[400] // "Number 400"
let lazySlice = lazyResult[401...450] // Slice<LazyMapCollection<Range<Int>, String>>
let fiveHundredItem = lazyResult[500] // "Number 500"
В Swift мы всегда ограничены Range’ем. Мы не может создать бесконечный ряд значений. Можно исхитриться и сделать это иначе, но “из коробки” этого нет. Зато в Haskell есть.
Можно сделать список от одного до бесконечности. Делаем map ко всем элементам (номер и число, которые превратятся в строку). Затем берем любые элементы либо срез или список. Срез тоже будет возвращен ленивым списком.
infiniteList = [1..]
mappedList = map (\x -> "Number " ++ show x) infiniteList -- called 2 times
fourHundredItem = mappedList !! 400 -- “Number 400”
lazySlice = take (450 - 400) (drop 400 mappedList) -- [401..450]
fiveHundredItem = mappedList !! 500 -- “Number 500”
lazyArray = [2+1, 3*2, 1/0, 5-4] -- item values not evaluated
lengthOfArray = length lazyArray -- still not evaluated
Haskell — самый ленивый язык из всех, которые я видел. В нем массивы могут содержать boxed (упакованные) и unboxed (распакованные) элементы. В случае, когда элементы массива упакованы (еще не вычислены) при операциях с массивом, которые не требуют получения значения элементов, эти значения вычислены не будут. Примером такой операции может служить метод length.
Лямбда-исчисления
Что это
Лямбда исчисление — формальная система в математической логике для выражения вычисления на основе операций аппликации и абстракции функций при помощи связывания и замены переменных.
Чем полезны
Концепция лямбда-исчисления приносит в языки программирования понятие анонимных функций, которые могут захватывать внешние (по отношению к функции) переменные.
Пример использования
Ниже — пример на Swift, где мы используем лямбду вместо именованных функций.
let numbers = [0,1,2,3,4,5,6,7,8,9,10]
let stringNumbers = numbers.map { String($0) } // ["0","1","2","3","4","5","6","7","8","9","10"]
let sum = numbers.reduce(0, combine: { $0 + $1 }) // 55
let avg = numbers.reduce(0.0, combine: { $0 + Double($1) / Double(numbers.count) }) // 5.0
let from4To7SquareNumbers = numbers.filter { $0 > 3 }.filter { $0 < 7 }.map { $0 * $0 } // [16, 25, 36]
Здесь показан пример, как вычислить сумму или среднее в одну строчку. И профильтровать.
Также концепция ламбда-исчисления привносит в языки программирования понятие каррирования. Каррирование позволяет нам разбить функцию с несколькими параметрами на несколько функций с одним параметром. Это дает нам возможность получать результат вычисления промежуточных функций и применять к этим функциям разные аргументы для получения нескольких результатов.
Пример использования каррирования
func raiseToPowerThenAdd(array: [Double], power: Double) -> ((Double) -> [Double]) {
let poweredArray = array.map { pow($0, power) }
return { value in
return poweredArray.map { $0 + value }
}
}
let array = [3.0, 4.0, 5.0]
let intermediateResult = raiseToPowerThenAdd(array, power: 3)
intermediateResult(0) // [27, 64, 125]
intermediateResult(5) // [32, 69, 230]
intermediateResult(10) // [37, 74, 135]
Здесь мы получаем результат вычисления степени чисел в массиве и потом добавляем к этому результату определенное число. Важно обратить внимание на то, что вычисление степеней происходит только один раз при вызове функции raiseToPowerThenAdd.
Заключение
На мой взгляд, самыми важными концепциями для разработки мобильного ПО (в плане качества кода) являются: концепция чистых функций и концепция опциональности. Первая — дает нам понятную и простую идею как сделать наш код более переносимым, качественным и тестируемым. Вторая — заставляет нас думать о возможных крайних случаях и ошибках, которые могут прийти извне, и обрабатывать их корректно.
Надеюсь, материал будет полезен и ваш код станет еще качественней.
Иван Смолин, iOS-разработчик.
Комментарии (26)
lalaki
06.11.2016 13:48+6Иван, есть неточности в определениях:
1) Функция первого класса — из вашего определения следует, что это какие-то особые функции, обладающие особыми свойствами, что неверно:
во-первых, нет такого самостоятельного понятия — "функция первого класса" — это всегда свойство языка программирования, которым некоторые из них обладают: "функции являются объектами первого класса".
во-вторых, свойство "объект первого класса" в рамках языка может быть присуще или не присуще любому из типов сущностей языка целиком — переменным, константам, функциям, классам, интерфейсам, пространствам имен и тп; то есть, если в языке функции являются объектами первого класса, то сразу все, а не какие-то конкретно.
в-третьих, свойства объектов первого класса могут различаться от языка к языку; в частности, в чисто функциональных языках переменных нет, поэтому никакая сущность не может быть присвоена переменной — из вашего определения следует, что там функции не являются объектами первого класса; здесь стоит хотя бы сделать оговорку;
в-четвертых, пропущено одно из ключевых универсальных свойств объектов первого класса — такие объекты могут быть созданы во время выполнения программы
в-пятых, приведен некорректный пример, но это сложно понять из-за предыдущего пункта — в языке C функции не могут быть созданы во время выполнения программы — только во время компиляции, поэтому в C они не являются объектами первого класса
MaximChistov
06.11.2016 15:43+3let sum = numbers.reduce(0, combine: { $0 + $1 }) // 55
Я бы на вашем месте лямбду в классическом виде написал. Не всем очевидна эта фича свифта что
{ $0 + $1 } равносильно записи (a, b) -> a + b
А то так можно вспомнить что в свифте вообще вот так можно написать:
let sum = numbers.reduce(0, combine: +)
vanxant
06.11.2016 23:22+3Дополнение про чистые функции.
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 и т.д. — они все как раз чистые). Но главное, он выделяет память под все строки ещё на этапе компиляции.
dim2r
07.11.2016 00:39Спасибо! Особенно порадовали функции высшего порядка. Из теории систем знаю, что это имеет название мета-уровень или эмерджентный уровень. Очень важная вещь при развитии проекта, если правильно их использовать.
Source
07.11.2016 01:40+2Вторая (концепция опциональности) — заставляет нас думать о возможных крайних случаях и ошибках, которые могут прийти извне, и обрабатывать их корректно.
В Haskell Вы наоборот не думаете о крайних случаях и не определяете функций, которые принимают Maybe (если следуете концепциям языка). Это особенность как концепции опциональности, так и монад в целом… Вы пишете функции, которые принимают чистые значения и возвращают монадические, обо всём остальном заботится>>=
, сам по себе или внутри do-блока.
Ещё в определении sum есть лишняя строка:sum (x:[]) = x -- one element
Source
07.11.2016 01:52В разделе «Пример использования каррирования» у Вас показано не каррирование, а то, что функции являются объектами первого рода, т.е. функция может вернуть функцию.
Каррирование — это когда функция принимает N аргументов, мы ей передаём M параметров и она превращается в функцию, принимающую N-M аргументов, где N >= M && M > 0. Swift скорее всего не поддерживает такое из коробки.
Возможно, Вы сможете определить свою функциюcarry(fn, arg)
, которая будет принимать произвольную функцию и первый параметр для неё, и возвращать её в каррированном виде.sophist
07.11.2016 08:08+1Позвольте, то, что вы написали, тоже не каррирование. Это частичное применение.
Каррирование – это когда функция принимает N аргументов, а мы из неё делаем функцию, которая принимает первый аргумент и возвращает функцию, которая принимает второй аргумент и возвращает функцию, которая … и возвращает функцию, которая принимает N-й аргумент и возвращает значение исходной функции.Source
07.11.2016 10:37Согласен, путаница вышла. Хотя для языков, которые не поддерживают ни то ни другое прозрачно, лучше частичное применение реализовать, а то
f(a)(b)(c)(d)
уже сложно читать, а что-то типаcarry(carry(carry(carry(f, a), b), c), d)()
и подавно.
stepanp
07.11.2016 02:06-3Функциональщина стала модной, с этим нельзя не согласиться. Думаю лет через 5-10 все перестанут с ней носиться и будут так же поливать грязью как сейчас ООП.
dim2r
09.11.2016 11:02А что не так с ООП, по Вашему мнению? можно в личку, что бы не минусовали, у пользователя на профиле есть конвертик для писем
stepanp
09.11.2016 13:46Все с ним нормально, просто раньше все носились с ООП как со священной коровой, пихая его везде где надо и не надо, не понимая что это просто инструмент, и как у любого инструмента у него есть границы применимости. Есть кейсы где этот инструмент подходит, а есть кейсы где не подходит. Сейчас такая же ситуация происходит с функциональщиной.
dim2r
09.11.2016 14:54А более конкретные есть примеры? у меня есть одно наблюдение, где методология дает сбой, но это идет против ветра, затрагивает веру
stepanp
09.11.2016 15:26Приведите лучше свое наблюдение
dim2r
09.11.2016 15:38В ООП и других областях программирования действует мышление, которое можно назвать обобщением. Бывают фразы, типа «все сущности», «все договора», «все операции». При внимательном рассмотрении базовый класс или интерфейс зачастую готов обрабатывать сущности только на момент написания ТЗ. Потом появляются то исключения из правил, то новые типы сущностей, которые никак не влезают в базовый класс. К примеру, на каком-то этапе в организации были клиенты физ-лица. А потом программист который делал интерфейс «Client» уволился, а организация начала обслуживать юр.лица. Последующие программисты будут иметь дело с абстракциами, которые созданы другими и пытаться разгадать что конкретно имелось в виду. Появляются смысловые люфты между программой и предметной областью.
sophist
07.11.2016 08:13+4> Берем Range от одного до биллиона
> let bigRange = 1...1_000_000_000_000 // from one to one billion
Биллион по-русски называется миллиард, а объявлен у вас вообще триллион.
Source
08.11.2016 21:29Если посмотреть на то, что человек, который явно понимает ФП, написал статью, но судя по коментам — не совсем правильно понимает базовые понятия ФП, то становится понятно что функциональное программирование — довольно «путанная штука».
Swift и уж тем более C — не являются функциональным ЯП. Haskell автор не знает на достаточном уровне. Я его и сам не знаю толком, но в статье совсем уж детские грабли в Haskell-коде.
Что до путанности, посмотрите статьи про ООП и вообще офигеете. Абсолютно ни у кого нет чёткого понимания, что входит в это понятие, а что нет. И по каждому ассоциированному термину влёгкую можно начать холивар. ФП в этом плане отлично формализовано, что конечно не страхует от того, что кто-то может что-то напутать. Человеческий фактор не отменить, зато всегда можно глянуть точное определение.
1) Получим массив элементов.
2) Определим, есть ли «наш элемент» в данном массиве?
Это тоже функциональный подход.
Императивный подход:
1) Получим указатель на массив элементов и целевой элемент
2) Определим длину массива
3) В цикле от 0 до длина-1 будем сравнивать i-ый элемент массива с целевым.
4) Если элемент совпал, то return true
5) return falsetaujavarob
08.11.2016 21:58Source > Что до путанности, посмотрите статьи про ООП и вообще офигеете. Абсолютно ни у кого нет чёткого понимания, что входит в это понятие, а что нет. И по каждому ассоциированному термину влёгкую можно начать холивар.
Возможно и так, но обычно все «инстинктивно» ООП понимают, что есть объекты, имеющие свойства (видимые или скрытые) которые (объекты) либо обмениваются сообщениями, либо эти объекты можно «дёрнуть» за их (объектов) методы. — То есть — «всё как в жизни». Типа того. А ещё есть иерархия объектов по наследованию — опять таки — как в жизни.
Source > ФП в этом плане отлично формализовано, что конечно не страхует от того, что кто-то может что-то напутать. Человеческий фактор не отменить, зато всегда можно глянуть точное определение.
Я всегда считал (читал) что «функция высшего порядка» — это «функция возвращающая функцию». Но то ли не там читал, то ли плохо читал, но в данной статье написано иное определение «функции высшего порядка». Печально мне.
Source > Императивный подход:…
Это уж слишком «низкий» подход у вас.
1) Получим массив элементов.
2) Определим, есть ли «наш элемент» в данном массиве?
Иначе, немного пояснив с ООП:
1) Получим объект(!) типа Массив элементов.
2) Определим, есть ли «наш элемент» в данном объекте вызвав («дёрнув») метод этого объекта.
Так что это чистейший ООП. Имхо, конечно, имхо.
Source
08.11.2016 23:11То есть — «всё как в жизни». Типа того. А ещё есть иерархия объектов по наследованию — опять таки — как в жизни.
Ага, именно "типа того". А по факту ничего общего с тем, как в жизни :-)
Да ещё и у каждого своё инстинктивное понимание. Некоторые вообще взаимоисключающие. Впрочем не будем про ООП, на Хабре уже есть много холиваров на эту тему, из которых понятно, что ничего не понятно и общепринятых определений нет :-)
Я всегда считал (читал) что «функция высшего порядка» — это «функция возвращающая функцию».
Там 2 критерия в определении:
- принимает одну или более функций в качестве аргумента
- возвращает функцию в качестве результата
Любого из этих критериев достаточно, чтобы функцию назвать функцией высшего порядка.
Это уж слишком «низкий» подход у вас.
Да нет, просто идеи ФП заимствуют для императивных мейнстрим-языков и они уже не совсем императивные. Те же ФВП теперь где только не встретишь..
1) Получим объект(!) типа Массив элементов.
2) Определим, есть ли «наш элемент» в данном объекте вызвав («дёрнув») метод этого объекта.Ну если уж про ООП, то надо ещё про паттерн Iterator добавить. А то Вы подменяете реализацию алгоритма на её использование. А использование вообще не сильно отличается:
Процедурный стиль:
in_array(arr, obj)
ООП:
arr.include?(obj)
ФП:
Enum.member?(arr, obj)
DrBAXA
А чем єта не чистая? func cube(x: Int) -> Int {
return square(x) * x
}
xGromMx
если в square нет подвоха, то она чистая
vladsabenin
Не square может быть не чистой, соответственно cude тоже не всегда возвращает одно и то же значение
Ostrea
Она использует внешний square, который может быть переопределн кем-то где угодно.
DrBAXA
Но она определена в етом же файле
Ostrea
Но имея только определение cube, вы не можете сказать чистая она или нет. В случая остальных — это возможно, так как они используют свои внутренние функции.