epogrebnyak / functional-programming-jargon

Jargon from the functional programming world in simple terms!
https://epogrebnyak.github.io/functional-programming-jargon/
MIT License
9 stars 0 forks source link

Функтор и другие классы типов #5

Open epogrebnyak opened 4 years ago

epogrebnyak commented 4 years ago

Functor

An object that implements a map function which, while running over each value in the object to produce a new object, adheres to two rules:

Preserves identity

object.map(x => x) ≍ object

Composable

object.map(compose(f, g)) ≍ object.map(g).map(f)

(f, g are arbitrary functions)

A common functor in JavaScript is Array since it abides to the two functor 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]

Pointed Functor

An object with an of function that puts any single value into it.

ES2015 adds Array.of making arrays a pointed functor.

Array.of(1) // [1]

Lifting

Lifting is when you take a value and put it into an object like a functor. If you lift a function into an Applicative Functor then you can make it work on values that are also in that functor.

Some implementations have a function called lift, or liftA2 to make it easier to run functions on functors.

const liftA2 = (f) => (a, b) => a.map(f).ap(b) // note it's `ap` and not `map`.

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]

Lifting a one-argument function and applying it does the same thing as map.

const increment = (x) => x + 1

lift(increment)([2]) // [3]
;[2].map(increment) // [3]

Monoid

An object with a function that "combines" that object with another of the same type.

One simple monoid is the addition of numbers:

1 + 1 // 2

In this case number is the object and + is the function.

An "identity" value must also exist that when combined with a value doesn't change it.

The identity value for addition is 0.

1 + 0 // 1

It's also required that the grouping of operations will not affect the result (associativity):

1 + (2 + 3) === (1 + 2) + 3 // true

Array concatenation also forms a monoid:

;[1, 2].concat([3, 4]) // [1, 2, 3, 4]

The identity value is empty array []

;[1, 2].concat([]) // [1, 2]

If identity and compose functions are provided, functions themselves form a monoid:

const identity = (a) => a
const compose = (f, g) => (x) => f(g(x))

foo is any function that takes one argument.

compose(foo, identity) ≍ compose(identity, foo) ≍ foo

Monad

A monad is an object with of and chain functions. chain is like map except it un-nests the resulting nested object.

// 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 is also known as return in other functional languages. chain is also known as flatmap and bind in other languages.

Comonad

An object that has extract and extend functions.

const CoIdentity = (v) => ({
  val: v,
  extract () {
    return this.val
  },
  extend (f) {
    return CoIdentity(f(this))
  }
})

Extract takes a value out of a functor.

CoIdentity(1).extract() // 1

Extend runs a function on the comonad. The function should return the same type as the comonad.

CoIdentity(1).extend((co) => co.extract() + 1) // CoIdentity(2)

Applicative Functor

An applicative functor is an object with an ap function. ap applies a function in the 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 if you have two objects and you want to apply a binary function to 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]

This gives you an array of functions that you can call ap on to get the result:

partiallyAppliedAdds.ap(arg2) // [5, 6, 7, 8]

Setoid

An object that has an equals function which can be used to compare other objects of the same type.

Make array a setoid:

Array.prototype.equals = function (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

An object that has a concat function that combines it with another object of the same type.

;[1].concat([2]) // [1, 2]

Foldable

An object that has a reduce function that applies a function against an accumulator and each element in the array (from left to right) to reduce it to a single value.

const sum = (list) => list.reduce((acc, val) => acc + val, 0)
sum([1, 2, 3]) // 6

Lens

A lens is a structure (often an object or function) that pairs a getter and a non-mutating setter for some other data structure.

// Using [Ramda's lens](http://ramdajs.com/docs/#lens)
const nameLens = R.lens(
  // getter for name property on an object
  (obj) => obj.name,
  // setter for name property
  (val, obj) => Object.assign({}, obj, {name: val})
)

Having the pair of get and set for a given data structure enables a few key features.

const person = {name: 'Gertrude Blanch'}

// invoke the getter
R.view(nameLens, person) // 'Gertrude Blanch'

// invoke the setter
R.set(nameLens, 'Shafi Goldwasser', person) // {name: 'Shafi Goldwasser'}

// run a function on the value in the structure
R.over(nameLens, uppercase, person) // {name: 'GERTRUDE BLANCH'}

Lenses are also composable. This allows easy immutable updates to deeply nested data.

// This lens focuses on the first item in a non-empty array
const firstLens = R.lens(
  // get first item in array
  xs => xs[0],
  // non-mutating setter for first item in array
  (val, [__, ...xs]) => [val, ...xs]
)

const people = [{name: 'Gertrude Blanch'}, {name: 'Shafi Goldwasser'}]

// Despite what you may assume, lenses compose left-to-right.
R.over(compose(firstLens, nameLens), uppercase, people) // [{'name': 'GERTRUDE BLANCH'}, {'name': 'Shafi Goldwasser'}]

Other implementations: