天天看點

js函數式程式設計術語總結

js函數式程式設計術語總結

高階函數 Higher-Order Functions

以函數為參數的函數,傳回一個函數的函數

const filter = (predicate, xs) => xs.filter(predicate)
const is = (type) => (x) => Object(x) instanceof type
filter(is(Number), [0, '1', 2, null]) // [0, 2]      

函數的元 Arity

函數所需的參數個數。來自于單詞 unary, binary, ternary 等等。這個單詞是由 -ary 與 -ity 兩個字尾組成。

例如,一個帶有兩個參數的函數被稱為二進制函數或者它的 arity 是2。它也被那些更喜歡希臘詞根而非拉丁詞根的人稱為 dyadic。

同樣地,帶有可變數量參數的函數被稱為 variadic,而二進制函數必須給出兩個且隻有兩個參數,見下文,柯裡化(Currying) 和 偏應用函數(Partial Application) 。

const sum = (a, b) => a + b
const arity = sum.length
console.log(arity) // 2
// The arity of sum is 2      

惰性求值 Lazy evaluation

是一種按需求值機制,它會延遲對表達式的求值,直到其需要為止

// 設定一個随機數,需要時,才會計算,每次計算都是一個不同的值
const rand = function*() {
  while (1 < 2) {
    yield Math.random()
  }
}
const randIter = rand()
randIter.next() // 每個執行都給出一個随機值,表達式按需求值。      

偏函數 Partial Application

即【降元】,将一個 n 元函數轉換成一個 n - x 元函數或者這樣了解,通過對【複雜的函數】填充一部分資料來構成一個【簡單的函數】柯裡化就是通過偏應用函數來實作。

function add(a, b,c) {
    return a + b+c;
}
//也可以
var addOne = add.bind(null, 1,2);
console.log(addOne(2));
//也可以
var addTwo = add.bind(null, 1);
console.log(addTwo(3,4));      

柯裡化 Currying

将一個多參數函數轉換成多個單參數函數,也就是将一個 n 元函數轉換成 n 個一進制函數。

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

将多個參數的函數轉換為單參數的函數,如果,給定的參數數量少于正确的參數,則傳回一個函數,該函數将獲得其餘的參數,如果,函數得到正确數量的參數時,它就會被求值示例,lodash 和 Ramda 都有一個 curry 函數,但 underscore 沒有。

const add = (x, y) => x + y
const curriedAdd = _.curry(add)
curriedAdd(1, 2) // 3
curriedAdd(1) // (y) => 1 + y
curriedAdd(1)(2) // 3      

compose 組合函數

概念:它将需要嵌套執行的函數平鋪。嵌套執行指的是,一個函數的傳回值将作為另一個函數的參數

作用:實作函數式程式設計中的 pointfree 風格(無參數),使我們專注于【轉換】而不是【資料】

實作:接收多個函數作為參數,從右到左,一個函數的輸入為另一個函數的輸出

意義:程式設計更精練、算法更清晰、無參數幹擾

威力:==【任意組合】==

缺點:不能直覺的看到參數

示例

var compose = function(fun1,fun2){
    return function(val){
        return fun1(fun2(val));
    }
}


var add = function(val){
  return val + "111";
}


var upperCase = function(val){
  return val.toUpperCase();
}


var double = function(val){
  return val += val;
}
// 無限組合,才是compose的威力所在
var upperCaseThenAdd = compose(add,upperCase);
var doubleThenAdd = compose(double,add);
var addThenAdd = compose(add,add);
var addThenAddThenUpperCase = compose(upperCase,addThenAdd);//注意這個函數,以組合函數addThenAdd作為其參數,很強大,有沒有!


console.log(upperCaseThenAdd("china"));//CHINA111
console.log(doubleThenAdd("china"));//china111china111
console.log(addThenAdd("china"));//china111111
console.log(addThenAddThenUpperCase("china"));//CHINA111111
//改進compose,支援2個以上參數
var compose1 = function(){
  var args = arguments;
  return function(initVal){
    var val = initVal;
    for(key in args){
      val = args[key](val);
    }
    return val;
  }
  
}
var doubleThenUpperCaseThenAddThenAdd = compose1(double,upperCase,add,add);
console.log(doubleThenUpperCaseThenAddThenAdd("china"));//CHINACHINA111111      

Continuation

概念:在一個程式執行的任意時刻,尚未執行的代碼稱為 Continuation

作用:異步請求回調、異步監聽回調等

示例

//continueFun函數就稱為一個Continuation
var addOneAndContinue = function(val,continueFun){
  var val = val + 1;
  return continueFun(val);
}
var mutiply = function(val){
  return val * 5;
}
console.log(addOneAndContinue(100,mutiply));//505      

純函數 Purity

如果傳回值僅由其輸入值決定,并且不産生副作用,那麼這個函數就是純函數。

const greet = (name) => `Hi, ${name}`
greet('Brianne') // 'Hi, Brianne'      

以下代碼不是純函數:

window.name = 'Brianne'
const greet = () => `Hi, ${window.name}`
greet() // "Hi, Brianne"      

上述示例的輸出基于存儲在函數外部的資料…

let greeting


const greet = (name) => {
  greeting = `Hi, ${name}`
}


greet('Brianne')
greeting // "Hi, Brianne"      

… 而這個示例則是修改了函數外部的狀态。

副作用 Side effects

如果函數與外部可變狀态進行互動,則它是有副作用的.函數或表達式如果被認為具有副作用,那麼除了傳回值之外,它可以與外部可變狀态(讀取或寫入)進行互動。

const differentEveryTime = new Date()
console.log('IO is a side effect!')      

幂等性 Idempotent

數學中的幂等性

foo(x) 将産生與 foo(foo(x))、foo(foo(foo(x))) 等相同的輸出

[二進制運算],它需要三個元素:二進制運算符以及該運算符作用的兩個變量。如四則運算的加、減、乘、除均屬于二進制運算。乘法下唯一兩個幂等實數為0和1

[一進制運算],例如 ++ ,正+,負-。比如[高斯符号],它是一個數學符号,形式為方括号[x],表示不大于x的最大整數,高斯符号是幂等的

接口的幂等性

對接口而言,幂等性實際上就是接口可重複調用,在調用方多次調用的情況下,接口最終得到的結果是一緻的。

比如,在App中下訂單的時候,點選确認之後,沒反應,就又點選了幾次。在這種情況下,如果無法保證該接口的幂等性,那麼将會出現重複下單問題

[http方法的幂等],指的是同樣的請求被執行一次與連續執行多次的效果是一樣的,伺服器的狀态也是一樣的(注意,隻是伺服器狀态,和伺服器傳回狀态無關)

舉例

GET /pageX HTTP/1.1是幂等的。連續調用多次,用戶端接收到的結果都是一樣的:
GET /pageX HTTP/1.1   
GET /pageX HTTP/1.1   
GET /pageX HTTP/1.1   
GET /pageX HTTP/1.1


POST /add_row HTTP/1.1不是幂等的。如果調用多次,就會增加多行記錄:
POST /add_row HTTP/1.1
POST /add_row HTTP/1.1   -> Adds a 2nd row
POST /add_row HTTP/1.1   -> Adds a 3rd row


DELETE /idX/delete HTTP/1.1是幂等的,即便是不同請求之間接收到的狀态碼不一樣:
DELETE /idX/delete HTTP/1.1   -> Returns 200 if idX exists
DELETE /idX/delete HTTP/1.1   -> Returns 404 as it just got deleted
DELETE /idX/delete HTTP/1.1   -> Returns 404      

程式的幂等性

概念:一個函數執行多次皆傳回相同的結果

作用:一個函數被調用多次時,保證内部狀态的一緻性

對比:和純函數相比,幂等主要強調多次調用,對内部的狀态的影響是一樣的(但多次調用傳回值可能不同)。而純函數,主要強調相同的輸入,多次調用,輸出也相同且無副作用。==純函數一定是幂等的==

意義:在任何可能的情況下通過幂等的操作限制副作用要比不做限制的更新要好得多。確定操作是幂等的,可避免意外的發生

//雖然是一個面向對象的例子,但是可以說明問題
var Student = function(name,age){
  this.name = name;
  this.age = age;
};
Student.prototype.delName = function(){
  var response = this.name ? this.name + "已被删除":"name不存在";
  this.name = null;
  return response;
}
//對内部的影響是一樣的,但是傳回值可以不同
var lilei = new Student("lilei",19);
console.log(lilei.delName());//lilei已被删除
console.log(lilei.delName());//name不存在
console.log(lilei.delName());//name不存在      

Point-Free 風格

定義函數時,不顯式地指出函數所帶參數。這種風格通常需要柯裡化或者高階函數。也叫 Tacit programming

// 給定
const map = (fn) => (list) => list.map(fn)
const add = (a) => (b) => a + b
// 然後
// 非 points-free - `numbers` 是一個明确的參數
const incrementAll = (numbers) => map(add(1))(numbers)
// Points-free - `list` 顯式地辨別
const incrementAll2 = map(add(1))      

incrementAll 明确的使用了參數 numbers,是以它是非 points-free 風格。incrementAll2 連接配接函數與值,并不提及它的參數。是以 是 points-free 風格. Point-Free 風格的函數就像平常的指派,不使用 function 或者 =>。

斷言函數 Predicate

根據輸入傳回 true 或 false。通常用在 Array.prototype.filter 的回調函數中。

const morethenTwo = (a) => a > 2;
;[1, 2, 3, 4].filter(morethenTwo);      

契約 Contracts

契約保證了函數或者表達式在運作時的行為。當違反契約時,将抛出一個錯誤

比如資料類型檢測

const contract = (input) => {
  if (typeof input === 'number') return true
  throw new Error('Contract Violated: expected int -> int')
}


const addOne = (num) => contract(num) && num + 1


addOne(2)
addOne('hello') // Error      

範疇 Category

【不好了解】

範疇是指,對象(object)及它們之間的态射(箭頭,箭頭可以組合)

在程式中,資料類型作為對象,函數作為态射

【一個範疇遵從三個原則】

必有一個态射(函數),使得 map 一個對象是它自身

态射(函數)必是可組合的

合成滿足結合律。f ? (g ? h) 與 (f ? g) ? h 是等價的

态射 morphism

一個變形的函數。  某一範疇中,對象之前的變換關系(一個變形的函數)

函子 functor(範疇學的内容)

一個實作 map 函數的對象

在 JavaScript 中一個常見的函子是 Array,因為它遵守因子的兩個準則

一緻性 Preserves identity,即範疇的第一個原則

組合性 Composable

示例

//一緻性
object.map(x => x) ? object
//組合性
var fun1 = function(x){
  return x+1;
}
var fun2 = function(x){
  return x*x;
}
var res1 = [1,2,3].map(fun1).map(fun2);
var res2 = [1,2,3].map(function(x){
  return fun2(fun1(x));
});
console.log(res1,res2);      

Pointed Functor

一個具有 of 函數的對象,它将 任何 單獨的值放入其中

ES6增加了 Array.of ,使數組成為一個 Pointed Functor

Array.of(1) // [1]      

引用透明性 Referential Transparency

定義:一個表達式在程式中可以被它等價的值替換,而不影響結果

對函數而言:如果函數的傳回值隻依賴于其輸入值,這種特性就稱為引用透明性

==純函數具有引用透明性==

等式推理 Equational Reasoning

指當應用程式由表達式組成,并且沒有副作用時,關于系統的真值可以從各個部分推導出來。

純函數式語言的優點之一是易于進行等式推理,通過引用透明度實作,并且能夠在所有上下文中用等号替換equals。

不可變性

比如es6中的 const 常量設計

Object.freeze({name: 'John', age: 30}) // `freeze` 強制實作不可變性。


const name="haha"      

匿名函數 Lambda

一個匿名函數,被當作一個值來對待。

;(function (a) {
  return a + 1
})


;(a) => a + 1      

匿名函數通常作為高階函數的參數

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

可以把 Lambda 指派給一個變量

const add1 = (a) => a + 1      

Monad 對象

擁有 of 和 chain 函數的對象。chain 很像 map, 除了用來鋪平嵌套資料

示例,以數組來實作

​​//of​​​​Array.of(1,2,3);//[ 1, 2, 3 ]​​​​//chain方法的實作​​​​Array.prototype.chain = function (f) {​​​​return this.reduce((acc, it) =>​​​​};​​​​Array.of('cat,dog', 'fish,bird').chain(s s.split(','));//[ "cat", "dog", "fish", "bird" ]​​      

Comonad 對象

擁有 extract 與 extend 函數的對象。

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

從函子中 extract (提取) 出一個值。

CoIdentity(1).extract() // 1      

Extend 在 comonad 上運作一個函數。函數應該傳回與 comonad 相同的類型。

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

自同态 Endomorphism

輸入輸出是相同類型的函數

示例:

// uppercase :: String -> String
const uppercase = (str) => str.toUpperCase()
// decrement :: Number -> Number
const decrement = (x) => x - 1      

Applicative Functor

應用函子是具有ap函數的對象。ap将對象中的函數應用于同一類型的另一個對象中的值。

// 實作
Array.prototype.ap = function (xs) {
  return this.reduce((acc, f) => acc.concat(xs.map(f)), [])
}
// 使用示例
;[(a) => a + 1].ap([1]) // [2]      

如果你有兩個對象,并需要對他們的元素執行一個二進制函數

// 你想合成的數組
const arg1 = [1, 3]
const arg2 = [4, 5]
// 合成函數 - 必須為此 curried 柯裡化 才能工作
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]      

同構 Isomorphism

不用類型對象的變形,保持結構并且不丢失資料

例如,一個二維坐标既可以表示為數組 [2, 3],也可以表示為對象 {x: 2, y: 3}

// 提供函數在兩種類型間互相轉換
const pairToCoords = (pair) => ({x: pair[0], y: pair[1]})
const coordsToPair = (coords) => [coords.x, coords.y]


console.log(pairToCoords([1, 2]));//{ "x": 1, "y": 2 }
console.log(coordsToPair({x: 1, y: 2}));//[ 1, 2 ]      

Setoid 對象

定義:擁有 equals 函數的對象。equals 可以用來和其它對象比較。

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([3, 4])   // false      

半群 Semigroup

定義:一個擁有 concat 函數的對象。concat 可以連接配接相同類型的兩個對象

示例:比如 Array具有concat方法

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

Foldable 對象

定義:一個擁有 reduce 函數的對象,reduce 可以把一種類型的對象轉化為另一種類型

示例:将一個list轉為number

var sum = [1,2,3,4].reduce(function(total,val){
  return total += val;
})
console.log(sum);      

類型簽名 Type Signatures

一種注釋方式

//通常 js 會在注釋中指出參數與傳回值的類型
// 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 的參數 f,把一種類型 a 的數組轉化為另一種類型 b 的數組。
// map :: (a -> b) -> [a] -> [b]
const map = (f) => (list) => list.map(f)      

代數資料類型 Algebraic data type

由其他類型組合在一起的複合類型。兩種常見的代數類型是 sum 和 product

聯合類型(對象) Union Type

定義:連接配接不同的資料類型

示例:add就是一個聯合類型對象,因為js天然支援number和sting求和時,進行自動資料類型轉換

// add :: (NumOrString, NumOrString) -> NumOrString
const add = (a, b) => a + b
add(1, 2) // Returns number 3
add('Foo', 2) // Returns string "Foo2"
add('Foo', 'Bar') // Returns string "FooBar"      

Product type

定義:用一種你可能更熟悉的方式把資料類型聯合起來

// point :: (Number, Number) -> {x: Number, y: Number}
const point = (x, y) => ({x: x, y: y})      

Sum 類型(有時稱為聯合類型 )

是将兩種類型的組合合并成另一種類型

之是以被稱為 sum ,是因為結果類型中可能的值的數量是輸入類型的總和

JavaScript 沒有這樣的類型,但是我們可以使用 Set 來假裝

// 想象一下,在這裡我們不能設定隻能具有這些值的類型
const bools = new Set([true, false])
const halfTrue = new Set(['half-true'])
// 弱邏輯類型包含 bools 和 halfTrue 值的總和
const weakLogicValues = new Set([...bools, ...halfTrue])      

Option | maybe

Option 是一種sum type ,它有兩種情況,Some 或者 None。

Option 對于組合可能不傳回值的函數很有用。

在其它的一些地方,Option 也稱為 Maybe,Some 也稱為 Just,None 也稱為 Nothing

閉包 Closure

閉包是通路其作用域之外的變量的一種方法。正式一點的解釋,閉包是一種用于實作詞法作用域的命名綁定的技術。它是一種用環境存儲函數的方法。

閉包是一個作用域,在這個作用域能夠捕獲通路函數的局部變量,即使執行已經從定義它的塊中移出。即,它們允許在聲明變量的塊執行完成之後保持對作用域的引用。

const addTo = x => y => x + y
var addToFive = addTo(5)
addToFive(3) // returns 8      

函數 addTo() 傳回一個函數(内部調用 add() ),将它存儲在一個名為 addToFive 的變量中,并柯裡化(Curried)調用,參數為 5 。

通常,當函數 addTo 完成執行時,其作用域與局部變量 add,x,y 不可通路。但是,它在調用 addToFive() 時傳回 8。

這意味着即使代碼塊執行完成後,函數 addTo 的狀态也被儲存,否則無法知道 addTo 被調用為 addTo(5),x 的值設定為 5。

詞法作用域是能夠找到 x 和 add 的值的原因 – 已完成執行的父級私有變量。這就稱為 Closure(閉包) 。

堆棧伴随着函數的詞法作用域存儲在父對象的引用形式中。這樣可以防止閉包和底層變量被當做垃圾回收(因為至少有一個實時引用)。

Lambda Vs Closure(閉包) :lambda 本質上是一個内聯定義的函數,而不是聲明函數的标準方法。lambda 經常可以作為對象傳遞。

閉合是一個函數,通過引用其函數體外部的字段來保持對外部變量的引用。

本文完~