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

資訊專欄INFORMATION COLUMN

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

thursday / 3520人閱讀

摘要:注意是單一參數(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)道上出了偏差...識得唔識得?)

3.2.柯里化(curry) 3.2.1.柯里化概念
把接受多個(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 命名的,
當(dāng)然編程語言 Haskell 也是源自他的名字,
雖然柯里化是由 Moses Schnfinkel 和 Gottlob Frege 發(fā)明的。
3.2.2.柯里化 VS 偏函數(shù)應(yīng)用(partial application)
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ù) addOnesquare,現(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 中 replacetoLowerCase 函數(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

相關(guān)文章

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

    摘要:函數(shù)式編程,一看這個(gè)詞,簡直就是學(xué)院派的典范。所以這期周刊,我們就重點(diǎn)引入的函數(shù)式編程,淺入淺出,一窺函數(shù)式編程的思想,可能讓你對編程語言的理解更加融會(huì)貫通一些。但從根本上來說,函數(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í)計(jì)劃

    摘要:由于技術(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)式編程,也稱為流式編程...

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

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

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

    摘要:通常一個(gè)完成的不僅僅包含了還包括了以及相關(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ù)式編程的四篇文章中的第二篇。這些部分被使用的越來越頻繁,人們把他們放到一個(gè)函數(shù)式編程的庫里面,有一些流行的庫包括未亡待續(xù)閱讀下一節(jié)原文地址歡迎關(guān)注 showImg(https://segmentfault.com/img/bVtSez); tips 原文鏈接: http://jrsinclair.com/articles/2016/gentl...

    smartlion 評論0 收藏0

發(fā)表評論

0條評論

最新活動(dòng)
閱讀需要支付1元查看
<