基於JavaScript的一些函數式編程概念講解

原文地址
譯者的Github 系列文章地址
本文原做者還沒有所有完成,有興趣的能夠到原文或者譯文地址關注更新javascript

Functional Programming Jargon:函數式編程術語解釋

本文的主要目的便是但願可以有一種通俗易懂的方式來闡述函數式編程中常見的理論術語概念java

Arity:參數數目

Arity代指一個函數的參數數量,該關鍵字來源於相似於unary、binary、ternary等等,由兩個後綴-ary-ity組成。譬如,若是一個函數容許輸入兩個參數,那就稱爲所謂的binary function(二元函數),或者一個有兩個參數的函數。有時候這種函數也會被喜歡拉丁語法的人稱爲"dyadic(二價的)"函數。以此類推,不定參數的方程也就被稱爲variadic(可變參數函數)git

const sum = (a, b) => a + b;

const arity = sum.length;
console.log(arity);
// => 2
// The arity of sum is 2

Higher-Order Functions (HOF):高等函數

一個接收某個函數做爲參數的函數成爲高等函數,該函數能夠選擇返回一個函數也能夠返回其餘類型github

const filter = (pred, xs) => {
  const result = [];
  for (var idx = 0; idx < xs.length; idx += 1) {
    if (pred(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:局部封裝

將本來一個多參數值的函數封裝爲固定參數數目的函數的過程稱爲Partial Applicationexpress

let sum = (a, b) => a + b;

// partially applying `a` to `40`
let partial = sum.bind(null, 40);

// Invoking it with `b`
partial(2); //=> 42

Currying

將一個N參數值的函數轉化爲N個一元函數的組合,Currying與Partial Application的區別在於Partial Application最終生成的函數容許接收多個值,而Currying生成的函數序列中的每一個函數只容許接收一個參數編程

let sum = (a, b) => a + b;

let curriedSum = (a) => (b) => a + b;

curriedSum(40)(2) // 42.

Composition:組合

感受有點像設計模式裏的Decorator,即可以將兩個指定類型組合轉化爲一個新值的函數設計模式

最多見的組合便是常見的函數組合,容許你將不一樣的函數組合成一個返回單值的函數數組

const compose = (f, g) => a => f(g(a)) // Definition
const floorAndToString = compose((val)=> val.toString(), Math.floor) //Usage
floorAndToString(121.212121) // "121"

Purity:純函數

一個沒有任何反作用,而且返回值只由輸入決定的函數成爲純函數app

let greet = "yo";

greet.toUpperCase(); // YO;

greet // yo;

As opposed to:dom

let numbers = [1, 2, 3];

numbers.splice(0); // [1, 2, 3]

numbers // []

Side effects:反作用

若是一個函數,除了返回值以外,還會修改某些其它狀態,或者與外部函數等有可觀測的交互

console.log("IO is a side effect!");

Idempotency:冪等性

屢次執行下都不會產生反作用的函數被稱爲具備冪等性的函數

f(f(x)) = f(x)

Math.abs(Math.abs(10))


Point-Free Style

那些並無線性定義參數的函數風格被稱爲Point-Free Style,這類型每每須要currying 或者 Higher-Order functions

// Given
let map = fn => list => list.map(fn);
let add = (a, b) => a + b;

// Then

// Not points-free - `numbers` is an explicit parameter
let incrementAll = (numbers) => map(add(1))(numbers);

// Points-free - The list is an implicit parameter
let incrementAll2 = map(add(1));

incrementAll明確規定了參數numbers, 而incrementAll2是對於參數的封裝,並無顯性說明numbers參數,所以它能夠被稱爲Points Free。通常來講,Points-free的函數都不會用常見的function或者=>關鍵字來定義。


Contracts

暫無

Guarded Functions

暫無

Categories:分類

關聯到遵循某些規則的函數的對象,譬如monoid


Value:值

計算中經常使用到的一些複合值(complex)或者簡單值(primitive),包括函數。通常來講,函數式編程中的值都被認爲是不可變值。

5
Object.freeze({name: 'John', age: 30}) // The `freeze` function enforces immutability.
(a) => a

注意,譬如Functor, Monad這樣包含其餘值的結構體自己也是值,這就是說,這些複合值也能夠相互包含。


Constant:常量

對於一個值的不可變引用,不能跟變量相混淆。Variable即指那些可能在任意點唄更改的引用。

const five = 5
const john = {name: 'John', age: 30}

常量通常認爲是透明的,也就是說,它們能夠被值自己代替而不影響最終的計算結果,上面的兩個常量也能夠用下述方式表述:

john.age + five === ({name: 'John', age: 30}).age + (5)

上述表達式會一直返回真。


Functor

Functor即指那些能夠引用map函數的對象,JavaScript中最簡單的函數就是Array

[2,3,4].map( n => n * 2 ); // [4,6,8]

假設func構造爲一個實現了map函數的對象,fg則是任意的函數,只要func遵循如下規則就能夠將func稱爲一個functor:
Let func be an object implementing a map function, and f, g be arbitrary functions, then func is said to be a functor if the map function adheres to the following rules:

func.map(x => x) == func

以及

func.map(x => f(g(x))) == func.map(g).map(f)

咱們將Array稱爲Functor,也是由於它遵循瞭如下規則:

[1, 2, 3].map(x => x); // = [1, 2, 3]

以及

let f = x => x + 1;
let 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方法的Functor,Of會將任何單值轉化爲一個Functor

Pointed Functor在Array中的實現爲:

Array.prototype.of = (v) => [v];
  
  [].of(1) // [1]

Lift

Lift很相似於map,不過它能夠用於多個Functors:

在單值函數下,Map與Lift的做用是一致的:

lift(n => n * 2)([2,3,4]); // [4,6,8]

而Lift能夠容許輸入多個值:

lift((a, b)  => a * b)([1, 2], [3]); // [3, 6]

Referential Transparency:透明引用

一個能夠直接用其值來替換而不會影響到程序表現的表達式稱爲透明引用

譬如咱們有一個叫greet的引用

let greet = () => "Hello World!";

任何對於greet()的調用均可以被Hello World!直接替換,所以能夠將greet稱爲透明引用。

Equational Reasoning

當一個應用由表達式組合而成而且沒有任何反作用的時候,該系統能夠由部分推導而來


Lazy evaluation:懶計算

Lazy evaluation 便是所謂的只有在須要某個值的時候才進行計算的機制。在函數式語言中,這個機制就容許對於那些近乎無限的列表進行操做。

let rand = function*() {
    while(1<2) {
        yield Math.random();
    }
}
let randIter = rand();
randIter.next(); // Each exectuion gives a random value, expression is evaluated on need.

Monoid:獨異點

一個monoid就是與某個恆等值進行組合以後不會影響現有結果的數據類型

一個最簡單的Monoid就是以下所示:

1 + 1; // 2

數據類型是number,函數是+

1 + 0; // 1

恆等式的值是0,將0與任何數相加並不會改變值。有時候,monoid類型進行不一樣的交換操做也不會影響結果:

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

數組鏈接也能夠認爲是一個monoid:

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

恆等值便是空數組: []

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

Monad

一個Monad就是擁有of以及chain函數的對象。 Chain 相似於 map只不過它會扁平化最終求得的嵌套式結果。

['cat,dog','fish,bird'].chain(a => a.split(',')) // ['cat','dog','fish','bird']

//Contrast to map
['cat,dog','fish,bird'].map(a => a.split(',')) // [['cat','dog'], ['fish','bird']]

You may also see of and chain referred to as return and bind (not be confused with the JS keyword/function...) in languages which provide Monad-like constructs as part of their standard library (e.g. Haskell, F#), on Wikipedia and in other literature. It's also important to note that return and bind are not part of the Fantasy Land spec and are mentioned here only for the sake of people interested in learning more about Monads.


Comonad:餘單子

實現了extractextend函數的對象

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

Extract 能夠將值從Functor中吐出來:

CoIdentity(1).extract() // 1

Extend則是會返回一個跟Commonad相同值的函數:

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

Applicative(可適用的) Functor

一個Applicative Functor就是一個實現了ap函數的對象,Ap能夠將某個對象中的某個值轉化爲另外一個對象中的相同類型的值

[(a)=> a + 1].ap([1]) // [2]

Morphism:態射

一個轉化函數


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函數的對象,便可以與其餘對象進行對比判斷是否屬於同一類型,被稱爲Setoid。

下面對於原型的擴充能夠將Array變成Setoid。

Array.prototype.equals = arr => {
    var len = this.length
    if (len != arr.length) {
        return false
    }
    for (var 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,即將另外一個對象轉化爲相同類型的函數,函數的對象稱爲Semigroup。

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

Foldable:可摺疊

實現了reduce函數,便可以將一個對象轉化爲其餘類型的函數,的對象稱爲Foldable對象。

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

Traversable

暫無

Type Signatures:類型簽名

通常來講,函數都會註釋代表它們的參數類型和返回值類型

// functionName :: firstArgType -> secondArgType -> returnType

// add :: Number -> Number -> Number
let add = x => y => x + y

// increment :: Number -> Number
let increment = x => x + 1

若是一個函數接收其餘函數做爲參數,譬如這樣:

// call :: (a -> b) -> a -> b
let call = f => x => f(x)

這裏的a, b, c, d代表參數能夠是任意類型,不過它會將類型a轉化爲另外一個類型b,而對於下面這個map,它的註釋代表了它會輸入一個a類型的列表,而後轉化爲另外一個包含了b類型的列表。

// map :: (a -> b) -> [a] -> [b]
let map = f => list =>  list.map(f)
相關文章
相關標籤/搜索