摘要:注意是單一參數(shù)柯里化是由以邏輯學(xué)家命名的,當(dāng)然編程語言也是源自他的名字,雖然柯里化是由和發(fā)明的。辨別類型和它們的含義是一項(xiàng)重要的技能,這項(xiàng)技能可以讓你在函數(shù)式編程的路上走得更遠(yuǎn)。
slide 地址
三、可以,這很函數(shù)式~ 3.1.函數(shù)是一等公民! 3.1.1.濫用匿名函數(shù)其實(shí)經(jīng)常寫 JavaScript 的人可能潛移默化地已經(jīng)接受了這個(gè)觀念,例如你可以像對待任何其他數(shù)據(jù)類型一樣對待函數(shù)——把它們存在數(shù)組里,當(dāng)作參數(shù)傳遞,賦值給變量.等等。
然而,常常可以看到濫用匿名函數(shù)的現(xiàn)象...
// 太傻了 const getServerStuff = function (callback) { return ajaxCall(function (json) { return callback(json) }) } // 這才像樣 const getServerStuff = ajaxCall // 下面來推導(dǎo)一下... const getServerStuff === callback => ajaxCall(json => callback(json)) === callback => ajaxCall(callback) === ajaxCall // from JS函數(shù)式編程指南
再來看一個(gè)例子...
const BlogController = (function () { const index = function (posts) { return Views.index(posts) } const show = function (post) { return Views.show(post) } const create = function (attrs) { return Db.create(attrs) } const update = function (post, attrs) { return Db.update(post, attrs) } const destroy = function (post) { return Db.destroy(post) } return { index, show, create, update, destroy } })() // 以上代碼 99% 都是多余的... const BlogController = { index: Views.index, show: Views.show, create: Db.create, update: Db.update, destroy: Db.destroy, } // ...或者直接全部刪掉 // 因?yàn)樗淖饔脙H僅就是把視圖(Views)和數(shù)據(jù)庫(Db)打包在一起而已。 // from JS函數(shù)式編程指南3.1.2.為何鐘愛一等公民?
以上那種多包一層的寫法最大的問題就是,一旦內(nèi)部函數(shù)需要新增或修改參數(shù),那么包裹它的函數(shù)也要改...
// 原始函數(shù) httpGet("/post/2", function (json) { return renderPost(json) }) // 假如需要多傳遞一個(gè) err 參數(shù) httpGet("/post/2", function (json, err) { return renderPost(json, err) }) // renderPost 將會(huì)在 httpGet 中調(diào)用, // 想要多少參數(shù),想怎么改都行 httpGet("/post/2", renderPost)3.1.3.提高函數(shù)復(fù)用率
除了上面說的避免使用不必要的中間函數(shù)包裹以外,對于函數(shù)參數(shù)的起名也很重要,盡量編寫通用參數(shù)的函數(shù)。
// 只針對當(dāng)前的博客 const validArticles = function (articles) { return articles.filter(function (article) { return article !== null && article !== undefined }) } // 通用性好太多 const compact = function(xs) { return xs.filter(function (x) { return x !== null && x !== undefined }) }
以上例子說明了在命名的時(shí)候,我們特別容易把自己限定在特定的數(shù)據(jù)上(本例中是 articles)。這種現(xiàn)象很常見,也是重復(fù)造輪子的一大原因。
3.1.4.this在函數(shù)式編程中,其實(shí)根本用不到 this...
但這里并不是說要避免使用 this(江來報(bào)道上出了偏差...識得唔識得?)
把接受多個(gè)參數(shù)的函數(shù)變換成一系列接受單一參數(shù)(從最初函數(shù)的第一個(gè)參數(shù)開始)的函數(shù)的技術(shù)。(注意是單一參數(shù))
import { curry } from "lodash" const add = (x, y) => x + y const curriedAdd = curry(add) const increment = curriedAdd(1) const addTen = curriedAdd(10) increment(2) // 3 addTen(2) // 12
柯里化是由 Christopher Strachey 以邏輯學(xué)家 Haskell Curry 命名的,3.2.2.柯里化 VS 偏函數(shù)應(yīng)用(partial application)
當(dāng)然編程語言 Haskell 也是源自他的名字,
雖然柯里化是由 Moses Schnfinkel 和 Gottlob Frege 發(fā)明的。
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)用簡單來說就是:一個(gè)函數(shù),接受一個(gè)多參數(shù)的函數(shù)且傳入部分參數(shù)后,返回一個(gè)需要更少參數(shù)的新函數(shù)。
柯里化一般和偏函數(shù)應(yīng)用相伴出現(xiàn),但這兩者是不同的概念:
import { curry, partial } from "lodash" const add = (x, y, z) => x + y + z const curriedAdd = curry(add) // <- 只接受一個(gè)函數(shù) const addThree = partial(add, 1, 2) // <- 不僅接受函數(shù),還接受至少一個(gè)參數(shù) === curriedAdd(1)(2) // <- 柯里化每次都返回一個(gè)單參函數(shù)
簡單來說,一個(gè)多參函數(shù)(n-ary),柯里化后就變成了 n * 1-ary,而偏函數(shù)應(yīng)用了 x 個(gè)參數(shù)后就變成了 (n-x)-ary
3.2.3.柯里化的實(shí)現(xiàn)雖然從理論上說柯里化應(yīng)該返回的是一系列的單參函數(shù),但在實(shí)際的使用過程中為了像偏函數(shù)應(yīng)用那樣方便的調(diào)用,所以這里柯里化后的函數(shù)也能接受多個(gè)參數(shù)。
// 實(shí)現(xiàn)一個(gè)函數(shù) curry 滿足以下調(diào)用、 const f = (a, b, c, d) => { ... } const curried = curry(f) curried(a, b, c, d) curried(a, b, c)(d) curried(a)(b, c, d) curried(a, b)(c, d) curried(a)(b, c)(d) curried(a)(b)(c, d) curried(a, b)(c)(d)
很明顯第一反應(yīng)是需要使用遞歸,這樣才能返回一系列的函數(shù)。而遞歸的結(jié)束條件就是接受了原函數(shù)數(shù)量的參數(shù),所以重點(diǎn)就是參數(shù)的傳遞~
// ES5 var curry = function curry (fn, arr) { arr = arr || [] return function () { var args = [].slice.call(arguments) var arg = arr.concat(args) return arg.length >= fn.length ? fn.apply(null, arg) : curry(fn, arg) } } // ES6 const curry = (fn, arr = []) => (...args) => ( arg => arg.length >= fn.length ? fn(...arg) : curry(fn, arg) )([...arr, ...args])3.2.4.柯里化的意義
寫習(xí)慣了傳統(tǒng)編程語言的人的第一反應(yīng)一般都是,柯里化這玩意兒有啥用咧?
柯里化和偏函數(shù)應(yīng)用的主要意義就是固定一些我們已知的參數(shù),然后返回一個(gè)函數(shù)繼續(xù)等待接收那些未知的參數(shù)。
所以常見的使用場景之一就是高級抽象后的代碼復(fù)用。例如首先編寫一個(gè)多參數(shù)的通用函數(shù),將其柯里化后,就可以基于偏函數(shù)應(yīng)用將其綁定不同的業(yè)務(wù)代碼。
// 定義通用函數(shù) const converter = ( toUnit, factor, offset = 0, input ) => ([ ((offset + input) * factor).toFixed(2), toUnit, ].join(" ")) // 分別綁定不同參數(shù) const milesToKm = curry(converter)("km", 1.60936, undefined) const poundsToKg = curry(converter)("kg", 0.45460, undefined) const farenheitToCelsius = curry(converter)("degrees C", 0.5556, -32) -- from https://stackoverflow.com/a/6861858
你可能會(huì)反駁說其實(shí)也可以不使用這些花里胡哨的柯里化啊,偏函數(shù)應(yīng)用啊什么的東東,我就鐵頭娃愣頭青地直接懟也能實(shí)現(xiàn)以上的邏輯。(這一手皮的嘛,就不談了...)
function converter (ratio, symbol, input) { return (input * ratio).toFixed(2) + " " + symbol } converter(2.2, "lbs", 4) converter(1.62, "km", 34) converter(1.98, "US pints", 2.4) converter(1.75, "imperial pints", 2.4) -- from https://stackoverflow.com/a/32379766
然而兩者的區(qū)別在于,假如函數(shù) converter 所需的參數(shù)無法同時(shí)得到,對柯里化的方式來說沒有影響,因?yàn)橐呀?jīng)用閉包保存住了已知參數(shù)。而后者可能就需要使用變量暫存或其他方法來保證同時(shí)得到所有參數(shù)。
3.3.函數(shù)組合(compose) 3.3.1.組合的概念函數(shù)組合就是將兩個(gè)或多個(gè)函數(shù)結(jié)合起來形成一個(gè)新函數(shù)。
就好像將一節(jié)一節(jié)的管道連接起來,原始數(shù)據(jù)經(jīng)過這一節(jié)一節(jié)的管道處理之后得到最終結(jié)果。
說起來很玄乎,其實(shí)就是假設(shè)有一個(gè)函數(shù) f 和另一個(gè)函數(shù) g,還有數(shù)據(jù) x,經(jīng)過計(jì)算最終結(jié)果就是 f(g(x))。
在高中數(shù)學(xué)中我們應(yīng)該都學(xué)到過復(fù)合函數(shù)。
如果 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í)這就是函數(shù)組合)
3.3.2.組合的實(shí)現(xiàn)const add1 = x => x + 1 const mul3 = x => x * 3 const div2 = x => x / 2 div2(mul3(add1(add1(0)))) // 結(jié)果是 3,但這樣寫可讀性太差了 const operate = compose(div2, mul3, add1, add1) operate(0) // => 相當(dāng)于 div2(mul3(add1(add1(0)))) operate(2) // => 相當(dāng)于 div2(mul3(add1(add1(2)))) // redux 版 const compose = (...fns) => { if (fns.length === 0) return arg => arg if (fns.length === 1) return fns[0] return fns.reduce((a, b) => (...args) => a(b(...args))) } // 一行版,支持多參數(shù),但必須至少傳一個(gè)函數(shù) const compose = (...fns) => fns.reduceRight((acc, fn) => (...args) => fn(acc(...args))) // 一行版,只支持單參數(shù),但支持不傳函數(shù) const compose = (...fns) => arg => fns.reduceRight((acc, fn) => fn(acc), arg)3.3.3.Pointfree
起名字是一個(gè)很麻煩的事兒,而 Pointfree 風(fēng)格能夠有效減少大量中間變量的命名。
Pointfree 即不使用所要處理的值,只合成運(yùn)算過程。中文可以譯作"無值"風(fēng)格。from Pointfree 編程風(fēng)格指南
請看下面的例子。(注意理解函數(shù)是一等公民和函數(shù)組合的概念)
const addOne = x => x + 1 const square = x => x * x
上面是兩個(gè)簡單函數(shù) addOne 和 square,現(xiàn)在把它們合成一個(gè)運(yùn)算。
const addOneThenSquare = compose(square, addOne) addOneThenSquare(2) // 9
上面代碼中,addOneThenSquare 是一個(gè)合成函數(shù)。定義它的時(shí)候,根本不需要提到要處理的值,這就是 Pointfree。
// 非 Pointfree,因?yàn)樘岬搅藬?shù)據(jù):word const snakeCase = function (word) { return word.toLowerCase().replace(/s+/ig, "_") } // Pointfree const snakeCase = compose(replace(/s+/ig, "_"), toLowerCase)
然而可惜的是,以上很 Pointfree 的代碼會(huì)報(bào)錯(cuò),因?yàn)樵?JavaScript 中 replace 和 toLowerCase 函數(shù)是定義在 String 的原型鏈上的...
此外有的庫(如 Underscore、Lodash...)把需要處理的數(shù)據(jù)放到了第一個(gè)參數(shù)。
const square = n => n * n; _.map([4, 8], square) // 第一個(gè)參數(shù)是待處理數(shù)據(jù) R.map(square, [4, 8]) // 一般函數(shù)式庫都將數(shù)據(jù)放在最后
這樣會(huì)有一些很不函數(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ù)組合的意義首先讓我們從抽象的層次來思考一下:一個(gè) app 由什么組成?(當(dāng)然是由 a、p、p 三個(gè)字母組成的啦)
一個(gè)應(yīng)用其實(shí)就是一個(gè)長時(shí)間運(yùn)行的進(jìn)程,并將一系列異步的事件轉(zhuǎn)換為對應(yīng)結(jié)果。
一個(gè) start 可以是:
開啟應(yīng)用
DOM 事件(DOMContentLoaded, onClick, onSubmit...)
接收到的 HTTP 請求
返回的 HTTP 響應(yīng)
查詢數(shù)據(jù)庫的結(jié)果
WebSocket 消息
..
一個(gè) end 或者說是 effect 可以是:
渲染或更新 UI
觸發(fā)一個(gè) DOM 事件
創(chuàng)建一個(gè) HTTP 請求
返回一個(gè) HTTP 響應(yīng)
保存數(shù)據(jù)到 DB
發(fā)送 WebSocket 消息
...
那么在 start 和 end 之間的東東,我們可以看做數(shù)據(jù)流的變換(transformations)。這些變換具體的說就是一系列的變換動(dòng)詞的結(jié)合。
這些動(dòng)詞描述了這些變換做了些什么(而不是怎么做)如:
filter
slice
map
reduce
concat
zip
fork
flatten
...
當(dāng)然日常編寫的程序中一般不會(huì)像之前的例子那樣的簡單,它的數(shù)據(jù)流可能是像下面這樣的...
并且,如果這些變換在編寫時(shí),遵守了基本的函數(shù)式規(guī)則和最佳實(shí)踐(純函數(shù),無副作用,引用透明...)。
那么這些變換可以被輕易地重用、改寫、維護(hù)、測試,這也就意味著編寫的應(yīng)用可以很方便地進(jìn)行擴(kuò)展,而這些變換結(jié)合的基礎(chǔ)正是函數(shù)組合。
3.4.Hindley-Milner 類型簽名 3.4.1.基本概念先來看一些例子~
// strLength :: String -> Number const strLength = s => s.length // join :: String -> [String] -> String const join = curry((what, xs) => xs.join(what)) // match :: Regex -> String -> [String] const match = curry((reg, s) => s.match(reg)) // replace :: Regex -> String -> String -> String const replace = curry((reg, sub, s) => s.replace(reg, sub))
在 Hindley-Milner 系統(tǒng)中,函數(shù)都寫成類似 a -> b 這個(gè)樣子,其中 a 和 b 是任意類型的變量。
以上例子中的多參函數(shù),可能看起來比較奇怪,為啥沒有括號?
例如對于 match 函數(shù),我們將其柯里化后,完全可以把它的類型簽名這樣分組:
// match :: Regex -> (String -> [String]) const match = curry((reg, s) => s.match(reg))
現(xiàn)在我們可以看出 match 這個(gè)函數(shù)首先接受了一個(gè) Regex 作為參數(shù),返回一個(gè)從 String 到 [String] 的函數(shù)。
因?yàn)榭吕锘斐傻慕Y(jié)果就是這樣:給 match 函數(shù)一個(gè) Regex 參數(shù)后,得到一個(gè)新函數(shù),它能夠接著處理 String 參數(shù)。
假設(shè)我們將第一個(gè)參數(shù)傳入 /holiday/ig,那么代碼就變成了這樣:
// match :: Regex -> (String -> [String]) const match = curry((reg, s) => s.match(reg)) // onHoliday :: String -> [String] const onHoliday = match(/holiday/ig)
可以看出柯里化后每傳一個(gè)參數(shù),就會(huì)彈出類型簽名最前面的那個(gè)類型。所以 onHoliday 就是已經(jīng)有了 Regex 參數(shù)的 match 函數(shù)。
// replace :: Regex -> (String -> (String -> String)) const replace = curry((reg, sub, s) => s.replace(reg, sub))
同樣的思路來看最后一個(gè)函數(shù) replace,可以看出為 replace 加上這么多括號未免有些多余。
所以這里的括號是完全可以省略的,如果我們愿意,甚至可以一次性把所有的參數(shù)都傳進(jìn)來。
再來看幾個(gè)例子~
// id :: a -> a const id = x => x // map :: (a -> b) -> [a] -> [b] const map = curry((f, xs) => xs.map(f))
這里的 id 函數(shù)接受任意類型的 a 并返回同一個(gè)類型的數(shù)據(jù)(話說 map 的簽名里為啥加了括號呢~)。
和普通代碼一樣,我們也可以在類型簽名中使用變量。把變量命名為 a 和 b 只是一種約定俗成的習(xí)慣,你可以使用任何你喜歡的名稱。但對于相同的變量名,其類型一定相同。
這是非常重要的一個(gè)原則,所以我們必須重申:a -> b 可以是從任意類型的 a 到任意類型的 b,但是 a -> a 必須是同一個(gè)類型。
例如,id 可以是 String -> String,也可以是 Number -> Number,但不能是 String -> Bool。
相似地,map 也使用了變量,只不過這里的 b 可能與 a 類型相同,也可能不相同。
我們可以這么理解:map 接受兩個(gè)參數(shù),第一個(gè)是從任意類型 a 到任意類型 b 的函數(shù);第二個(gè)是一個(gè)數(shù)組,元素是任意類型的 a;map 最后返回的是一個(gè)類型 b 的數(shù)組。
辨別類型和它們的含義是一項(xiàng)重要的技能,這項(xiàng)技能可以讓你在函數(shù)式編程的路上走得更遠(yuǎn)。不僅論文、博客和文檔等更易理解,類型簽名本身也基本上能夠告訴你它的函數(shù)性(functionality)。要成為一個(gè)能夠熟練讀懂類型簽名的人,你得勤于練習(xí);不過一旦掌握了這項(xiàng)技能,你將會(huì)受益無窮,不讀手冊也能獲取大量信息。
最后再舉幾個(gè)復(fù)雜的例子~~
// head :: [a] -> a const head = xs => xs[0] // filter :: (a -> Bool) -> [a] -> [a] const filter = curry((f, xs) => xs.filter(f)) // reduce :: (b -> a -> b) -> b -> [a] -> b const reduce = curry((f, x, xs) => xs.reduce(f, x))
reduce 可能是以上簽名里讓人印象最為深刻的一個(gè),同時(shí)也是最復(fù)雜的一個(gè)了,所以如果你理解起來有困難的話,也不必氣餒。為了滿足你的好奇心,我還是試著解釋一下吧;盡管我的解釋遠(yuǎn)遠(yuǎn)不如你自己通過類型簽名理解其含義來得有教益。
不保證解釋完全正確...(譯者注:此處原文是“here goes nothing”,一般用于人們在做沒有把握的事情之前說的話。)
注意看 reduce 的簽名,可以看到它的第一個(gè)參數(shù)是個(gè)函數(shù)(所以用了括號),這個(gè)函數(shù)接受一個(gè) b 和一個(gè) a 并返回一個(gè) b。
那么這些 a 和 b 是從哪來的呢?
很簡單,簽名中的第二個(gè)和第三個(gè)參數(shù)就是 b 和元素為 a 的數(shù)組,所以唯一合理的假設(shè)就是這里的 b 和每一個(gè) a 都將傳給前面說的函數(shù)作為參數(shù)。我們還可以看到,reduce 函數(shù)最后返回的結(jié)果是一個(gè) b,也就是說,reduce 的第一個(gè)參數(shù)函數(shù)的輸出就是 reduce 函數(shù)的輸出。知道了 reduce 的含義,我們才敢說上面關(guān)于類型簽名的推理是正確的。
3.4.2.參數(shù)態(tài)(Parametricity)一旦引入一個(gè)類型變量,就會(huì)出現(xiàn)一個(gè)奇怪的特性叫做參數(shù)態(tài)。
這個(gè)特性表明,函數(shù)將會(huì)以一種統(tǒng)一的行為作用于所有的類型。
// head :: [a] -> a
以 head 函數(shù)為例,可以看到它接受 [a] 返回 a。我們除了知道參數(shù)是個(gè)數(shù)組,其他的一概不知;所以函數(shù)的功能就只限于操作這個(gè)數(shù)組上。
在它對 a 一無所知的情況下,它可能對 a 做什么操作呢?
換句話說,a 告訴我們它不是一個(gè)特定的類型,這意味著它可以是任意類型;那么我們的函數(shù)對每一個(gè)可能的類型的操作都必須保持統(tǒng)一,這就是參數(shù)態(tài)的含義。
要讓我們來猜測 head 的實(shí)現(xiàn)的話,唯一合理的推斷就是它返回?cái)?shù)組的第一個(gè),或者最后一個(gè),或者某個(gè)隨機(jī)的元素;當(dāng)然,head 這個(gè)命名已經(jīng)告訴我們了答案。
再看一個(gè)例子:
// reverse :: [a] -> [a]
僅從類型簽名來看,reverse 可能的目的是什么?
再次強(qiáng)調(diào),它不能對 a 做任何特定的事情。它不能把 a 變成另一個(gè)類型,或者引入一個(gè) b;這都是不可能的。
那它可以排序么?我覺得不行,我覺得很普通~,沒有足夠的信息讓它去為每一個(gè)可能的類型排序。
它能重新排列么?我覺得還 ok,但它必須以一種可預(yù)料的方式達(dá)成目標(biāo)。另外,它也有可能刪除或者重復(fù)某一個(gè)元素。
重點(diǎn)是,不管在哪種情況下,類型 a 的多態(tài)性(polymorphism)都會(huì)大幅縮小 reverse 函數(shù)可能的行為的范圍。
這種“可能性范圍的縮小”(narrowing of possibility)允許我們利用類似 Hoogle 這樣的類型簽名搜索引擎去搜索我們想要的函數(shù)。類型簽名所能包含的信息量真的非常大。
3.4.3.自由定理(Free Theorems)類型簽名除了能夠幫助我們推斷函數(shù)可能的實(shí)現(xiàn),還能夠給我們帶來自由定理。下面是兩個(gè)直接從 Wadler 關(guān)于此主題的論文 中隨機(jī)選擇的例子:
// head :: [a] -> a compose(f, head) === compose(head, map(f)) // filter :: (a -> Bool) -> [a] -> [a] // 其中 f 和 p 是謂詞函數(shù) compose(map(f), filter(compose(p, f))) === compose(filter(p), map(f))
不用寫一行代碼你也能理解這些定理,它們直接來自于類型本身。
第一個(gè)例子中,等式左邊說的是,先獲取數(shù)組的頭部(譯者注:即第一個(gè)元素),然后對它調(diào)用函數(shù) f;等式右邊說的是,先對數(shù)組中的每一個(gè)元素調(diào)用 f,然后再取其返回結(jié)果的頭部。這兩個(gè)表達(dá)式的作用是相等的,但是前者要快得多。
第二個(gè)例子 filter 也是一樣。等式左邊是說,先組合 f 和 p 檢查哪些元素要過濾掉,然后再通過 map 實(shí)際調(diào)用 f(別忘了 filter 是不會(huì)改變數(shù)組中元素的,這就保證了 a 將保持不變);等式右邊是說,先用 map 調(diào)用 f,然后再根據(jù) p 過濾元素。這兩者也是相等的。
你可能會(huì)想,這不是常識么。但計(jì)算機(jī)是沒有常識的。實(shí)際上,計(jì)算機(jī)必須要有一種形式化方法來自動(dòng)進(jìn)行類似的代碼優(yōu)化。數(shù)學(xué)提供了這種方法,能夠形式化直觀的感覺,這無疑對死板的計(jì)算機(jī)邏輯非常有用。
以上只是兩個(gè)例子,但它們傳達(dá)的定理卻是普適的,可以應(yīng)用到所有的多態(tài)性類型簽名上。在 JavaScript 中,你可以借助一些工具來聲明重寫規(guī)則,也可以直接使用 compose 函數(shù)來定義重寫規(guī)則。總之,這么做的好處是顯而易見且唾手可得的,可能性則是無限的。
3.4.4.類型約束最后要注意的一點(diǎn)是,簽名也可以把類型約束為一個(gè)特定的接口(interface)。
// sort :: Ord a => [a] -> [a]
胖箭頭左邊表明的是這樣一個(gè)事實(shí):a 一定是個(gè) Ord 對象,或者說 a 必須要實(shí)現(xiàn) Ord 接口。
Ord 到底是什么?它是從哪來的?在一門強(qiáng)類型語言中,它可能就是一個(gè)自定義的接口,能夠讓不同的值排序。通過這種方式,我們不僅能夠獲取關(guān)于 a 的更多信息,了解 sort 函數(shù)具體要干什么,而且還能限制函數(shù)的作用范圍。我們把這種接口聲明叫做類型約束(type constraints)。
// assertEqual :: (Eq a, Show a) => a -> a -> Assertion
這個(gè)例子中有兩個(gè)約束:Eq 和 Show。它們保證了我們可以檢查不同的 a 是否相等,并在有不相等的情況下打印出其中的差異。
3.4.5.類型簽名的作用總結(jié)一下類型簽名的作用就是:
聲明函數(shù)的輸入和輸出
讓函數(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://specialneedsforspecialkids.com/yun/96873.html
摘要:函數(shù)式編程,一看這個(gè)詞,簡直就是學(xué)院派的典范。所以這期周刊,我們就重點(diǎn)引入的函數(shù)式編程,淺入淺出,一窺函數(shù)式編程的思想,可能讓你對編程語言的理解更加融會(huì)貫通一些。但從根本上來說,函數(shù)式編程就是關(guān)于如使用通用的可復(fù)用函數(shù)進(jìn)行組合編程。 showImg(https://segmentfault.com/img/bVGQuc); 函數(shù)式編程(Functional Programming),一...
摘要:由于技術(shù)棧的學(xué)習(xí),筆者需要在原來函數(shù)式編程知識的基礎(chǔ)上,學(xué)習(xí)的使用。筆者在社區(qū)發(fā)現(xiàn)了一個(gè)非常高質(zhì)量的響應(yīng)式編程系列教程共篇,從基礎(chǔ)概念到實(shí)際應(yīng)用講解的非常詳細(xì),有大量直觀的大理石圖來輔助理解流的處理,對培養(yǎng)響應(yīng)式編程的思維方式有很大幫助。 showImg(https://segmentfault.com/img/bVus8n); [TOC] 一. 響應(yīng)式編程 響應(yīng)式編程,也稱為流式編程...
摘要:函數(shù)式編程二拖延癥了好久,第二篇終于寫出來了。如果你對熟悉的話應(yīng)該還記得,是可以調(diào)用來集中處理錯(cuò)誤的對于函數(shù)式編程我們也可以做同樣的操作,如果運(yùn)行正確,那么就返回正確的結(jié)果如果錯(cuò)誤,就返回一個(gè)用于描述錯(cuò)誤的結(jié)果。 JavaScript函數(shù)式編程(二) 拖延癥了好久,第二篇終于寫出來了。 上一篇在這里:JavaScript函數(shù)式編程(一) 上一篇文章里我們提到了純函數(shù)的概念,所謂的純函數(shù)...
摘要:通常一個(gè)完成的不僅僅包含了還包括了以及相關(guān)版本該版本在中使用。基于原型函數(shù)先行的語言使用基于原型的的繼承機(jī)制,函數(shù)是的第一等公民其他相關(guān)的語言特性編譯型語言把做好的源程序全部編譯成二進(jìn)制代碼的可運(yùn)行程序。 轉(zhuǎn)載請注明出處,創(chuàng)作不易,更多文章請戳 https://github.com/ZhengMaste... 前言:JavaScript誕生于1995年,它是一門腳本語言,起初的目...
摘要:原文鏈接原文作者函數(shù)式編程這篇文章是介紹函數(shù)式編程的四篇文章中的第二篇。這些部分被使用的越來越頻繁,人們把他們放到一個(gè)函數(shù)式編程的庫里面,有一些流行的庫包括未亡待續(xù)閱讀下一節(jié)原文地址歡迎關(guān)注 showImg(https://segmentfault.com/img/bVtSez); tips 原文鏈接: http://jrsinclair.com/articles/2016/gentl...
閱讀 2064·2021-09-22 15:43
閱讀 8623·2021-09-22 15:07
閱讀 1078·2021-09-03 10:28
閱讀 2052·2021-08-19 10:57
閱讀 1061·2020-01-08 12:18
閱讀 2972·2019-08-29 15:09
閱讀 1521·2019-08-29 14:05
閱讀 1640·2019-08-29 13:57