Functional programming has many advantages, and its popularity is constantly growing. But, like any programming paradigm, the FP has its own jargon. We decided to make a small dictionary for everyone who is familiar with the OP.
The examples use javascript ES2015). ( Why javascript? )
Work on the material continues ; send your pull-requests to the original repository in English.
The document uses terms from the Fantasy Land spec specification as needed.
The number of function arguments. From the words unary, binary, ternary (unary, binary, ternary) and so on. This is an unusual word because it consists of two suffixes: "-ary" and "-ity.". Addition, for example, takes two arguments, so this is a binary function, or a function that has an arity equal to two. Sometimes the term "dyadic" (dyadic) is used if they prefer Greek roots instead of Latin ones. A function that takes an arbitrary number of arguments is called the variadic, respectively. But a binary function can take two and only two arguments, without regard to currying or partial application.
const sum = (a, b) => a + b const arity = sum.length console.log(arity) // 2 // The arity of sum is 2
A function that takes a function as an argument and / or returns a function.
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 of the function means the creation of a new function with the pre-filling of some of the arguments of the original function.
// 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
Also in JS, you can use Function.prototype.bind
to partially apply the function:
const add1More = add3.bind(null, 2, 3) // (c) => 2 + 3 + c
Due to preliminary data preparation, partial application helps to create simpler and more complex functions. Functions with currying automatically perform partial applications.
The process of converting a function that takes several arguments to a function that takes one argument at a time.
For each function call, it takes one argument and returns a function that takes one argument until all arguments have been processed.
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
Transform a function that takes several arguments to a new function. If you pass a smaller number of arguments to the new function, it will return a function that accepts the remaining arguments. When the function receives the correct number of arguments, it is executed.
In Underscore, lodash and ramda there is a curry
function.
const add = (x, y) => x + y const curriedAdd = _.curry(add) curriedAdd(1, 2) // 3 curriedAdd(1) // (y) => 1 + y curriedAdd(1)(2) // 3
Additional materials
Combining two functions to form a new function in which the output of the first function is the input of the second.
const compose = (f, g) => (a) => f(g(a)) // Definition const floorAndToString = compose((val) => val.toString(), Math.floor) // Usage floorAndToString(121.212121) // '121'
A function is pure if the value returned to it is determined solely by the input values, and the function has no side effects.
const greet = (name) => 'Hi, ' + name greet('Brianne') // 'Hi, Brianne'
Unlike:
let greeting const greet = () => { greeting = 'Hi, ' + window.name } greet() // "Hi, Brianne"
A function has side effects if, in addition to returning a value, it interacts (reads or writes) with an external changeable state.
const differentEveryTime = new Date()
console.log('IO is a side effect!')
A function is idempotent if its repeated execution produces the same result.
f(f(x)) ≍ f(x)
Math.abs(Math.abs(10))
sort(sort(sort([2, 1])))
Writing functions in such a way that the definition implicitly indicates the number of arguments used. This style usually requires currying or another high-order function (or, in general, implicit programming).
// 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))
The incrementAll
function defines and uses the numbers
parameter, so that it does not use pointless notation. incrementAll2
simply combines functions and values without mentioning arguments. It uses no- point notation.
Definitions with no-point notation look like ordinary assignments without function
or =>
.
A predicate is a function that returns true or false depending on the value passed. A common case of using a predicate is a callback function for an array filter.
const predicate = (a) => a > 2 ;[1, 2, 3, 4].filter(predicate) // [3, 4]
Objects with functions that obey certain rules. For example, monoids.
Anything that can be assigned to a variable.
5 Object.freeze({name: 'John', age: 30}) // The `freeze` function enforces immutability. ;(a) => a ;[1] undefined
Variable that cannot be reassigned after definition.
const five = 5 const john = {name: 'John', age: 30}
Constants have referential transparency or referential transparency. That is, they can be replaced by the values that they represent, and this will not affect the result.
With constants from the previous listing, the following expression will always return true
.
john.age + five === ({name: 'John', age: 30}).age + (5)
An object that implements the map
function, which, when traversing all values in an object, creates a new object, and obeys two rules:
// (identity) object.map(x => x) === object
and
// object.map(x => f(g(x))) === object.map(g).map(f)
( f
, g
- arbitrary functions)
JavaScript has an Array
functor because it obeys these rules:
[1, 2, 3].map(x => x) // = [1, 2, 3]
and
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]
An object with a of function with any value. In ES2015, there is Array.of
, which makes arrays a pointing functor.
Array.of(1) // [1]
Lifting is when a value is placed in an object like a functor. If we "lift" (lift) a function into an applicative functor, then we can make it work with values that are also present in the functor.
In some implementations, there is a function lift
or liftA2
, which are used to simplify the launch of functions on functors.
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]
Raising a function with one argument and applying it does the same thing as map
.
const increment = (x) => x + 1 lift(increment)([2]) // [3] ;[2].map(increment) // [3]
If an expression can be replaced with its value without affecting the program's behavior, then it has transparency of the links.
For example, there is a greet
function:
const greet = () => 'Hello World!'
Any greet()
call can be replaced with Hello World!
so this function is transparent (referentially transparent).
An anonymous function that can be used as a value.
;(function (a) { return a + 1 }) ;(a) => a + 1
Lambdas are often passed as arguments to higher order functions.
[1, 2].map((a) => a + 1) // [2, 3]
Lambda can be assigned to a variable.
const add1 = (a) => a + 1
Informatics, in which functions are used to create a universal model of calculus .
The calculation mechanism, if necessary, with a delay in the calculation of the expression until the value is required. In functional languages, this allows you to create structures like endless lists that are not normally possible in imperative programming languages, where the order of commands matters.
const rand = function*() { while (1 < 2) { yield Math.random() } }
const randIter = rand() randIter.next() // , .
An object with a function that "combines" an object with another object of the same type. A simple example of a monoid is the addition of numbers:
1 + 1 // 2
In this case, the number is an object, and +
is a function.
There must be a neutral element (identity), so that combining the value with it does not change the value. In the case of addition, such an element is 0
.
1 + 0 // 1
It is also necessary that the grouping of operations does not affect the result (associativity):
1 + (2 + 3) === (1 + 2) + 3 // true
Concatenation of arrays is also a monoid:
;[1, 2].concat([3, 4]) // [1, 2, 3, 4]
The neutral element is an empty array []
;[1, 2].concat([]) // [1, 2]
If there are functions of a neutral element and composition, then the functions as a whole form a monoid:
const identity = (a) => a const compose = (f, g) => (x) => f(g(x))
foo
is any function with one argument.
compose(foo, identity) ≍ compose(identity, foo) ≍ foo
A monad is an object with the functions of
and chain
. chain
is similar to map
, but it decomposes nested objects as a result.
// 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
also known as return
in other functional languages.chain
also known as flatmap
and bind
in other languages.
An object with extract
and extend
functions.
const CoIdentity = (v) => ({ val: v, extract () { return this.val }, extend (f) { return CoIdentity(f(this)) } })
Extract takes a value from the functor.
CoIdentity(1).extract() // 1
Extend performs a function on the board. The function should return the same type as the comonade.
CoIdentity(1).extend((co) => co.extract() + 1) // CoIdentity(2)
An object with the ap
function. ap
applies a function in an object to a value in another object of the same type.
// Implementation Array.prototype.ap = function (xs) { return this.reduce((acc, f) => acc.concat(xs.map(f)), []) } // Example usage ;[(a) => a + 1].ap([1]) // [2]
This is useful when there are two objects, and you need to apply a binary operation on their contents.
// 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]
As a result, we obtain an array of functions that can be called with ap
to get the result:
partiallyAppliedAdds.ap(arg2) // [5, 6, 7, 8]
Transformation function
A function whose input and output are of the same type.
// uppercase :: String -> String const uppercase = (str) => str.toUpperCase() // decrement :: Number -> Number const decrement = (x) => x - 1
A pair of structural transformations between two types of objects without data loss.
For example, two-dimensional coordinates can be stored in array [2,3]
or object {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}
An object that has an equals
function that can be used to compare objects of the same type.
Make an array a setoid:
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
An object with a concat
function that combines it with another object of the same type.
;[1].concat([2]) // [1, 2]
An object in which there is a reduce
function that transforms an object into another type.
const sum = (list) => list.reduce((acc, val) => acc + val, 0) sum([1, 2, 3]) // 6
Often functions in JavaScript contain comments indicating the types of their arguments and return values. There are different approaches in the community, but they are all similar:
// functionName :: firstArgType -> secondArgType -> returnType // add :: Number -> Number -> Number const add = (x) => (y) => x + y // increment :: Number -> Number const increment = (x) => x + 1
If the function takes another function as an argument, then it is placed in parentheses.
// call :: (a -> b) -> a -> b const call = (f) => (x) => f(x)
The symbols a
, b
, c
, d
show that the arguments can be of any type. The next version of the map
function accepts:
a
to another type b
a
,and returns an array of values of type b
.
// map :: (a -> b) -> [a] -> [b] const map = (f) => (list) => list.map(f)
Additional materials
The combination of two types in one, a new type.
There are no static types in JavaScript, but let's imagine that we invented the type NumOrString
, which is the addition of String
and Number
.
The +
operation in JavaScript works with strings and numbers, so you can use our new type to describe its input and output:
// add :: (NumOrString, NumOrString) -> NumOrString const add = (a, b) => a + b add(1, 2) // 3 add('Foo', 2) // "Foo2" add('Foo', 'Bar') // "FooBar"
A union type is also known as an algebraic type, a marked union, and a sum-type.
There are a couple of JavaScript libraries to define and use such types.
Type- product combines types in such a way that you are most likely familiar with:
// point :: (Number, Number) -> {x: Number, y: Number} const point = (x, y) => ({x: x, y: y})
It is called the product, because the possible value of the data structure is the product (product) of different values.
See also: set theory .
Type-union with two cases: Some
and None
. Useful for composing functions that may not return values.
// 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])
Use chain
to build a sequence of functions that return 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
also known as Maybe
. Some
sometimes called Just
. None
sometimes called Nothing
.
Source: https://habr.com/ru/post/310172/
All Articles