如何用JavaScript實現一個數組惰性求值庫
在編程語言理論中,惰性求值(英語:Lazy Evaluation),又譯為惰性計算、懶惰求值,也稱為傳需求調用(call-by-need),是一個計算機編程中的一個概念,它的目的是要最小化計算機要做的工作。它有兩個相關而又有區別的含意,可以表示為“延遲求值”和“最小化求值”,除可以得到性能的提升外,惰性計算的最重要的好處是它可以構造一個無限的數據類型。
看到函數式語言里面的惰性求值,想自己用JavaScript寫一個最簡實現,加深對惰性求值了解。用了兩種方法,都不到 80 行實現了基本的數組的惰性求值。
怎么實現惰性求值每次求值的時候并不是返回數值,而是返回一個包含計算參數的求值函數,每次到了要使用值得時候,才會進行計算。
當有多個惰性操作的時候,構成一個求值函數鏈,每次求值的時候,每個求值函數都向上一個求值函數求值,返回一個值。最后當計算函數終止的時候,返回一個終止值。
每次求值函數都會返回各種數據,所以得使用一個獨一無二的值來作為判斷流是否完成的標志。剛好 Symbol() 可以創建一個新的 symbol ,它的值與其它任何值皆不相等。
const over = Symbol();const isOver = function (_over) { return _over === over;}生成函數 range
range 函數接受一個起始和終止參數,返回一個求值函數,運行求值函數返回一個值,終止的時候返回終止值。
const range = function (from, to) { let i = from; return function () { if (i < to) { i++ console.log(’ranget’, i); return i } return over; }}轉換函數 map
接受一個求值函數和處理函數,獲取求值函數 flow 中的數據,對數據進行處理,返回一個流。
const map = function (flow, transform) { return function () { const data = flow(); console.log(’mapt’, data); return isOver(data) ? data : transform(data); }}過濾函數 filter
接受一個求值函數,對求值函數 flow 中數據進行過濾,找到符合的數據并且返回。
const filter = function (flow, condition) { return function () { while(true) { const data = flow(); if (isOver(data)) {return data; } if(condition(data)) {console.log(’filtert’, data);return data; } } }}中斷函數 stop
接受一個求值函數,當達到某個條件時中斷,可以用閉包函數加上 stop 函數接著實現一個 take 函數。
const stop = function (flow, condition) { let _stop = false; return function () { if (_stop) return over; const data = flow(); if (isOver(data)) { return data; } _stop = condition(data); return data; }}const take = function(flow, num) { let i = 0; return stop(flow, (data) => { return ++i >= num; });}收集函數 join
因為返回的都是一個函數,最后得使用一個 join 函數來收集所有的值并且返回一個數組。
const join = function (flow) { const array = []; while(true) { const data = flow(); if (isOver(data)) { break; } array.push(data); } return array;}測試:
const nums = join(take(filter(map(range(0, 20), n => n * 10), n => n % 3 === 0), 2));console.log(nums);
輸出:
range 1
map 1
range 2
map 2
range 3
map 3
filter 30
range 4
map 4
range 5
map 5
range 6
map 6
filter 60
更優雅的實現上面使用 函數 + 閉包 實現了惰性求值,但是還是不夠優雅,絕大部分代碼都放到迭代和判斷求值是否完成上面去了。其實 es6 中還有更好方法來實現惰性求值,就是使用 generator,generator 已經幫我們解決了迭代和判斷流是否完成,我們就可以專注于邏輯,寫出更簡潔易懂結構清晰的代碼。
const range = function* (from, to) { for(let i = from; i < to; i++) { console.log(’ranget’, i); yield i; }}const map = function* (flow, transform) { for(const data of flow) { console.log(’mapt’, data); yield(transform(data)); }}const filter = function* (flow, condition) { for(const data of flow) { console.log(’filtert’, data); if (condition(data)) { yield data; } }}const stop = function*(flow, condition) { for(const data of flow) { yield data; if (condition(data)) { break; } }}const take = function (flow, number) { let count = 0; const _filter = function (data) { count ++ return count >= number; } return stop(flow, _filter);}
還得加上鏈式調用才算是完成了。
class _Lazy{ constructor() { this.iterator = null; } range(...args) { this.iterator = range(...args); return this; } map(...args) { this.iterator = map(this.iterator, ...args); return this; } filter(...args) { this.iterator = filter(this.iterator, ...args); return this; } take(...args) { this.iterator = take(this.iterator, ...args); return this; } [Symbol.iterator]() { return this.iterator; }}function lazy () { return new _Lazy();}
最后再測試一下:
const nums = lazy().range(0, 100).map(n => n * 10).filter(n => n % 3 === 0).take(2);for(let n of nums) { console.log(’num:t’, n, ’n’);}
輸出:
range 0
map 0
filter 0
num: 0
range 1
map 1
filter 10
range 2
map 2
filter 20
range 3
map 3
filter 30
num: 30
好了,大功告成。
總結這樣我們就完成了一個最簡的數組惰性求值的庫,這里只是簡單實現了惰性求值,要放到工程中還需要添加很多細節。因為代碼不過 80 行,可以很清楚的了解惰性求值原理,還能加深對生成器的理解。
以上就是如何用JavaScript實現一個數組惰性求值庫的詳細內容,更多關于JavaScript實現數組惰性求值庫的資料請關注好吧啦網其它相關文章!
相關文章: