新聞中心
(Applicative f, Traversable t) => (a → f a) → t (f a) → f (t a)
或者,某一些函數(shù)"奇怪"的用法:

創(chuàng)新互聯(lián)建站執(zhí)著的堅持網(wǎng)站建設(shè),微信平臺小程序開發(fā);我們不會轉(zhuǎn)行,已經(jīng)持續(xù)穩(wěn)定運營10多年。專業(yè)的技術(shù),豐富的成功經(jīng)驗和創(chuàng)作思維,提供一站式互聯(lián)網(wǎng)解決方案,以客戶的口碑塑造品牌,攜手廣大客戶,共同發(fā)展進步。
// R.ap can also be used as S combinator // when only two functions are passed
R.ap(R.concat, R.toUpper)('Ramda') //=> 'RamdaRAMDA'
這些"奇怪"的點背后投射著 Ramda "更深"一層的設(shè)計邏輯, 本文將會對此作出講解, 并闡述背后通用的函數(shù)式編程理論知識。
Ramda 為人熟知的一面?
Ramda 經(jīng)常被當(dāng)做 Lodash 的另外一個"更加FP"的替代庫,相對于 Lodash,Ramda 的優(yōu)勢(之一)在于完備的柯里化與 data last 的設(shè)計帶來的便捷的管道式編程(pipe)。
舉一個簡單的代碼對比示例:
- Ramda:
const myFn = R.pipe (
R.fn1,
R.fn2 ('arg1', 'arg2'),
R.fn3 ('arg3'),
R.fn4
)
- Lodash:
const myFn = (x, y) => {
const var1 = _.fn1 (x, y)
const var2 = _.fn2 (var1, 'arg1', 'arg2')
const var3 = _.fn3 (var2, 'arg3')
return _.fn4 (var3)
}
Ramda 類型簽名?
在 Ramda 的 API 文檔中, 類型簽名的語法有些"奇怪":
- add: Number → Number → Number
我們結(jié)合 Ramda 的柯里化規(guī)則, 稍加推測, 可以將這個函數(shù)轉(zhuǎn)換為TypeScript 的定義:
export function add(a: number, b: number): number;
export function add(a: number): (b: number) => number;
OK, 那為什么Ramda 的文檔不直接使用TypeScript 表達函數(shù)的類型呢? -- 因為更加簡潔!
Ramda 文檔中的類型簽名使用的是Haskell 的語法, Haskell 作為一門純函數(shù)式編程語言, 可以很簡潔地表達柯里化的語義, 相較之下, TypeScript 的表達方式就顯得比較臃腫。
當(dāng)然, 使用Haskell 的類型簽名的意義不僅于此, 讓我們再看看其他"奇怪"的函數(shù)類型:
- ap:
[a → b] → [a] → [b]
Apply f => f (a → b) → f a → f b
(r → a → b) → (r → a) → (r → b)
結(jié)合文檔中的demo:
R.ap([R.multiply(2), R.add(3)], [1,2,3]); //=> [2, 4, 6, 4, 5, 6]
R.ap([R.concat('tasty '), R.toUpper], ['pizza', 'salad']); //=> ["tasty pizza", "tasty salad", "PIZZA", "SALAD"]
// R.ap can also be used as S combinator
// when only two functions are passed
R.ap(R.concat, R.toUpper)('Ramda') //=> 'RamdaRAMDA'
[a → b] → [a] → [b]我們好理解, 就是笛卡爾積;
(r → a → b) → (r → a) → (r → b)我們也能理解, 就是兩個函數(shù)的串聯(lián);
Apply f => f (a → b) → f a → f b就有點難理解了, 語法上就有些陌生, 我們先將其翻譯成TypeScript 語法:
:), 好吧, 這段類型沒法簡單地翻譯成TypeScript, 因為: TypeScript 不支持將 「類型構(gòu)造器」 作為類型參數(shù)!舉個例子:
type T= F ;
報錯信息如下:
Type 'F' is not generic.
在類型簽名中F?是一個類型構(gòu)造器, 既和Array一樣的 「返回類型的類型」, 然而, TypeScript 里根本無法聲明"一個類型參數(shù)為類型構(gòu)造器"。
正如示例中type T
OK, 我們假設(shè)TypeScript 支持聲明"一個類型參數(shù)為類型構(gòu)造器", 讓我們再來看看Apply f => f (a → b) → f a → f b該怎么翻譯:
type AP =(f: F<((a: A) => B)>) => (fa: F) => F;
這里的F可以理解為一種 「上下文」, 這段類型簽名可以先簡單地理解為:
將一個包裹在上下文中的「函數(shù)」取出, 再將另一個包裹在上下文中的「值」取出, 調(diào)用函數(shù)后, 將函數(shù)的返回值重新包裹進上下文中并返回。
這里的 「上下文」 是一個泛指, 比如我們可以將其特異化(specialize)為 Promise :
type AP = (f: Promise<((a: A) => B)>) => (fa: Promise) => Promise;
const ap: AP = (f) => fa => f.then(ff => fa.then(ff));
ap? 或說 Apply 作為函數(shù)式編程中的一種常見抽象, 有非常重要重要的學(xué)習(xí)意義, 但其抽象的解析超出本文范圍, 在這里我們只聚焦于「是什么」, 暫不考慮「為什么」。
那么, (r → a → b) → (r → a) → (r → b)與Apply f => f (a → b) → f a → f b是什么關(guān)系?
他們之間是同父異母的關(guān)系, (r → a → b) → (r → a) → (r → b)?是對Apply f => f (a → b) → f a → f b的特異化, 正如我們對Promise 做的那樣。
函數(shù)也可以是一個 「上下文」?
答案是可以的, 我們可以將一個一元函數(shù)a -> b?理解為"一個包裹在上下文中的b?, 只不過為了獲取這個b?, 需要先傳入一個a。
先看看 Haskell 對ap 的定義:
instance Applicative ((->) r) where
(<*>) f g x = f x (g x)
替換為TypeScript 的實現(xiàn), 我們將上面的Promise 的例子稍微修改下, 得出:
type F = (a: any) => A;
type AP = (f: F<((a: A) => B)>) => (fa: F) => F;
const ap: AP = f => fa => {
return (r) => f(r)(fa(r));
}
同樣的, 我們得到Apply 特異化為Array 的實現(xiàn):
type AP = (f: Array<((a: A) => B)>) => (fa: Array) => Array;
const ap: AP = f => fa => {
return f.flatMap(ff => fa.map(ff));
};
綜上所述, 我們可以得出結(jié)論:
ap的類型簽名[a → b] → [a] → [b]和(r → a → b) → (r → a) → (r → b)是Apply f => f (a → b) → f a → f b的特異化。
網(wǎng)站標(biāo)題:Ramda哪些讓人困惑的函數(shù)簽名規(guī)則
當(dāng)前地址:http://www.dlmjj.cn/article/dpdjdcj.html


咨詢
建站咨詢
