国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

JavaScript 函數(shù)式編程(二)

thursday / 3526人閱讀

摘要:注意是單一參數(shù)柯里化是由以邏輯學(xué)家命名的,當(dāng)然編程語言也是源自他的名字,雖然柯里化是由和發(fā)明的。辨別類型和它們的含義是一項重要的技能,這項技能可以讓你在函數(shù)式編程的路上走得更遠(yuǎn)。

slide 地址

三、可以,這很函數(shù)式~

3.1.函數(shù)是一等公民! 3.1.1.濫用匿名函數(shù)

其實經(jīng)常寫 JavaScript 的人可能潛移默化地已經(jīng)接受了這個觀念,例如你可以像對待任何其他數(shù)據(jù)類型一樣對待函數(shù)——把它們存在數(shù)組里,當(dāng)作參數(shù)傳遞,賦值給變量.等等。

然而,常常可以看到濫用匿名函數(shù)的現(xiàn)象...

</>復(fù)制代碼

  1. // 太傻了
  2. const getServerStuff = function (callback) {
  3. return ajaxCall(function (json) {
  4. return callback(json)
  5. })
  6. }
  7. // 這才像樣
  8. const getServerStuff = ajaxCall
  9. // 下面來推導(dǎo)一下...
  10. const getServerStuff
  11. === callback => ajaxCall(json => callback(json))
  12. === callback => ajaxCall(callback)
  13. === ajaxCall
  14. // from JS函數(shù)式編程指南

再來看一個例子...

</>復(fù)制代碼

  1. const BlogController = (function () {
  2. const index = function (posts) {
  3. return Views.index(posts)
  4. }
  5. const show = function (post) {
  6. return Views.show(post)
  7. }
  8. const create = function (attrs) {
  9. return Db.create(attrs)
  10. }
  11. const update = function (post, attrs) {
  12. return Db.update(post, attrs)
  13. }
  14. const destroy = function (post) {
  15. return Db.destroy(post)
  16. }
  17. return { index, show, create, update, destroy }
  18. })()
  19. // 以上代碼 99% 都是多余的...
  20. const BlogController = {
  21. index: Views.index,
  22. show: Views.show,
  23. create: Db.create,
  24. update: Db.update,
  25. destroy: Db.destroy,
  26. }
  27. // ...或者直接全部刪掉
  28. // 因為它的作用僅僅就是把視圖(Views)和數(shù)據(jù)庫(Db)打包在一起而已。
  29. // from JS函數(shù)式編程指南
3.1.2.為何鐘愛一等公民?

以上那種多包一層的寫法最大的問題就是,一旦內(nèi)部函數(shù)需要新增或修改參數(shù),那么包裹它的函數(shù)也要改...

</>復(fù)制代碼

  1. // 原始函數(shù)
  2. httpGet("/post/2", function (json) {
  3. return renderPost(json)
  4. })
  5. // 假如需要多傳遞一個 err 參數(shù)
  6. httpGet("/post/2", function (json, err) {
  7. return renderPost(json, err)
  8. })
  9. // renderPost 將會在 httpGet 中調(diào)用,
  10. // 想要多少參數(shù),想怎么改都行
  11. httpGet("/post/2", renderPost)
3.1.3.提高函數(shù)復(fù)用率

除了上面說的避免使用不必要的中間函數(shù)包裹以外,對于函數(shù)參數(shù)的起名也很重要,盡量編寫通用參數(shù)的函數(shù)。

</>復(fù)制代碼

  1. // 只針對當(dāng)前的博客
  2. const validArticles = function (articles) {
  3. return articles.filter(function (article) {
  4. return article !== null && article !== undefined
  5. })
  6. }
  7. // 通用性好太多
  8. const compact = function(xs) {
  9. return xs.filter(function (x) {
  10. return x !== null && x !== undefined
  11. })
  12. }

以上例子說明了在命名的時候,我們特別容易把自己限定在特定的數(shù)據(jù)上(本例中是 articles)。這種現(xiàn)象很常見,也是重復(fù)造輪子的一大原因。

3.1.4.this

在函數(shù)式編程中,其實根本用不到 this...

但這里并不是說要避免使用 this
(江來報道上出了偏差...識得唔識得?)

3.2.柯里化(curry) 3.2.1.柯里化概念

</>復(fù)制代碼

  1. 把接受多個參數(shù)的函數(shù)變換成一系列接受單一參數(shù)(從最初函數(shù)的第一個參數(shù)開始)的函數(shù)的技術(shù)。(注意是單一參數(shù))

</>復(fù)制代碼

  1. import { curry } from "lodash"
  2. const add = (x, y) => x + y
  3. const curriedAdd = curry(add)
  4. const increment = curriedAdd(1)
  5. const addTen = curriedAdd(10)
  6. increment(2) // 3
  7. addTen(2) // 12

</>復(fù)制代碼

  1. 柯里化是由 Christopher Strachey 以邏輯學(xué)家 Haskell Curry 命名的,
    當(dāng)然編程語言 Haskell 也是源自他的名字,
    雖然柯里化是由 Moses Schnfinkel 和 Gottlob Frege 發(fā)明的。
3.2.2.柯里化 VS 偏函數(shù)應(yīng)用(partial application)

</>復(fù)制代碼

  1. In computer science, partial application (or partial function application) refers to the process of fixing a number of arguments to a function, producing another function of smaller arity.

    by wikipedia

偏函數(shù)應(yīng)用簡單來說就是:一個函數(shù),接受一個多參數(shù)的函數(shù)且傳入部分參數(shù)后,返回一個需要更少參數(shù)的新函數(shù)。

柯里化一般和偏函數(shù)應(yīng)用相伴出現(xiàn),但這兩者是不同的概念:

</>復(fù)制代碼

  1. import { curry, partial } from "lodash"
  2. const add = (x, y, z) => x + y + z
  3. const curriedAdd = curry(add) // <- 只接受一個函數(shù)
  4. const addThree = partial(add, 1, 2) // <- 不僅接受函數(shù),還接受至少一個參數(shù)
  5. === curriedAdd(1)(2) // <- 柯里化每次都返回一個單參函數(shù)

簡單來說,一個多參函數(shù)(n-ary),柯里化后就變成了 n * 1-ary,而偏函數(shù)應(yīng)用了 x 個參數(shù)后就變成了 (n-x)-ary

3.2.3.柯里化的實現(xiàn)

雖然從理論上說柯里化應(yīng)該返回的是一系列的單參函數(shù),但在實際的使用過程中為了像偏函數(shù)應(yīng)用那樣方便的調(diào)用,所以這里柯里化后的函數(shù)也能接受多個參數(shù)。

</>復(fù)制代碼

  1. // 實現(xiàn)一個函數(shù) curry 滿足以下調(diào)用、
  2. const f = (a, b, c, d) => { ... }
  3. const curried = curry(f)
  4. curried(a, b, c, d)
  5. curried(a, b, c)(d)
  6. curried(a)(b, c, d)
  7. curried(a, b)(c, d)
  8. curried(a)(b, c)(d)
  9. curried(a)(b)(c, d)
  10. curried(a, b)(c)(d)

很明顯第一反應(yīng)是需要使用遞歸,這樣才能返回一系列的函數(shù)。而遞歸的結(jié)束條件就是接受了原函數(shù)數(shù)量的參數(shù),所以重點(diǎn)就是參數(shù)的傳遞~

</>復(fù)制代碼

  1. // ES5
  2. var curry = function curry (fn, arr) {
  3. arr = arr || []
  4. return function () {
  5. var args = [].slice.call(arguments)
  6. var arg = arr.concat(args)
  7. return arg.length >= fn.length
  8. ? fn.apply(null, arg)
  9. : curry(fn, arg)
  10. }
  11. }
  12. // ES6
  13. const curry = (fn, arr = []) => (...args) => (
  14. arg => arg.length >= fn.length
  15. ? fn(...arg)
  16. : curry(fn, arg)
  17. )([...arr, ...args])
3.2.4.柯里化的意義

寫習(xí)慣了傳統(tǒng)編程語言的人的第一反應(yīng)一般都是,柯里化這玩意兒有啥用咧?

柯里化和偏函數(shù)應(yīng)用的主要意義就是固定一些我們已知的參數(shù),然后返回一個函數(shù)繼續(xù)等待接收那些未知的參數(shù)。

所以常見的使用場景之一就是高級抽象后的代碼復(fù)用。例如首先編寫一個多參數(shù)的通用函數(shù),將其柯里化后,就可以基于偏函數(shù)應(yīng)用將其綁定不同的業(yè)務(wù)代碼。

</>復(fù)制代碼

  1. // 定義通用函數(shù)
  2. const converter = (
  3. toUnit,
  4. factor,
  5. offset = 0,
  6. input
  7. ) => ([
  8. ((offset + input) * factor).toFixed(2),
  9. toUnit,
  10. ].join(" "))
  11. // 分別綁定不同參數(shù)
  12. const milesToKm =
  13. curry(converter)("km", 1.60936, undefined)
  14. const poundsToKg =
  15. curry(converter)("kg", 0.45460, undefined)
  16. const farenheitToCelsius =
  17. curry(converter)("degrees C", 0.5556, -32)
  18. -- from https://stackoverflow.com/a/6861858

你可能會反駁說其實也可以不使用這些花里胡哨的柯里化啊,偏函數(shù)應(yīng)用啊什么的東東,我就鐵頭娃愣頭青地直接懟也能實現(xiàn)以上的邏輯。(這一手皮的嘛,就不談了...)

</>復(fù)制代碼

  1. function converter (ratio, symbol, input) {
  2. return (input * ratio).toFixed(2) + " " + symbol
  3. }
  4. converter(2.2, "lbs", 4)
  5. converter(1.62, "km", 34)
  6. converter(1.98, "US pints", 2.4)
  7. converter(1.75, "imperial pints", 2.4)
  8. -- from https://stackoverflow.com/a/32379766

然而兩者的區(qū)別在于,假如函數(shù) converter 所需的參數(shù)無法同時得到,對柯里化的方式來說沒有影響,因為已經(jīng)用閉包保存住了已知參數(shù)。而后者可能就需要使用變量暫存或其他方法來保證同時得到所有參數(shù)

3.3.函數(shù)組合(compose) 3.3.1.組合的概念

函數(shù)組合就是將兩個或多個函數(shù)結(jié)合起來形成一個新函數(shù)。

就好像將一節(jié)一節(jié)的管道連接起來,原始數(shù)據(jù)經(jīng)過這一節(jié)一節(jié)的管道處理之后得到最終結(jié)果。

說起來很玄乎,其實就是假設(shè)有一個函數(shù) f 和另一個函數(shù) g,還有數(shù)據(jù) x,經(jīng)過計算最終結(jié)果就是 f(g(x))。

在高中數(shù)學(xué)中我們應(yīng)該都學(xué)到過復(fù)合函數(shù)。

</>復(fù)制代碼

  1. 如果 y 是 w 的函數(shù),w 又是 x 的函數(shù),即 y = f(w), w = g(x),那么 y 關(guān)于 x 的函數(shù) y = f[g(x)] 叫做函數(shù) y = f(w) 和 w = g(x) 的復(fù)合函數(shù)。其中 w 是中間變量,x 是自變量,y 是函數(shù)值。

此外在離散數(shù)學(xué)里,應(yīng)該還學(xué)過復(fù)合函數(shù) f(g(h(x))) 可記為 (f ○ g ○ h)(x)。(其實這就是函數(shù)組合)

3.3.2.組合的實現(xiàn)

</>復(fù)制代碼

  1. const add1 = x => x + 1
  2. const mul3 = x => x * 3
  3. const div2 = x => x / 2
  4. div2(mul3(add1(add1(0)))) // 結(jié)果是 3,但這樣寫可讀性太差了
  5. const operate = compose(div2, mul3, add1, add1)
  6. operate(0) // => 相當(dāng)于 div2(mul3(add1(add1(0))))
  7. operate(2) // => 相當(dāng)于 div2(mul3(add1(add1(2))))
  8. // redux 版
  9. const compose = (...fns) => {
  10. if (fns.length === 0) return arg => arg
  11. if (fns.length === 1) return fns[0]
  12. return fns.reduce((a, b) => (...args) => a(b(...args)))
  13. }
  14. // 一行版,支持多參數(shù),但必須至少傳一個函數(shù)
  15. const compose = (...fns) => fns.reduceRight((acc, fn) => (...args) => fn(acc(...args)))
  16. // 一行版,只支持單參數(shù),但支持不傳函數(shù)
  17. const compose = (...fns) => arg => fns.reduceRight((acc, fn) => fn(acc), arg)
3.3.3.Pointfree

起名字是一個很麻煩的事兒,而 Pointfree 風(fēng)格能夠有效減少大量中間變量的命名。

</>復(fù)制代碼

  1. Pointfree 即不使用所要處理的值,只合成運(yùn)算過程。中文可以譯作"無值"風(fēng)格。

    from Pointfree 編程風(fēng)格指南

請看下面的例子。(注意理解函數(shù)是一等公民和函數(shù)組合的概念)

</>復(fù)制代碼

  1. const addOne = x => x + 1
  2. const square = x => x * x

上面是兩個簡單函數(shù) addOnesquare,現(xiàn)在把它們合成一個運(yùn)算。

</>復(fù)制代碼

  1. const addOneThenSquare = compose(square, addOne)
  2. addOneThenSquare(2) // 9

上面代碼中,addOneThenSquare 是一個合成函數(shù)。定義它的時候,根本不需要提到要處理的值,這就是 Pointfree

</>復(fù)制代碼

  1. // 非 Pointfree,因為提到了數(shù)據(jù):word
  2. const snakeCase = function (word) {
  3. return word.toLowerCase().replace(/s+/ig, "_")
  4. }
  5. // Pointfree
  6. const snakeCase = compose(replace(/s+/ig, "_"), toLowerCase)

然而可惜的是,以上很 Pointfree 的代碼會報錯,因為在 JavaScript 中 replacetoLowerCase 函數(shù)是定義在 String 的原型鏈上的...

此外有的庫(如 Underscore、Lodash...)把需要處理的數(shù)據(jù)放到了第一個參數(shù)。

</>復(fù)制代碼

  1. const square = n => n * n;
  2. _.map([4, 8], square) // 第一個參數(shù)是待處理數(shù)據(jù)
  3. R.map(square, [4, 8]) // 一般函數(shù)式庫都將數(shù)據(jù)放在最后

這樣會有一些很不函數(shù)式的問題,即:

1.無法柯里化后偏函數(shù)應(yīng)用

2.無法進(jìn)行函數(shù)組合

3.無法擴(kuò)展 map(reduce 等方法) 到各種其他類型

(詳情參閱參考文獻(xiàn)之《Hey Underscore, You"re Doing It Wrong!》)

3.3.4.函數(shù)組合的意義

</>復(fù)制代碼

  1. 首先讓我們從抽象的層次來思考一下:一個 app 由什么組成?(當(dāng)然是由 a、p、p 三個字母組成的啦

一個應(yīng)用其實就是一個長時間運(yùn)行的進(jìn)程,并將一系列異步的事件轉(zhuǎn)換為對應(yīng)結(jié)果。

一個 start 可以是:

開啟應(yīng)用

DOM 事件(DOMContentLoaded, onClick, onSubmit...)

接收到的 HTTP 請求

返回的 HTTP 響應(yīng)

查詢數(shù)據(jù)庫的結(jié)果

WebSocket 消息

..

一個 end 或者說是 effect 可以是:

渲染或更新 UI

觸發(fā)一個 DOM 事件

創(chuàng)建一個 HTTP 請求

返回一個 HTTP 響應(yīng)

保存數(shù)據(jù)到 DB

發(fā)送 WebSocket 消息

...

那么在 start 和 end 之間的東東,我們可以看做數(shù)據(jù)流的變換(transformations)。這些變換具體的說就是一系列的變換動詞的結(jié)合。

這些動詞描述了這些變換做了些什么(而不是怎么做)如:

filter

slice

map

reduce

concat

zip

fork

flatten

...

當(dāng)然日常編寫的程序中一般不會像之前的例子那樣的簡單,它的數(shù)據(jù)流可能是像下面這樣的...




并且,如果這些變換在編寫時,遵守了基本的函數(shù)式規(guī)則和最佳實踐(純函數(shù),無副作用,引用透明...)。

那么這些變換可以被輕易地重用、改寫、維護(hù)、測試,這也就意味著編寫的應(yīng)用可以很方便地進(jìn)行擴(kuò)展,而這些變換結(jié)合的基礎(chǔ)正是函數(shù)組合

3.4.Hindley-Milner 類型簽名 3.4.1.基本概念

先來看一些例子~

</>復(fù)制代碼

  1. // strLength :: String -> Number
  2. const strLength = s => s.length
  3. // join :: String -> [String] -> String
  4. const join = curry((what, xs) => xs.join(what))
  5. // match :: Regex -> String -> [String]
  6. const match = curry((reg, s) => s.match(reg))
  7. // replace :: Regex -> String -> String -> String
  8. const replace = curry((reg, sub, s) => s.replace(reg, sub))

在 Hindley-Milner 系統(tǒng)中,函數(shù)都寫成類似 a -> b 這個樣子,其中 a 和 b 是任意類型的變量。

</>復(fù)制代碼

  1. 以上例子中的多參函數(shù),可能看起來比較奇怪,為啥沒有括號?

例如對于 match 函數(shù),我們將其柯里化后,完全可以把它的類型簽名這樣分組:

</>復(fù)制代碼

  1. // match :: Regex -> (String -> [String])
  2. const match = curry((reg, s) => s.match(reg))

現(xiàn)在我們可以看出 match 這個函數(shù)首先接受了一個 Regex 作為參數(shù),返回一個從 String[String] 的函數(shù)。

因為柯里化,造成的結(jié)果就是這樣:給 match 函數(shù)一個 Regex 參數(shù)后,得到一個新函數(shù),它能夠接著處理 String 參數(shù)。

假設(shè)我們將第一個參數(shù)傳入 /holiday/ig,那么代碼就變成了這樣:

</>復(fù)制代碼

  1. // match :: Regex -> (String -> [String])
  2. const match = curry((reg, s) => s.match(reg))
  3. // onHoliday :: String -> [String]
  4. const onHoliday = match(/holiday/ig)

可以看出柯里化后每傳一個參數(shù),就會彈出類型簽名最前面的那個類型。所以 onHoliday 就是已經(jīng)有了 Regex 參數(shù)的 match 函數(shù)。

</>復(fù)制代碼

  1. // replace :: Regex -> (String -> (String -> String))
  2. const replace = curry((reg, sub, s) => s.replace(reg, sub))

同樣的思路來看最后一個函數(shù) replace,可以看出為 replace 加上這么多括號未免有些多余。

所以這里的括號是完全可以省略的,如果我們愿意,甚至可以一次性把所有的參數(shù)都傳進(jìn)來。

再來看幾個例子~

</>復(fù)制代碼

  1. // id :: a -> a
  2. const id = x => x
  3. // map :: (a -> b) -> [a] -> [b]
  4. const map = curry((f, xs) => xs.map(f))

這里的 id 函數(shù)接受任意類型的 a 并返回同一個類型的數(shù)據(jù)(話說 map 的簽名里為啥加了括號呢~)。

和普通代碼一樣,我們也可以在類型簽名中使用變量。把變量命名為 a 和 b 只是一種約定俗成的習(xí)慣,你可以使用任何你喜歡的名稱。但對于相同的變量名,其類型一定相同。

這是非常重要的一個原則,所以我們必須重申:a -> b 可以是從任意類型的 a 到任意類型的 b,但是 a -> a 必須是同一個類型。

例如,id 可以是 String -> String,也可以是 Number -> Number,但不能是 String -> Bool。

相似地,map 也使用了變量,只不過這里的 b 可能與 a 類型相同,也可能不相同。

我們可以這么理解:map 接受兩個參數(shù),第一個是從任意類型 a 到任意類型 b 的函數(shù);第二個是一個數(shù)組,元素是任意類型的 a;map 最后返回的是一個類型 b 的數(shù)組。

辨別類型和它們的含義是一項重要的技能,這項技能可以讓你在函數(shù)式編程的路上走得更遠(yuǎn)。不僅論文、博客和文檔等更易理解,類型簽名本身也基本上能夠告訴你它的函數(shù)性(functionality)。要成為一個能夠熟練讀懂類型簽名的人,你得勤于練習(xí);不過一旦掌握了這項技能,你將會受益無窮,不讀手冊也能獲取大量信息。

最后再舉幾個復(fù)雜的例子~~

</>復(fù)制代碼

  1. // head :: [a] -> a
  2. const head = xs => xs[0]
  3. // filter :: (a -> Bool) -> [a] -> [a]
  4. const filter = curry((f, xs) => xs.filter(f))
  5. // reduce :: (b -> a -> b) -> b -> [a] -> b
  6. const reduce = curry((f, x, xs) => xs.reduce(f, x))

reduce 可能是以上簽名里讓人印象最為深刻的一個,同時也是最復(fù)雜的一個了,所以如果你理解起來有困難的話,也不必氣餒。為了滿足你的好奇心,我還是試著解釋一下吧;盡管我的解釋遠(yuǎn)遠(yuǎn)不如你自己通過類型簽名理解其含義來得有教益。

不保證解釋完全正確...(譯者注:此處原文是“here goes nothing”,一般用于人們在做沒有把握的事情之前說的話。)

注意看 reduce 的簽名,可以看到它的第一個參數(shù)是個函數(shù)(所以用了括號),這個函數(shù)接受一個 b 和一個 a 并返回一個 b。

那么這些 a 和 b 是從哪來的呢?

很簡單,簽名中的第二個和第三個參數(shù)就是 b 和元素為 a 的數(shù)組,所以唯一合理的假設(shè)就是這里的 b 和每一個 a 都將傳給前面說的函數(shù)作為參數(shù)。我們還可以看到,reduce 函數(shù)最后返回的結(jié)果是一個 b,也就是說,reduce 的第一個參數(shù)函數(shù)的輸出就是 reduce 函數(shù)的輸出。知道了 reduce 的含義,我們才敢說上面關(guān)于類型簽名的推理是正確的。

3.4.2.參數(shù)態(tài)(Parametricity)

一旦引入一個類型變量,就會出現(xiàn)一個奇怪的特性叫做參數(shù)態(tài)。

這個特性表明,函數(shù)將會以一種統(tǒng)一的行為作用于所有的類型。

</>復(fù)制代碼

  1. // head :: [a] -> a

以 head 函數(shù)為例,可以看到它接受 [a] 返回 a。我們除了知道參數(shù)是個數(shù)組,其他的一概不知;所以函數(shù)的功能就只限于操作這個數(shù)組上。

在它對 a 一無所知的情況下,它可能對 a 做什么操作呢?

換句話說,a 告訴我們它不是一個特定的類型,這意味著它可以是任意類型;那么我們的函數(shù)對每一個可能的類型的操作都必須保持統(tǒng)一,這就是參數(shù)態(tài)的含義。

要讓我們來猜測 head 的實現(xiàn)的話,唯一合理的推斷就是它返回數(shù)組的第一個,或者最后一個,或者某個隨機(jī)的元素;當(dāng)然,head 這個命名已經(jīng)告訴我們了答案。

再看一個例子:

</>復(fù)制代碼

  1. // reverse :: [a] -> [a]

僅從類型簽名來看,reverse 可能的目的是什么?

再次強(qiáng)調(diào),它不能對 a 做任何特定的事情。它不能把 a 變成另一個類型,或者引入一個 b;這都是不可能的。

那它可以排序么?我覺得不行,我覺得很普通~,沒有足夠的信息讓它去為每一個可能的類型排序。

它能重新排列么?我覺得還 ok,但它必須以一種可預(yù)料的方式達(dá)成目標(biāo)。另外,它也有可能刪除或者重復(fù)某一個元素。

重點(diǎn)是,不管在哪種情況下,類型 a 的多態(tài)性(polymorphism)都會大幅縮小 reverse 函數(shù)可能的行為的范圍。

這種“可能性范圍的縮小”(narrowing of possibility)允許我們利用類似 Hoogle 這樣的類型簽名搜索引擎去搜索我們想要的函數(shù)。類型簽名所能包含的信息量真的非常大。

3.4.3.自由定理(Free Theorems)

類型簽名除了能夠幫助我們推斷函數(shù)可能的實現(xiàn),還能夠給我們帶來自由定理。下面是兩個直接從 Wadler 關(guān)于此主題的論文 中隨機(jī)選擇的例子:

</>復(fù)制代碼

  1. // head :: [a] -> a
  2. compose(f, head) === compose(head, map(f))
  3. // filter :: (a -> Bool) -> [a] -> [a]
  4. // 其中 f 和 p 是謂詞函數(shù)
  5. compose(map(f), filter(compose(p, f))) ===
  6. compose(filter(p), map(f))

不用寫一行代碼你也能理解這些定理,它們直接來自于類型本身。

第一個例子中,等式左邊說的是,先獲取數(shù)組的頭部(譯者注:即第一個元素),然后對它調(diào)用函數(shù) f;等式右邊說的是,先對數(shù)組中的每一個元素調(diào)用 f,然后再取其返回結(jié)果的頭部。這兩個表達(dá)式的作用是相等的,但是前者要快得多。

第二個例子 filter 也是一樣。等式左邊是說,先組合 f 和 p 檢查哪些元素要過濾掉,然后再通過 map 實際調(diào)用 f(別忘了 filter 是不會改變數(shù)組中元素的,這就保證了 a 將保持不變);等式右邊是說,先用 map 調(diào)用 f,然后再根據(jù) p 過濾元素。這兩者也是相等的。

你可能會想,這不是常識么。但計算機(jī)是沒有常識的。實際上,計算機(jī)必須要有一種形式化方法來自動進(jìn)行類似的代碼優(yōu)化。數(shù)學(xué)提供了這種方法,能夠形式化直觀的感覺,這無疑對死板的計算機(jī)邏輯非常有用。

以上只是兩個例子,但它們傳達(dá)的定理卻是普適的,可以應(yīng)用到所有的多態(tài)性類型簽名上。在 JavaScript 中,你可以借助一些工具來聲明重寫規(guī)則,也可以直接使用 compose 函數(shù)來定義重寫規(guī)則。總之,這么做的好處是顯而易見且唾手可得的,可能性則是無限的。

3.4.4.類型約束

最后要注意的一點(diǎn)是,簽名也可以把類型約束為一個特定的接口(interface)。

</>復(fù)制代碼

  1. // sort :: Ord a => [a] -> [a]

胖箭頭左邊表明的是這樣一個事實:a 一定是個 Ord 對象,或者說 a 必須要實現(xiàn) Ord 接口。

Ord 到底是什么?它是從哪來的?在一門強(qiáng)類型語言中,它可能就是一個自定義的接口,能夠讓不同的值排序。通過這種方式,我們不僅能夠獲取關(guān)于 a 的更多信息,了解 sort 函數(shù)具體要干什么,而且還能限制函數(shù)的作用范圍。我們把這種接口聲明叫做類型約束(type constraints)。

</>復(fù)制代碼

  1. // assertEqual :: (Eq a, Show a) => a -> a -> Assertion

這個例子中有兩個約束:Eq 和 Show。它們保證了我們可以檢查不同的 a 是否相等,并在有不相等的情況下打印出其中的差異。

3.4.5.類型簽名的作用

總結(jié)一下類型簽名的作用就是:

聲明函數(shù)的輸入和輸出

讓函數(shù)保持通用和抽象

可以用于編譯時候檢查

代碼最好的文檔

參考資料

JS函數(shù)式編程指南

Pointfree 編程風(fēng)格指南

Hey Underscore, You"re Doing It Wrong!

Functional Concepts with JavaScript: Part I

Professor Frisby Introduces Composable Functional JavaScript

函數(shù)式編程入門教程

相關(guān)文章

JavaScript 函數(shù)式編程(一)

JavaScript 函數(shù)式編程(二)-- 本文

JavaScript 函數(shù)式編程(三)

JavaScript 函數(shù)式編程(四)正在醞釀...

以上 to be continued...

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/96873.html

相關(guān)文章

  • SegmentFault 技術(shù)周刊 Vol.16 - 淺入淺出 JavaScript 函數(shù)編程

    摘要:函數(shù)式編程,一看這個詞,簡直就是學(xué)院派的典范。所以這期周刊,我們就重點(diǎn)引入的函數(shù)式編程,淺入淺出,一窺函數(shù)式編程的思想,可能讓你對編程語言的理解更加融會貫通一些。但從根本上來說,函數(shù)式編程就是關(guān)于如使用通用的可復(fù)用函數(shù)進(jìn)行組合編程。 showImg(https://segmentfault.com/img/bVGQuc); 函數(shù)式編程(Functional Programming),一...

    csRyan 評論0 收藏0
  • 【響應(yīng)編程的思維藝術(shù)】 (1)Rxjs專題學(xué)習(xí)計劃

    摘要:由于技術(shù)棧的學(xué)習(xí),筆者需要在原來函數(shù)式編程知識的基礎(chǔ)上,學(xué)習(xí)的使用。筆者在社區(qū)發(fā)現(xiàn)了一個非常高質(zhì)量的響應(yīng)式編程系列教程共篇,從基礎(chǔ)概念到實際應(yīng)用講解的非常詳細(xì),有大量直觀的大理石圖來輔助理解流的處理,對培養(yǎng)響應(yīng)式編程的思維方式有很大幫助。 showImg(https://segmentfault.com/img/bVus8n); [TOC] 一. 響應(yīng)式編程 響應(yīng)式編程,也稱為流式編程...

    lscho 評論0 收藏0
  • JavaScript函數(shù)編程

    摘要:函數(shù)式編程二拖延癥了好久,第二篇終于寫出來了。如果你對熟悉的話應(yīng)該還記得,是可以調(diào)用來集中處理錯誤的對于函數(shù)式編程我們也可以做同樣的操作,如果運(yùn)行正確,那么就返回正確的結(jié)果如果錯誤,就返回一個用于描述錯誤的結(jié)果。 JavaScript函數(shù)式編程(二) 拖延癥了好久,第二篇終于寫出來了。 上一篇在這里:JavaScript函數(shù)式編程(一) 上一篇文章里我們提到了純函數(shù)的概念,所謂的純函數(shù)...

    booster 評論0 收藏0
  • JavaScript的語言特性以及重要版本

    摘要:通常一個完成的不僅僅包含了還包括了以及相關(guān)版本該版本在中使用。基于原型函數(shù)先行的語言使用基于原型的的繼承機(jī)制,函數(shù)是的第一等公民其他相關(guān)的語言特性編譯型語言把做好的源程序全部編譯成二進(jìn)制代碼的可運(yùn)行程序。 轉(zhuǎn)載請注明出處,創(chuàng)作不易,更多文章請戳 https://github.com/ZhengMaste... 前言:JavaScript誕生于1995年,它是一門腳本語言,起初的目...

    Yangder 評論0 收藏0
  • JavaScript中的函數(shù)編程(翻譯)

    摘要:原文鏈接原文作者函數(shù)式編程這篇文章是介紹函數(shù)式編程的四篇文章中的第二篇。這些部分被使用的越來越頻繁,人們把他們放到一個函數(shù)式編程的庫里面,有一些流行的庫包括未亡待續(xù)閱讀下一節(jié)原文地址歡迎關(guān)注 showImg(https://segmentfault.com/img/bVtSez); tips 原文鏈接: http://jrsinclair.com/articles/2016/gentl...

    smartlion 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<