函數語言程式設計術語
高階函式 Higher-Order Functions
- 以函式為引數的函式
- 返回一個函式的函式
函式的元 Arity
- 比如,一個帶有兩個引數的函式被稱為二元函式
惰性求值 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
- 輸出僅由輸入決定,不依賴也不修改外部狀態,即不產生副作用
副作用 Side effects
- 如果函式與外部可變狀態進行互動,則它是有副作用的
冪等性 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
斷言函式 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 常量設計
匿名函式 Lambda
- 匿名函式往往被視作一個值
- 匿名函式通常作為高階函式的引數
- 可以把 Lambda 賦值給一個變數
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) => acc.concat(f(it)), [])
};
Array.of('cat,dog', 'fish,bird').chain(s => s.split(','));//[ "cat", "dog", "fish", "bird" ]
複製程式碼
Comonad 物件
- 擁有 extract 與 extend 函式的物件
自同態 Endomorphism
- 輸入輸出是相同型別的函式
- 示例:
// uppercase :: String -> String
const uppercase = (str) => str.toUpperCase()
// decrement :: Number -> Number
const decrement = (x) => x - 1
複製程式碼
Applicative Functor
- 一個擁有 ap 函式的物件
同構 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方法
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