[譯]藉助函數完成可組合的數據類型(軟件編寫)(第十部分)

藉助函數完成可組合的數據類型(軟件編寫)(第十部分)

Smoke Art Cubes to Smoke — MattysFlicks — (CC BY 2.0)
Smoke Art Cubes to Smoke — MattysFlicks — (CC BY 2.0)

(譯註:該圖是用 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

  1. 將函數 fn 變爲 add 函數,該函數返回 t(value + n)n 表示傳入參數。
  2. 爲函數 t 添加一個 .valueOf() 方法,使得新的 add() 函數可以接受 t() 返回的實例做爲參數。 + 運算符會使用 n.valueOf() 的結果做爲第二個操做數。
  3. 使用 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 SystemsZumba FitnessThe Wall Street JournalESPNBBC 等 , 也是不少機構的頂級藝術家,包括但不限於 UsherFrank Ocean 以及 Metallica

大多數時間,他都在 San Francisco Bay Area,同這世上最美麗的女子在一塊兒。


掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 AndroidiOSReact前端後端產品設計 等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章
相關標籤/搜索