- 原文地址:Composable Datatypes with Functions
- 原文做者:
Eric Elliott- 譯文出自:掘金翻譯計劃
- 本文永久連接:github.com/xitu/gold-m…
- 譯者:yoyoyohamapi
- 校對者:IridescentMia lampui
(譯註:該圖是用 PS 將煙霧處理成方塊狀後獲得的效果,參見 flickr。)javascript
注意:這是 「軟件編寫」 系列文章的第十部分,該系列主要闡述如何在 JavaScript ES6+ 中從零開始學習函數式編程和組合化軟件(compositional software)技術(譯註:關於軟件可組合性的概念,參見維基百科 Composability)。後續還有更多精彩內容,敬請期待!
<上一篇 | << 返回第一章前端
在 JavaScript 中,最簡單的方式完成組合就是函數組合,而且一個函數只是一個你可以爲之添加方法的對象。換言之,你能夠這麼作:java
const t = value => {
const fn = () => value;
fn.toString = () => `t(${ value })`;
return fn;
};
const someValue = t(2);
console.log(
someValue.toString() // "t(2)"
);複製代碼
這是一個返回數字類型實例的工廠函數 t
。可是要注意,這些實例不是簡單的對象,它們是函數,而且是可組合的函數。假定咱們使用 t()
來完成求和任務,那麼當咱們組合若干個函數 t()
來求和也就是合情合理的。 react
首先,假定咱們爲 t()
確立了一些規則(====
意味着 「等於」):android
t(x)(t(0)) ==== t(x)
t(x)(t(1)) ==== t(x + 1)
在 JavaScript 中,你也能夠經過咱們建立好的 .toString()
方法進行比較:ios
t(x)(t(0)).toString() === t(x).toString()
t(x)(t(1)).toString() === t(x + 1).toString()
咱們也能將上述代碼翻譯爲一種簡單的單元測試:git
const assert = {
same: (actual, expected, msg) => {
if (actual.toString() !== expected.toString()) {
throw new Error(`NOT OK: ${ msg } Expected: ${ expected } Actual: ${ actual } `);
}
console.log(`OK: ${ msg }`);
}
};
{
const msg = 'a value t(x) composed with t(0) ==== t(x)';
const x = 20;
const a = t(x)(t(0));
const b = t(x);
assert.same(a, b, msg);
}
{
const msg = 'a value t(x) composed with t(1) ==== t(x + 1)';
const x = 20;
const a = t(x)(t(1));
const b = t(x + 1);
assert.same(a, b, msg);
}複製代碼
起初,測試會失敗:github
NOT OK: a value t(x) composed with t(0) ==== t(x)
Expected: t(20)
Actual: 20複製代碼
可是咱們通過下面 3 步能讓測試經過:npm
fn
變爲 add
函數,該函數返回 t(value + n)
,n
表示傳入參數。t
添加一個 .valueOf()
方法,使得新的 add()
函數可以接受 t()
返回的實例做爲參數。 +
運算符會使用 n.valueOf()
的結果做爲第二個操做數。Object.assign()
將 toString()
,.valueOf()
方法分配給 add()
函數將 1 至 3 步綜合起來獲得:編程
const t = value => {
const add = n => t(value + n);
return Object.assign(add, {
toString: () => `t(${ value })`,
valueOf: () => value
});
};複製代碼
以後,測試便能經過:
"OK: a value t(x) composed with t(0) ==== t(x)"
"OK: a value t(x) composed with t(1) ==== t(x + 1)"複製代碼
如今,你可使用函數組合來組合 t() ,從而達到求和任務:
// 自頂向下的函數組合:
const pipe = (...fns) => x => fns.reduce((y, f) => f(y), x);
// 求和函數爲 pipeline 傳入須要的初始值
// curry 化的 pipeline 複用度更好,咱們能夠延遲傳入任意的初始值
const sumT = (...fns) => pipe(...fns)(t(0));
sumT(
t(2),
t(4),
t(-1)
).valueOf(); // 5複製代碼
不管你的數據形態是什麼樣子的,只要它存在有意義的組合操做,上面的策略都能幫到你。對於列表或者字符串來講,組合可以完成鏈接操做。對於 DSP(數字信號處理)來講,組合完成的就是信號的求和。固然,其餘的操做也能爲你帶來想要的結果。那麼問題來了,哪一種操做最能反映組合的觀念?換言之,哪一種操做能更受益於下面的代碼組織方式:
const result = compose(
value1,
value2,
value3
);複製代碼
Moneysafe 是一個實現了這個可組合的、函數式數據類型風格的開源庫。JavaScript 的 Number
類型沒法精確地表示美分的計算:
.1 + .2 === .3 // false複製代碼
Moneysafe 經過將美圓類型提高爲美分類型解決了這個問題:
npm install --save moneysafe複製代碼
以後:
import { $ } from 'moneysafe';
$(.1) + $(.2) === $(.3).cents; // true複製代碼
ledger 語法利用了 Moneysafe 將通常的值提高爲可組合函數的優點。它暴露一個簡單的、稱之爲 ledger 的函數組合套件:
import { $ } from 'moneysafe';
import { $$, subtractPercent, addPercent } from 'moneysafe/ledger';
$$(
$(40),
$(60),
// 減去折扣
subtractPercent(20),
// 上稅
addPercent(10)
).$; // 88複製代碼
該函數的返回值類型是提高後 money 類型。該返回值暴露一個 .$
getter 方法,這個 getter 可以將內部的浮點美分值四捨五入爲美圓。
該結果是執行 ledger 風格的金幣計算一個直觀反映。
克隆 Moneysafe 倉庫:
git clone git@github.com:ericelliott/moneysafe.git複製代碼
執行安裝過程:
npm install複製代碼
運行單元測試,監控控制檯輸出。全部的用例都會經過:
npm run watch複製代碼
打開一個新的終端,刪除 moneysafe 的實現:
rm source/moneysafe.js && touch source/moneysafe.js複製代碼
回到以前的終端窗口,你將會看到一個錯誤。
你如今的任務是利用單元測試輸出及文檔的幫助,從頭實現 moneysafe.js
並經過全部測試。
下一篇: JavaScript Monads 讓一切變得簡單 >
想學習更多 JavaScript 函數式編程嗎?
跟着 Eric Elliott 學 Javacript,機不可失時再也不來!
Eric Elliott 是 「編寫 JavaScript 應用」 (O’Reilly) 以及 「跟着 Eric Elliott 學 Javascript」 兩書的做者。他爲許多公司和組織做過貢獻,例如 Adobe Systems、Zumba Fitness、The Wall Street Journal、ESPN 和 BBC 等 , 也是不少機構的頂級藝術家,包括但不限於 Usher、Frank Ocean 以及 Metallica。
大多數時間,他都在 San Francisco Bay Area,同這世上最美麗的女子在一塊兒。
掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 Android、iOS、React、前端、後端、產品、設計 等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃、官方微博、知乎專欄。