函數式編程4-高階函數

以其餘函數做爲參數的函數

本章的全部代碼,均在github.com/antgod/func…git

關於傳遞函數的思考

  • max
    在不少編程語言的核心庫,都包含一個叫作max的函數。包括underscore也有這樣的函數。
const max = (data) => {
  return data.reduce((maxer, next) => {
    return maxer > next ? maxer : next
  })
}

console.log(max([5, 1, 3, 4, 2])) // 輸出5複製代碼

執行結果並無什麼奇怪,可是這樣特定的函數存在一個限制,若是咱們不是從數字而是從對象中尋找最大值,該怎麼辦?github

const max = (data, compare = item => item) => {
  return data.reduce((maxer, next) => {
    return compare(maxer) > compare(next) ? maxer : next
  })
}

console.log(max([{ age: 64 }, { age: 32 }, { age: 50 }], item => item.age))複製代碼

可是,在某些方面,這個函數仍然受限,並非真正的函數式,讀者想一想看,爲何呢?算法

這個函數的大於號,是定死的。編程

咱們能夠構建一個新的函數,一個用來生成可比較的值,另外一個用來比較兩個值返回最佳值。設計模式

const finder = (data, need, compare) => {
  return data.reduce((last, next) => {
    return compare(last) === need(compare(last), compare(next)) ? last : next
  })
}

const identity = prop => prop

console.log(finder([1, 2, 3, 4, 5], Math.max, identity))複製代碼

當咱們要比較對象大小時候,就能夠傳遞第三個參數了。數組

const finder = (data, need, compare) => {
  return data.reduce((last, next) => {
    return compare(last) === need(compare(last), compare(next)) ? last : next
  })
}

const plucker = prop => item => item[prop]

console.log(finder([{ age: 64 }, { age: 32 }, { age: 50 }], Math.max, plucker('age')))複製代碼

當咱們要查找以B開頭的名字,怎麼辦呢?安全

const finder = (data, need, compare) => {
  return data.reduce((last, next) => {
    return compare(last) === need(compare(last), compare(next)) ? last : next
  })
}

const plucker = prop => item => item[prop]

console.log(finder([{ name: 'A', age: 64 }, { name: 'B', age: 32 }, { name: 'C', age: 50 }], (x, y) => {
  return x.charAt(0) === 'B' ? x : y
}, plucker('name')))複製代碼

發現問題

咱們發現,函數雖然短小精幹,而且也按照了咱們的預期工做。可是卻有一些重複性代碼。閉包

// in finder
return compare(last) === need(compare(last), compare(next)) ? last : next

// in used
return x.charAt(0) === 'B' ? x : y複製代碼

你會發現,這兩段邏輯徹底相同,也就是說,這兩種算法都是返回最佳值或者當前值。dom

咱們徹底能夠按照如下思路縮減代碼:編程語言

  • 若是第一個參數比第二個參數更好,返回第一個參數。
const bester = (data, need) => {
  return data.reduce((last, next) => {
    return need(last, next) ? last : next
  })
}

console.log(bester([{ name: 'A', age: 64 }, { name: 'B', age: 32 }, { name: 'C', age: 50 }], (x, y) => {
  return x.age < y.age
}))複製代碼

關於傳遞函數的思考:重複、與條件

上一章中,咱們建立了接受兩個函數參數的finder,而且簡化成一個函數參數的bester。事實上,在大多數js函數設計中,都不須要返回多個函數。

可是某些狀況下,咱們須要返回多個函數。讓咱們以repeat開始,介紹爲何要使用多個函數爲參數。

const range = (times) => {
  const ranges = []
  for (let idx = 0; idx < times; idx++) {
    ranges.push(null)
  }
  return ranges
}

const repeat = (value, time) => {
  return range(time).map(() => value)
}

console.log(repeat(4, 3))複製代碼

range函數對underscore的range作了下刪減,返回一個包含n個值爲null的數組。

使用函數,而不是值

做爲repeat的常規實現方式,咱們仍然有提升的空間。若是將重複值運算,那樣使用場景更普遍。

好比咱們要隨機生成10個10之內的數字(range函數與前文相同)。

const repeatness = (createValue, time) => {
  return range(time).map((value, index) => createValue(index))
}


console.log(repeatness(() => Math.floor(Math.random() * 10) + 1, 10))複製代碼
一個函數是不夠的,須要多參函數

有的時候,咱們不知道函數須要調用多少次,咱們只有一個條件。好比說,當咱們不斷重複調用一個函數,當函數超過某個閾值或者條件,咱們中止調用。使用repeatness明顯達不到咱們的需求。

好比,咱們想計算1024如下全部的2的整數指數值(2, 4, 8, 16, 32, 64, 128, 256, 512)。

const iterate = (createValue, checker, init) => {
  const ret = []
  let result = createValue(init)
  while (checker(result)) {
    ret.push(result)
    result = createValue(result)
  }
  return ret
}

console.log(iterate(n => n + n, n => n < 1024, 1))複製代碼

函數接收兩個函數參數,一個用來執行動做,一個用來校驗結果。當結果知足時返回true。這算是repeatness的升級版了,連重複次數都是開放的,受到一個函數的執行結果影響。

返回其餘函數的函數

回憶下以前repeatness返回三個常量的狀況

repeatness(() => 'Odelay', 3)複製代碼

這種返回常量的函數很是有用,是函數式編程的一種設計模式,經過函數來返回值。咱們常常稱之爲k,爲了清晰起見,咱們稱之爲alwasy。

const always = value => () => value
console.log(repeatness(always('Odelay'), 3))複製代碼

always的行爲能夠用來解釋閉包。閉包用來捕獲一個值,並屢次返回相同的值。每個新的閉包都會返回不同的值

const f = always(() => {})

console.log(f() === f())
// => true
const g = always(() => {})

console.log(g() === f())
// => false複製代碼

像always這樣的函數被稱爲組合子。

高階函數捕獲參數

高階函數的參數用來配置返回函數的行爲。
注意觀察如下代碼:

const makeAdder = init => rest => init + rest
const add100 = makeAdder(100)
console.log(add100(38))
// => 138複製代碼

你會常常看到一個函數返回了一個捕獲變量的函數。

捕獲變量的好處

好比你要生成一個特定前綴的隨機字符串。

const uniqueString = prefix => [prefix, new Date().getTime()].join('')

console.log(uniqueString('ghosts'))
console.log(uniqueString('turkey'))複製代碼

看起來不錯,可是若是咱們須要自增的索引,而不是時間戳做爲後綴,怎麼辦?

可使用閉包來實現:

const generator = (init, prefix) => {
  let counter = init
  return (pre = prefix) => {
    return [pre, counter++].join('')
  }
}

const g1 = generator(0, 'prefix')

console.log(g1())
console.log(g1())
console.log(g1('new'))
/*
=>
prefix0
prefix1
new2
*/複製代碼

咱們也能夠用對象來實現:

const generator2 =  (init, prefix) => {
  return {
    count: init,
    uniqueString: function(pre = prefix) {
      return [pre, this.count++].join('')
    },
  }
}

const g2 = generator2(0, 'prefix')

console.log(g2.uniqueString.call(g2))
console.log(g2.uniqueString.call(g2))
console.log(g2.uniqueString('new'))
/*
=>
prefix0
prefix1
new2
*/複製代碼

注意這裏使用了this訪問數據,那麼函數不能再使用箭頭函數。對象的缺點是不安全,由於能夠隨意訪問 count的值。不少時候隱藏實現細節是很重要的。事實上,咱們能夠把count像閉包同樣隱藏在函數內部。

const generator3 = (init, prefix) => {
  let counter = init
  return {
    uniqueString: (pre = prefix) => {
      return [pre, counter++].join('')
    },
  }
}

const g3 = generator3(0, 'prefix')

console.log(g3.uniqueString())
console.log(g3.uniqueString())
console.log(g3.uniqueString('new'))
/*
=>
prefix0
prefix1
new2
*/複製代碼

閉包的方式乾淨、簡單,可是也充滿了陷阱。

改值時候要當心

雖然對於外界操做來講,該變量是安全的。可是它會增長複雜度,當一個函數的返回值只依賴參數時,被稱爲引用透明。

這個詞看起來很花哨,意味着不破壞代碼結構的狀況下,用預期的值替換函數的任意調用。當你使用會改變內部代碼變量的閉包時,你不必定能作到這一點。由於不少閉包的返回值是依賴於調用次數的。也就是說,調用uniqueString10次與10000次,返回值是不一樣的。

因此咱們在任何地方調用閉包的函數時,都須要格外當心,有必要時,須要增長監控或者日誌。不然在閉包返回值值任意變化時,咱們每每找不到變化緣由。

防止不存在的函數: fnull(grund)

咱們建立幾個高階函數的例子,第一個叫作fnull,咱們先來舉例說明下它的目的。

假設咱們有一組須要乘法的數組:

const nums = [1, 2, 3, null, 5]

console.log(nums.reduce((total, n) => (total * n)))複製代碼

很顯然null不會給咱們任何有用的答案。這時候fnull函數頗有用。

const nums = [1, 2, 3, null, 5]
const fillnull = (handle, ...args) => (...argvs) => handle(...argvs.map((argv, i) => argv || args[i]))

console.log(nums.reduce(fillnull((total, n) => { return total * n }, 1, 1)))複製代碼

函數檢查每一個傳入的參數是不是null或者undefined。若是是,則用默認值替換掉,而後再調用函數。

若是要查找的目標是對象,能夠用如下方式使用:

const defaults = d => (o, k) => {
  const val = fillnull(identity, d[k])
  return o && val(o[k])
}

const ages = [{ age: 100 }, { age: 120 }, { age: 150 }, { }, { age: 30 }]

const lookup = defaults({ age: 0 })

console.log(ages.reduce((total, age) => {
  return total + lookup(age, 'age')
}, 0))複製代碼

其中defaults函數用來配置默認值,返回一個函數lookup,以後每次執行lookup函數,若是遇到空值,都會返回默認值代替。

相關文章
相關標籤/搜索