У функционального программирования много преимуществ, и его популярность постоянно растет. Но, как и у любой парадигмы программирования, у ФП есть свой жаргон. Мы решили сделать небольшой словарь для всех, кто знакомится с ФП.
В примерах используется JavaScript ES2015). (Почему JavaScript?)
Работа над материалом продолжается; присылайте свои пулл-реквесты в оригинальный репозиторий на английском языке.
В документе используются термины из спецификации Fantasy Land spec по мере необходимости.
Arity (арность)
Количество аргументов функции. От слов унарный, бинарный, тернарный (unary, binary, ternary) и так далее. Это необычное слово, потому что состоит из двух суффиксов: "-ary" и "-ity.". Сложение, к примеру, принимает два аргумента, поэтому это бинарная функция, или функция, у которой арность равна двум. Иногда используют термин "диадный" (dyadic), если предпочитают греческие корни вместо латинских. Функция, которая принимает произвольное количество аргументов называется, соответственно, вариативной (variadic). Но бинарная функция может принимать два и только два аргумента, без учета каррирования или частичного применения.
const sum = (a, b) => a + b
const arity = sum.length
console.log(arity) // 2
// The arity of sum is 2
Higher-Order Functions (функции высокого порядка)
Функция, которая принимает функцию в качестве аргумента и/или возвращает функцию.
const filter = (predicate, xs) => {
const result = []
for (let idx = 0; idx < xs.length; idx++) {
if (predicate(xs[idx])) {
result.push(xs[idx])
}
}
return result
}
const is = (type) => (x) => Object(x) instanceof type
filter(is(Number), [0, '1', 2, null]) // [0, 2]
Partial Application (частичное применение)
Частичное применение функции означает создание новой функции с пред-заполнением некоторых аргументов оригинальной функции.
// Helper to create partially applied functions
// Takes a function and some arguments
const partial = (f, ...args) =>
// returns a function that takes the rest of the arguments
(...moreArgs) =>
// and calls the original function with all of them
f(...args, ...moreArgs)
// Something to apply
const add3 = (a, b, c) => a + b + c
// Partially applying `2` and `3` to `add3` gives you a one-argument function
const fivePlus = partial(add3, 2, 3) // (c) => 2 + 3 + c
fivePlus(4) // 9
Также в JS можно использовать Function.prototype.bind
для частичного применения функции:
const add1More = add3.bind(null, 2, 3) // (c) => 2 + 3 + c
Благодаря предварительной подготовке данных частичное применение помогает создавать более простые функции из более сложных. Функции с каррированием автоматически выполняют частичное применение.
Currying (каррирование)
Процесс конвертации функции, которая принимает несколько аргументов, в функцию, которая принимает один аргумент за раз.
При каждом вызове функции она принимает один аргумент и возвращает функцию, которая принимает один аргумент до тех пор, пока все аргументы не будут обработаны.
const sum = (a, b) => a + b
const curriedSum = (a) => (b) => a + b
curriedSum(40)(2) // 42.
const add2 = curriedSum(2) // (b) => 2 + b
add2(10) // 12
Auto Currying (автоматическое каррирование)
Трансформация функции, которая принимает несколько аргументов, в новую функцию. Если в новую функцию передать меньшее чем предусмотрено количество аргументов, то она вернет функцию, которая принимает оставшиеся аргументы. Когда функция получает правильное количество аргументов, то она исполняется.
В Underscore, lodash и ramda есть функция curry
.
const add = (x, y) => x + y
const curriedAdd = _.curry(add)
curriedAdd(1, 2) // 3
curriedAdd(1) // (y) => 1 + y
curriedAdd(1)(2) // 3
Дополнительные материалы
Function Composition (композиция функций)
Соединение двух функций для формирования новой функции, в которой вывод первой функции является вводом второй.
const compose = (f, g) => (a) => f(g(a)) // Definition
const floorAndToString = compose((val) => val.toString(), Math.floor) // Usage
floorAndToString(121.212121) // '121'
Purity (чистота)
Функция является чистой, если возвращаемое ей значение определяется исключительно вводными значениями, и функция не имеет побочных эффектов.
const greet = (name) => 'Hi, ' + name
greet('Brianne') // 'Hi, Brianne'
В отличие от:
let greeting
const greet = () => {
greeting = 'Hi, ' + window.name
}
greet() // "Hi, Brianne"
Side effects (побочные эффекты)
У функции есть побочные эффекты если кроме возврата значения она взаимодействует (читает или пишет) с внешним изменяемым состоянием.
const differentEveryTime = new Date()
console.log('IO is a side effect!')
Idempotent (идемпотентность)
Функция является идемпотентной если повторное ее исполнение производит такой же результат.
f(f(x)) ? f(x)
Math.abs(Math.abs(10))
sort(sort(sort([2, 1])))
Point-Free Style (бесточечная нотация)
Написание функций в таком виде, что определение не явно указывает на количество используемых аргументов. Такой стиль обычно требует каррирования или другой функции высокого порядка (или в целом — неявного программирования).
// Given
const map = (fn) => (list) => list.map(fn)
const add = (a) => (b) => a + b
// Then
// Not points-free - `numbers` is an explicit argument
const incrementAll = (numbers) => map(add(1))(numbers)
// Points-free - The list is an implicit argument
const incrementAll2 = map(add(1))
Функция incrementAll
определяет и использует параметр numbers
, так что она не использует бесточечную нотацию. incrementAll2
просто комбинирует функции и значения, не упоминая аргументов. Она использует бесточечную нотацию.
Определения с бесточечной нотацией выглядят как обычные присваивания без function
или =>
.
Predicate (предикат)
Предикат — это функция, которая возвращает true или false в зависимости от переданного значения. Распространенный случай использования предиката — функция обратного вызова (callback) для фильтра массива.
const predicate = (a) => a > 2
;[1, 2, 3, 4].filter(predicate) // [3, 4]
Categories (категории)
Объекты с функциями, которые подчиняются определенным правилам. Например, моноиды.
Value (значение)
Все, что может быть присвоено переменной.
5
Object.freeze({name: 'John', age: 30}) // The `freeze` function enforces immutability.
;(a) => a
;[1]
undefined
Constant (константа)
Переменная, которую нельзя переназначить после определения.
const five = 5
const john = {name: 'John', age: 30}
Константы обладают референциальной прозрачностью или прозрачностью ссылок (referential transparency). То есть, их можно заменить значениями, которые они представляют, и это не повлияет на результат.
С константами из предыдущего листинга следующее выражение выше всегда будет возвращать true
.
john.age + five === ({name: 'John', age: 30}).age + (5)
Functor (функтор)
Объект, который реализует функцию map
, которая при проходе по всем значениям в объекте создает новый объект, и подчиняется двум правилам:
// сохраняет нейтральный элемент (identity)
object.map(x => x) === object
и
// поддерживает композицию
object.map(x => f(g(x))) === object.map(g).map(f)
(f
, g
— произвольные функции)
В JavaScript есть функтор Array
, потому что он подчиняется эти правилам:
[1, 2, 3].map(x => x) // = [1, 2, 3]
и
const f = x => x + 1
const g = x => x * 2
;[1, 2, 3].map(x => f(g(x))) // = [3, 5, 7]
;[1, 2, 3].map(g).map(f) // = [3, 5, 7]
Pointed Functor (указывающий функтор)
Объект с функцией of
с любым значением. В ES2015 есть Array.of
, что делает массивы указывающим функтором.
Array.of(1) // [1]
Lift
Lifting — это когда значение помещается в объект вроде функтора. Если "поднять" (lift) функцию в аппликативный функтор, то можно заставить ее работать со значениями, которые также присутствуют в функторе.
В некоторых реализациях есть функция lift
или liftA2
, которые используются для упрощения запуска функций на функторах.
const liftA2 = (f) => (a, b) => a.map(f).ap(b)
const mult = a => b => a * b
const liftedMult = liftA2(mult) // this function now works on functors like array
liftedMult([1, 2], [3]) // [3, 6]
liftA2((a, b) => a + b)([1, 2], [3, 4]) // [4, 5, 5, 6]
Подъем функции с одним аргументом и её применение выполняет то же самое, что и map
.
const increment = (x) => x + 1
lift(increment)([2]) // [3]
;[2].map(increment) // [3]
Referential Transparency (прозрачность ссылок)
Если выражение можно заменить его значением без влияния на поведение программы, то оно обладает прозрачностью ссылок.
Например, есть функция greet
:
const greet = () => 'Hello World!'
Любой вызов greet()
можно заменить на Hello World!
, так что эта функция является прозрачной (referentially transparent).
Lambda (лямбда)
Анонимная функция, которую можно использовать как значение.
;(function (a) {
return a + 1
})
;(a) => a + 1
Лямбды часто передают в качестве аргументов в функции высокого порядка.
[1, 2].map((a) => a + 1) // [2, 3]
Лямбду можно присвоить переменной.
const add1 = (a) => a + 1
Lambda Calculus (лямбда-исчисление)
Область информатики, в которой функции используются для создания универсальной модели исчисления.
Lazy evaluation (ленивые вычисления)
Механизм вычисления при необходимости, с задержкой вычисления выражения до того момента, пока значение не потребуется. В функциональных языках это позволяет создавать структуры вроде бесконечных списков, которые в обычных условиях невозможны в императивных языках программирования, где очередность команд имеет значение.
const rand = function*() {
while (1 < 2) {
yield Math.random()
}
}
const randIter = rand()
randIter.next() // Каждый вызов дает случайное значение, выражение исполняется при необходимости.
Monoid (моноид)
Объект с функцией, которая "комбинирует" объект с другим объектом того же типа. Простой пример моноида это сложение чисел:
1 + 1 // 2
В этом случае число — это объект, а +
это функция.
Должен существовать нейтральный элемент (identity), так, чтобы комбинирование значения с ним не изменяло значение. В случае сложения таким элементом является 0
.
1 + 0 // 1
Также необходимо, чтобы группировка операций не влияла на результат (ассоциативность):
1 + (2 + 3) === (1 + 2) + 3 // true
Конкатенация массивов — это тоже моноид:
;[1, 2].concat([3, 4]) // [1, 2, 3, 4]
Нейтральный элемент — это пустой массив []
;[1, 2].concat([]) // [1, 2]
Если существуют функции нейтрального элемента и композиции, то функции в целом формируют моноид:
const identity = (a) => a
const compose = (f, g) => (x) => f(g(x))
foo
— это любая функция с одним аргументом.
compose(foo, identity) ? compose(identity, foo) ? foo
Monad (монада)
Монада — это объект с функциями of
и chain
. chain
похож на map
, но он производит разложение вложенных объектов в результате.
// Implementation
Array.prototype.chain = function (f) {
return this.reduce((acc, it) => acc.concat(f(it)), [])
}
// Usage
;Array.of('cat,dog', 'fish,bird').chain((a) => a.split(',')) // ['cat', 'dog', 'fish', 'bird']
// Contrast to map
;Array.of('cat,dog', 'fish,bird').map((a) => a.split(',')) // [['cat', 'dog'], ['fish', 'bird']]
of
также известен как return
в других функциональных языках.
chain
также известен как flatmap
и bind
в других языках.
Comonad (комонада)
Объект с функциями extract
и extend
.
const CoIdentity = (v) => ({
val: v,
extract () {
return this.val
},
extend (f) {
return CoIdentity(f(this))
}
})
Extract берет значение из функтора.
CoIdentity(1).extract() // 1
Extend выполняет функцию на комонаде. Функция должна вернуть тот же тип, что комонада.
CoIdentity(1).extend((co) => co.extract() + 1) // CoIdentity(2)
Applicative Functor (аппликативный функтор)
Объект с функцией ap
. ap
применяет функцию в объекте к значению в другом объекте того же типа.
// Implementation
Array.prototype.ap = function (xs) {
return this.reduce((acc, f) => acc.concat(xs.map(f)), [])
}
// Example usage
;[(a) => a + 1].ap([1]) // [2]
Это полезно, когда есть два объекта, и нужно применить бинарную операцию на их содержимом.
// Arrays that you want to combine
const arg1 = [1, 3]
const arg2 = [4, 5]
// combining function - must be curried for this to work
const add = (x) => (y) => x + y
const partiallyAppliedAdds = [add].ap(arg1) // [(y) => 1 + y, (y) => 3 + y]
В итоге получим массив функций, которые можно вызвать с ap
чтобы получить результат:
partiallyAppliedAdds.ap(arg2) // [5, 6, 7, 8]
Morphism (морфизм)
Функция трансформации.
Endomorphism (эндоморфизм)
Функция, у которой ввод и вывод — одного типа.
// uppercase :: String -> String
const uppercase = (str) => str.toUpperCase()
// decrement :: Number -> Number
const decrement = (x) => x - 1
Isomorphism (изоморфизм)
Пара структурных трансформаций между двумя типами объектов без потери данных.
Например, двумерные координаты можно хранить в массиве [2,3]
или объекте {x: 2, y: 3}
.
// Providing functions to convert in both directions makes them isomorphic.
const pairToCoords = (pair) => ({x: pair[0], y: pair[1]})
const coordsToPair = (coords) => [coords.x, coords.y]
coordsToPair(pairToCoords([1, 2])) // [1, 2]
pairToCoords(coordsToPair({x: 1, y: 2})) // {x: 1, y: 2}
Setoid
Объект, у которого есть функция equals
, которую можно использовать для сравнения объектов одного типа.
Сделать массив сетоидом:
Array.prototype.equals = (arr) => {
const len = this.length
if (len !== arr.length) {
return false
}
for (let i = 0; i < len; i++) {
if (this[i] !== arr[i]) {
return false
}
}
return true
}
;[1, 2].equals([1, 2]) // true
;[1, 2].equals([0]) // false
Semigroup (полугруппа)
Объект с функцией concat
, которая комбинирует его с другим объектом того же типа.
;[1].concat([2]) // [1, 2]
Foldable
Объект, в котором есть функция reduce
, которая трансформирует объект в другой тип.
const sum = (list) => list.reduce((acc, val) => acc + val, 0)
sum([1, 2, 3]) // 6
Type Signatures (сигнатуры типа)
Часто функции в JavaScript содержат комментарии с указанием типов их аргументов и возвращаемых значений. В сообществе существуют разные подходы, но они все схожи:
// functionName :: firstArgType -> secondArgType -> returnType
// add :: Number -> Number -> Number
const add = (x) => (y) => x + y
// increment :: Number -> Number
const increment = (x) => x + 1
Если функция принимает другую функцию в качестве аргумента, то ее помещают в скобки.
// call :: (a -> b) -> a -> b
const call = (f) => (x) => f(x)
Символы a
, b
, c
, d
показывают, что аргументы могут быть любого типа. Следующая версия функции map
принимает:
- функцию, которая трансформирует значение типа
a
в другой типb
- массив значений типа
a
,
и возвращает массив значений типа b
.
// map :: (a -> b) -> [a] -> [b]
const map = (f) => (list) => list.map(f)
Дополнительные материалы
- Ramda's type signatures
- Mostly Adequate Guide
- What is Hindley-Milner? на Stack Overflow
Union type (тип-объединение)
Комбинация двух типов в один, новый тип.
В JavaScript нет статических типов, но давайте представим, что мы изобрели тип NumOrString
, который является сложением String
и Number
.
Операция +
в JavaScript работает со строками и числами, так что можно использовать наш новый тип для описания его ввода и вывода:
// add :: (NumOrString, NumOrString) -> NumOrString
const add = (a, b) => a + b
add(1, 2) // Возвращает число 3
add('Foo', 2) // Возвращает строку "Foo2"
add('Foo', 'Bar') // Возвращает строку "FooBar"
Тип-объединение также известно как алгебраический тип, размеченное объединение и тип-сумма.
Существует пара библиотек в JavaScript для определения и использования таких типов.
Product type (тип-произведение)
Тип-произведение комбинирует типы таким способом, который вам скорее всего знаком:
// point :: (Number, Number) -> {x: Number, y: Number}
const point = (x, y) => ({x: x, y: y})
Его называют произведением, потому что возможное значение структуры данных это произведение (product) разных значений.
См. также: теория множеств.
Option (опцион)
Тип-объединение с двумя случаями: Some
и None
. Полезно для композиции функций, которые могут не возвращать значения.
// Naive definition
const Some = (v) => ({
val: v,
map (f) {
return Some(f(this.val))
},
chain (f) {
return f(this.val)
}
})
const None = () => ({
map (f) {
return this
},
chain (f) {
return this
}
})
// maybeProp :: (String, {a}) -> Option a
const maybeProp = (key, obj) => typeof obj[key] === 'undefined' ? None() : Some(obj[key])
Используйте chain
для построения последовательности функций, которые возвращают Option
.
// getItem :: Cart -> Option CartItem
const getItem = (cart) => maybeProp('item', cart)
// getPrice :: Item -> Option Number
const getPrice = (item) => maybeProp('price', item)
// getNestedPrice :: cart -> Option a
const getNestedPrice = (cart) => getItem(obj).chain(getPrice)
getNestedPrice({}) // None()
getNestedPrice({item: {foo: 1}}) // None()
getNestedPrice({item: {price: 9.99}}) // Some(9.99)
Option
также известен как Maybe
. Some
иногда называют Just
. None
иногда называют Nothing
.
Библиотеки функционального программирования в JavaScript
Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Комментарии (51)
xGromMx
16.09.2016 13:36+6Если реально хотите понять про ФП, то лучше почитайте книги о Haskell. Из русскоязычного настоятельно рекомендую http://anton-k.github.io/ru-haskell-book/book/toc.html тут и про ФП и про ТК есть отчасти достаточно интуитивно понятно (вообще это наверное лучшее, что я читал из бесплатных онлайн книг на русском языке)
xGromMx
16.09.2016 15:07+2Еще по поводу ТК https://www.youtube.com/watch?v=I8LbkfSSR58&list=PLbgaMIhjbmEnaH_LTkxLI7FMa2HsnawM_ и вот подборка по ФП https://github.com/xgrommx/awesome-functional-programming (welcome to PR)
Warlock_29A
16.09.2016 16:36Дополню, что у Bartosz Milewski помимо ТК, так же можно и по Haskell лекции посмотреть.
greendimka
16.09.2016 15:18-10По-моему приверженцам "функционального" программирования чего-то в жизни не хватает.
Вот объясните мне (простому приверженцу принципа "работа должна быть сделана качественно и в сроки") — какую ценность несёт, хотя бы и подразделение функций на унарные, бинарные, тернарные и т. д.?
Сколько не читаю про так называемое ФП (функциональное программирование), так и не могу понять, что же в нём особенного? Функции используются практически в любом языке, практически в любой архитектуре. Нафига вводить еще один жаргон? Время девать некуда?80x86
16.09.2016 15:41+3Узость мышления зачастую компенсируется яростностью отстаивания точки зрения.
ФП помогает расширить интеллектуальный горизонт, например :) Как по мне, так хотя бы для этого оно нужно. Наверное.greendimka
16.09.2016 15:59-1То есть разложение на категории ради разложения и присвоение терминов ради присвоения — это и есть расширение мышления? По-моему это больше походит на подмену понятий, когда знание большого количества фактов пытаются выдать за умение мыслить.
Поэтому повторю свой основной вопрос: нафига? Время девать некуда?
И перенесу вопрос в другую плоскость: вот переписываемся мы с вами на великом и могучем. Я, честно признаюсь, давно уже забыл чем причастие отличается от деепричастия, что такое склонение, и многое другое, относящееся к терминам правил русского языка. Но это не мешает мне формулировать мысли, понимать вас, и вести беседу. Вопрос: как изменится моя речь, если я начну думать не о смысле сказанного, а о терминах?Warlock_29A
16.09.2016 16:32+4"работа должна быть сделана качественно и в сроки"
Идеи функционального программирования как раз таки и способствуют достижения и этих целей тоже, иначе бы они не появлялись в мейнстриме.
И для получения от ФП профита уже сегодня, совершенно не нужно знать что такое функторы, монады, моноиды и другие термины из Теории Категорий. Не ужели вы не используете лямбда функции и хотя бы такие паттерны как map, reduce, filter ?
Большая часть терминов из этой статьи придумана математики в рамках разработки теории категорий, можете посоветовать им что именно они могли бы сделать полезного, заместо того что бы заниматься всякой ерундой на вроде:
разложение на категории ради разложения и присвоение терминов ради присвоения
faiwer
16.09.2016 16:35+3Есть такое слово: парадигма. Судя по вики это: "совокупность фундаментальных научных установок, представлений и терминов". Т.е. нечто большое, что может повлечь за собой смену образа мышления. Скажем есть декларативность и императивность. Согласитесь, это далеко не одно и тоже. И там и там большая мат. база. И чтобы этим жить "в полный рост" нужно эту базу понимать и уметь применять.
Вот и с ФП такая же картинка. Если рассматривать отдельные кирпичики из него, возникнет недопонимание: зачем это может быть нужно? Всё так сложно и без явных преимуществ. Но кирпичики комбинируются, соединяются в причудливые схемы. И вот тогда программист пожинает profit. А так как область эта очень большая и развивается десятилетиями, да ещё и сугубо математична (а математика никогда не была особенно простой), то и нечему удивляться, что всё как-то абстрактно и сложно.
greendimka
16.09.2016 16:44-1Спасибо за ответ. Ваша точка зрения мне понятна.
Всё же останусь при своём. А именно: считаю чрезмерное использование терминов излишней тратой времени и ресурсов.
Безусловно, всем, представленным в статье, парадигмам должны быть обучены все, без исключения, программисты. Но не на уровне терминов, а на подсознательном уровне. Ровно так же, как и с языками, на которых мы общаемся.faiwer
16.09.2016 16:53+1Но не на уровне терминов, а на подсознательном уровне
Можно ли понять диффуры без подготовки в виде многих лет изучения алгебры? Думаю да. Но какой ценой.
80x86
16.09.2016 16:38+3Прошу заранее прощения за грубость и контрастность того, что будет ниже.
То есть разложение на категории ради разложения и присвоение терминов ради присвоения
Это зависит от точки зрения.
Можно воспринимать ФП (в контексте парадигмы) как бессмысленный набор стрелочек и символов и относиться к этому как к интеллектуальной мастурбации. Можно воспринимать его как онтологию и, соответственно, как радикальный и целостный подход к решению прикладных статических задач.
Первая точка зрения, в таком случае, автоматически (согласно поведенческой установке, лежащей в её базе) отсекает для носителя целый пласт интеллектуального творчества неглупых людей. Вторая же точка зрения позволяет взглянуть на достижение цели (т.е., сложную динамическую задачу) как на, прошу прощения за аналогию, «интеграл» статических задач по состоянию (стейту) и, соответственно, даёт возможность элегантно отделять декларативные (описательные) элементы достижения поставленной цели от императивных (последовательных и зависимых) элементов. Не думаю, что это плохая возможность.
Не секрет же, что метапрограммирование, проникшее во многие императивные языки, позволяет более эффективно решать задачи с точки зрения производительности разработчика. Это что, тоже плохо? А полностью овладеть метапрограммированием, без понимания функционального программирования, просто невозможно.
Вопрос: как изменится моя речь, если я начну думать не о смысле сказанного, а о терминах?
Никак. Ваша интеллектуальная деятельность уже впитала в себя парадигму русского языка как комплекса синтаксиса, морфологии, семантики и, соответственно, ментальности как онтологии русского языка. Но, без изучения синтаксиса, морфологии, семантики и, как следствие, восприятия ментальности, вы бы не смогли вести беседу на русском языке как русский. При этом, как вы правильно заметили, онтология, как абстракция, может существовать и без её структурного описания. Но, для её рождения это структурное описание нужно.
Аналогия с функциональным программированием как парадигмой ясна, я думаю?
P.S. Исправил разметку, но суть текста не менял.greendimka
16.09.2016 16:49+1Спасибо за хороший развёрнутый ответ.
P.S. жаль, что докапываться до истины всегда приходится сквозь шквал минусов и грубость80x86
16.09.2016 16:55+2Да не за что.
Просто вы, в конфронтационной манере типировав, задели тех, для кого эта статья предназначалась. Реакция читателей, думаю, очевидна. Минус поставить проще, чем потратить время на ответ :)
erlyvideo
16.09.2016 23:50Вы абсолютно неправы, а greendimka задал очень важный вопрос: зачем все эти термины и как они помогают программировать?
Labunsky
17.09.2016 01:27Этот вопрос можно спокойно задавать в любой другой статье по любой парадигме. А ответ везде будет один — «кому-то так удобнее»
erlyvideo
17.09.2016 08:05это глупый ответ людей, которые не смогли до конца разобраться в том, что для них придумали.
Человек, который разобрался, даст условия и покажет: как и когда этим пользоваться.
Посмотрите Мартина Фаулера Рефакторинг кода: все приемы он сопровождает примерами кода в которых это будет удобно, причем к каждому примеру делает контрпример.
sshikov
16.09.2016 19:26-1Знаете, я вот не понял, за что вас заминусовали. На самом деле, очень многие статьи по ФП страдают именно этим — из них нифига не ясно, в чем состоит полезность той или иной парадигмы. Причем ее в общем-то можно сформулировать в нескольких предложениях или максимум абзацах — но почему-то довольно редко получается это сделать. И вопрос ваш вполне имеет право на жизнь, более того — просто обязан быть задан.
Но в данном случае вы выбрали неудачный объект для претензий. Разделение функций конечно существует, но большого значения ему не придается, по одной простой причине — в большинстве полноценных ФП языков достаточно иметь унарные функции, которые являются сущностью первого порядка, чтобы свести все остальные к ним. И это, кстати, является одним из достоинств — в ФП есть совсем немного фундаментальных сущностей, которые пришли как правило из математики, при этом все остальные сущности можно из них эффективно выразить.
80x86
16.09.2016 15:37+5Демонстрация парадигм ФП
на котятахв Javascript-переложении, безусловно, хороша в пропагандистских и общеобразовательных целях, но, к сожалению, чревата импринтингом. Надо бы от этом в комментарию к переводу написать, т.к. монада, например, в общем случае, не есть контракт на chain/of в терминах реализации на Javacript.
Несмотря на это, перевод стоит добавить в избранное как intelligence mock.
ParkeTg
16.09.2016 15:55+1Раз уже речь зашла о ФП на примере JavaScript, хочу обратить внимание общественности на один замечательный проект:
PureScript. Компилятор генерирует хороший человекочитаемый код.
Очень похож (практически клон) на Haskell, однако есть некоторые ключевые отличия.
Более глубоко можно ознакомиться в документе PureScript by Example.Warlock_29A
16.09.2016 16:08+2Тогда можно еще и на Elm обратить внимание.
hellosandrik
17.09.2016 08:58Тысячи их :) Но из всех них к терминам в статье ближе всего именно PureScript, т.к. он ближе всего к Haskell. Конечно, есть еще ghcjs, но это не особо прагматичный подход и тянет за собой большой рантайм (в первую очередь из-за сохранения семантики Haskell, а именно laziness). У PureScript вообще нет рантайма, его очень легко начать использовать в проекте (например, с webpack loader'ом) и сразу получить все плюсы чисто-функционального языка, не отказываясь при этом от богатой экосистемы Node.js (потому что у PureScript супер простой FFI). Что касается Elm, то это скорее «all-or-nothing» решение. Если захочется использовать third-party js библиотеки, то надо быть готовым к тому, чтобы городить огород кода (как js, так и elm). А еще, в отличие от PureScript, который уже достаточно стабилен (он все-таки копирует Haskell), Elm все еще развивается и буквально в недавнем релизе снова поменялся API. В общем, я считаю, что для продакшена он пока не готов, хотя идея довольно интересная. P.S.: Сам уже устал от js, «undefined is not a function», тонны рантайм ошибок, boilerplate'а из Immutable.js и пр., поэтому потратил довольно много времени на выбор альтернативы и остался с PureScript, т.к. это наиболее прагматичный из всех подход.
BiosUefi
16.09.2016 16:38Смотрю многие хвалят функциональное программирование, посоветуйте как его можно приментить для инициализации UEFI. Подойдет любой вариант хоть для x86-64, хоть для ARM.
80x86
16.09.2016 16:50+7«Смотрю, многие хвалят метапрограммирование, паттерны проектирования, контейнеризацию, сервисно-ориентированную архитектуру, посоветуйте, как их можно применить для инициализации UEFI. Подойдёт любой вариант хоть для amd64, хоть для ARM.»
Советовать сварщику варить металл кисточкой для рисования, я думаю, глупо. Глупо и сварщику о таком спрашивать.
ForNeVeR
16.09.2016 18:03+1Я не квалифицирован достаточно, чтобы отвечать на ваш вопрос, но попробую просто в виде примера показать, как ФП может помочь решать задачи из реального мира.
Есть такая штука — SSIS. По жизни штуковина довольно сложная, и вызывающая множество проблем и боли у разработчиков, которые её пытаются использовать для каких-то прикладных задач (а она действительно бывает нужна, просто поверьте пока что на слово). Итак, разработчики плачут, колются, но продолжают пожирать кактус с полуимперативными действиями по деплойменту и настройке SSIS-пакетов, воюют с разными версиями IDE, серверов и прочей дребеденью.
И тут появляется Chimayo. С использованием сравнительно небольшого количества паттернов функционального языка она позволяет описывать эти SSIS-задачи, формировать их в функциональном стиле, манипулировать как значениями первого класса, ну и всё такое. В общем, позволяет избавиться от боли и начать программистам, наконец, программировать.
К чему я это сказал? Я думаю, что для вашей инициализации UEFI просто не написан (или пока что вам не встретился) подходящий декларативный инструмент, использующий современные достижения функционального программирования. Если б он был — поверьте, вам бы понравилось, как и нам понравилось работать с Chimayo после ада ручной настройки SSIS.
BiosUefi
16.09.2016 16:59+1«Глупо и сварщику о таком спрашивать. „
Ну вот опять рабочий класс послали, а так хотелось приобщиться к прекрасному! Не “стрелять себе а ногу», манипулировать обьектами и функциональностью, наслаждаться шаблонами и паттернами.
Увы и ах, ухожу плакать.80x86
16.09.2016 17:12+3А почему вы уходите плакать? В каждом технологическом стеке есть слои, внутри которых существуют специализированные инструменты, зона применимости которых ограничена этими слоями и какой-то пограничной их областью. Узость пограничной область, в свою очередь, определяется обратной степенной функцией комбинаторной сложности проникающих между слоями решений. Это обуславливается накладными расходами компрессии механики описания прикладной области слоя (т.е. мощностью абстракции) и, соответственно, ресурсами, необходимыми для компенсации этих накладных расходов.
Радуйтесь тому, что у вас есть возможность невозбранно использовать архитектурно-специфичные конструкции, красивую арифметику указателей, радоваться плотной бинарной упаковке и наслаждаться битовыми масками. Хотя, вполне возможно, в современном мире UEFI и этой низкоуровневщины уже нет.
eugzol
16.09.2016 19:59+1Вообще, ответы на ваши вполне корректные вопросы показывает всю несостоятельность местных икспертов по ФП, надувающих здесь щёки. Если есть решения на функциональных ЯП, решающих задачи конвертации контейнеров видеопотоков, почему бы оно было априори не применимо к другим низкоуровневым задачам? Я не эксперт в ФП (хотя тут ни один комментатор каких-то надёжных свидетельств своей экспертности не привёл), но мне кажется, что ФП не применяют к низкоуровневым задачам из-за сложившейся привычки и отсутствия должной инфраструктуры (компиляторов, стандартных библиотек с нужными структурами данных, и т.д.), а не из-за какой-то принципиальной невозможности.
Собственно, ФП практически нигде не применяют по этой причине. Инерция мышления и наличие огромной императивной базы кода.
Кстати, Javascript, каким бы он уродским языком не был (я сам его таковым считаю) и как бы не кривили морды парящие в абстракных функторо-монадных высях завезённые эксперты, как раз за счёт своей огромной популярности по факту подогревает интерес к теме.lorc
16.09.2016 22:02+1ФП хороши для преобразования данных. Собственно, функции в математическом смысле только и делают что отображают элементы одних множеств на элементы других множеств. Идеальная программа на ФП не содержит сайд-эффектов.
Напротив, вся работа железа на низком уровне построена именно на сайд-эффектах. Настолько, что это даже сносит крышу даже обычным С-компиляторам. Например как вам такое — вы пишете в область памяти одно число, а потом читаете оттуда другое? Или читаете два раза подряд из одной и тоже ячейки памяти и при этом получаете совершенно разные значения?
Это не глюк, так работает вся memory-mapped периферия. Приходится использовать хитрые конструкции чтобы сишные компиляторы не ломали работу с периферией своими оптимизациями. Более того, нынешние процессоры тоже шибко умные и выбрасывают лишние (по их мнениею) обращения к памяти. А что тогда говорить про языки более высокого уровня? Поэтому и нет применеия ФП в UEFI.
Зато, если мне надо будет превращать тысячи HTTP запросов в тысячи HTTP ответов — я вользу Эрланг. Потому что ключевое слово — «превращать». Отображать множество HTTP запросов на множество HTTP ответов.hellosandrik
17.09.2016 09:48Мне кажется, что то, как сейчас построена работа железа на низком уровне вовсе не означает, что применения ФП там нет и быть не может. Просто так сложилось — мы сделали императивное железо и начали писать императивный код. Но если применить принципы ФП в нужном месте, то можно получить большой плюс, даже в отношении железа. Я считаю, что яркий пример — шейдеры. Раньше компьютерная графика была довольно слабой, но распараллелив вычисления удалось достигнуть большого прироста производительности. А распараллелить удалось применив принципы ФП. Ведь шейдеры, по сути — функции без сайд-эффектов: мы передаем данные в вершинный шейдер, а результат вычисления в фрагментный шейдер. Так что, с «инерцией мышления» я очень согласен.
xGromMx
16.09.2016 22:59Netflix полностью построили свой бизнес на RxJava и RxJS что используется у них в проде. От части в Rx есть некие элементы FP
seryh
17.09.2016 08:58Пример Clojure показывает что наличием должной инфраструктуры, популяризации должной не добиться. Там через interop в java, доступны все богатства и мощь JVM мира. Но как видим, только небольшая прослойка ценителей ФП осторожно работают с ним или со Scala. Так что инерция мышления, самый главный враг ФП.
lorc
16.09.2016 22:05+1А потому что для каждой задачи свой инструмент. Я вот тоже по работе занимаюсь тем, что битики флипаю. Но когда мне надо обработать мегабайт трейс-логов я достаю Питон, а не начинаю писать парсер на С. Угадайте, почему?
xGromMx
16.09.2016 17:28В школе нас учат арифметики, операции над числами что есть некое множество которое поддается законам моноида. Но если обобщить все это и перенести на различный уровень абстракций то это правило будет справедливо и для других абстракций. Вот в программировании ТК это есть некая категория типов, а морфизмы это ф-ции вида a->b. Также эти ф-ции являются моноидами ибо есть единичный элемент id = x->x и бинарный оператор композиция. Функтор это тоже морфизм просто между 2-категориями (грубо говоря если вы простой тип упакуете в другой, например int в array). Задача ТК научится манипулировать между абстракциями не взирая на содержимое. Если был массив то после морфизма это и должен быть массив. Не важно был это массив целых, а стал массив строк, но ведь условие сохранения внешней структуры осталось это все тот же массив. В древнем Египте все начиналось с того, что люди рисовали 10,20,300 различных объектов (людей, и прочее). Но вскоре они поняли, что всему этому можно придать некую абстрактность и что 2 человека + 1 человек будет 3 и это справедливо к пирамидам, золоту и тд. Далее понимается тот факт что помимо простой манипуляции это все можно объединить в некий класс (множество) и иметь все те же законы (вот например в школе мы учили всякие коммутативные, ассоциативные, дистрибутивные, левая/правая тождественность и прочее) И в чем парадокс, что все эти законы ложаться на любой уровень абстракции. Вот например для моноида работают законы ассоциативности, левой и правой тождественности ну иногда (например как с числами, а более того у них два бинарных оператора типа +/* и два возможных множества, это множество целых и натуральных чисел) закон коммутативности. Об этом можно продолжать еще долго, просто нужно понять одно те, маленькие кирпичики что мы знали раньше при определенных условиях мы можем применять на различных структурах, если поддать их неким общим математическим правилам и законам. И тогда (в отличии от императивных подходов) мы можем строго уверять, что поведение будет строгим и закономерным. Думаю все рассказать в одном комментарии трудно =)
erlyvideo
16.09.2016 18:55const map = (fn) => (list) => list.map(fn)
const add = (a) => (b) => a + b
Совершенно непонятная запись. Что здесь вообще написано?xGromMx
16.09.2016 19:48+2еще для одного аргумента скобки можно опустить и будет все читаемей)
const S = x => y => z => x(z)(y(z))
const K = x => y => x
const I = S(K)(K)
G-M-A-X
16.09.2016 22:38Вот что мне не нравится в ООП, так то, что там методы объектов имеют побочные эффекты (влияют на члены объекта).
Ну и вообще не люблю трехэтажные абстракции.
Сам я не фанат ни одной из парадигм.
Абстракции повышаю по мере надобности.
erlyvideo
16.09.2016 23:54-2Рахим, прости, но я поставил минус.
Мы зарабатываем деньги на разработке и продаже софта на эрланге, но мне ничего непонятно из этой статьи, я ощутил себя так же беспомощно, как при чтении книжки Душина и так же без понимания того: зачем вся эта терминология, какие проблемы решает и как.
Плюс это всё таки статья для тех, кто уже знает хаскель, а для них она мягко говоря бессмысленна, они уже итак всю терминологию знают.
Хотите сделать статью не для любителей хаскеля — используйте ну хотя бы яваскрипт что ли, но ни в коем случае не хаскелевский синтаксис.
yamatoko
17.09.2016 05:57в чем разница между каррированием и частичным применением функции?
Optik
17.09.2016 08:23Каррирование — преобразование сигнатуры функции, для получения результата нужно передать то же количество аргументов. После каррирования получается функция, которая принимает часть аргументов и возвращает новую функцию, которая принимает другую часть аргументов и возвращает…
Частичное применение производится на каррированной функции. Если передать передать лишь первую группу аргументов, то на выходе будет новая функция (которая благодаря замыканию, будет использовать переданные ранее значения). Это не так часто применимо как об этом пишут, но когда потребуется, позволяет сильно сократить код, повысить читаемость (наверно потому так часто и упоминают, что польза большая, а минусов нет). Если применять везде, то читаемость только ухудшится, потому что сигнатуры не будут нести логического обоснования и в большом нагромождении принесут путаницу.
Каррирование и частичное применение связаны непосредственно друг с другом, поэтому это нормально, что не видно сначала разницы.
e_Hector
17.09.2016 08:59насколько понял из статьи, каррированная функция всегда принимает строго один аргумент и возвращает либо результат, либо функцию для следующего аргумента, а частично примененная функция просто принимает меньшее число аргументов чем изначальная функция и сразу возвращает результат.
koldyr
Очень странное определение категории. Морфизмы в катеориях это не всегда функции. И определенных правил всего три: Существование композиции, существование единицы и ассоциативность композиции.
xGromMx
Тут вообще очень много странных определений