嘗試在JavaScript中構建一個"Maybe"檢測器

翻譯原文出處:Building a Maybe in JavaScript
鄙人翻譯略差且略有出入,別見笑。javascript

不少時候咱們會碰到:Uncaught TypeError: Cannot read property 'x' of undefined(沒法讀取未定義的屬性「x」)。
我猜,若是你正好看到這個你之前不單隻碰過還歷歷在目的東西,可能有那麼一刻想把顯示器給砸了。
這裏想起了咱們尊敬的計算機領域的爵士——託尼·霍爾;他在Infoq辦的大會演講時,用到的主題是:「Null References: The Billion Dollar Mistake」(Null 引用:一個十億美圓級別的錯誤),講演摘要中這樣寫的:
「我把Null引用稱爲本身的十億美圓錯誤。它的發明是在1965年,那時我用一個面嚮對象語言( ALGOL W )設計了第一個全面的引用類型系統。個人目的是確保全部引用的使用都是絕對安全的,編譯器會自動進行檢查。可是我未能抵禦住誘惑,加入了Null引用,僅僅是由於實現起來很是容易。它致使了數不清的錯誤、漏洞和系統崩潰,可能在以後40年中形成了十億美圓的損失。近年來,你們開始使用各類程序分析程序,好比微軟的PREfix和PREfast來檢查引用,若是存在爲非Null的風險時就提出警告。更新的程序設計語言好比Spec#已經引入了非Null引用的聲明。這正是我在1965年拒絕的解決方案。」html

那十億美圓級別的錯誤

幸運的是,咱們可使用一些功能性的編程技術,以清潔、簡潔和可靠的方式緩解這帶來疼痛。讓咱們想象一下,咱們要從下面的對象中提取屬性「c」的值,並附加字符串「is great」。前端

const a = {
    b: {
        c: "fp"
    }
};

咱們使用的簡單方法多是:java

const appendString = (obj) =>
    obj.b.c + " is great";
    
appendString(a);

這樣的寫法很棒,但可悲的是a對象並不是是一成不變的。所以,咱們收回的數據有時會採起到如下的形式:react

const a = {
    b: {}
};

// or

const a = {};

當這個時候咱們調用了appendString函數時,整個宇宙將會爆炸的...git

沒法讀取未定義的屬性「c」

這個時候可能要咱們對函數的傳參進行空檢查:github

const appendString = (obj) => {
    if (!obj || !obj.b || !obj.b.c || !) return null;
    return obj.b.c + " is great";
}

這是有效的,但它看起來很醜陋和很容易出錯。咱們必須對傳參進行每種類型的對象定義特定的(正確的)空檢查,這是否是頗有趣(複雜)。
哈哈哈,這個時候可能Maybe就派得上場了。編程

Maybe的基本用法

基本上,咱們都會將要構建的對象封裝其值可能爲null的概念,而且考慮到隨之而來的複雜性。在學習Elm(一門專一於Web前端的純函數式語言)以後,我會在Maybe上的封裝了兩個概念狀態Maybe.justMaybe.nothing。對於初學者,咱們簡單地定義一個返回一個布爾值的isNothing方法,告訴咱們Maybe是否不包含任何內容:c#

const isNullOrUndef = (value) => value === null || typeof value === "undefined";

const maybe = (value) => ({
    isNothing: () => isNullOrUndef(value)
});

甚至使用一個簡單的工廠函數來建立咱們的Maybe - 考慮到日後可能會添加更多的方法,咱們將使用一個對象來定義它:segmentfault

const Maybe = {
    just: maybe,
    nothing: () => maybe(null)
};

因此如今咱們能夠這樣作:

const maybeNumberOne = Maybe.just("a value");
const maybeNumberTwo = Maybe.nothing();

maybeNumberOne.isNothing(); // false
maybeNumberTwo.isNothing(); // true

一切都很好,但到目前爲止還不是很實用。編程是關於轉換數據的,因此咱們須要一種改變咱們的Maybe的方法 - 一個map函數。這個map函數將使用一個表示咱們但願進行轉換的函數參數,並返回一個包含轉換結果的新參數。重要的是,若是maybe不包含任何內容,那麼該函數將不會被應用,咱們將返回一個新的maybe.nothing方法。

const maybe = (value) => ({
    isNothing: () => isNullOrUndef(value),
    map: (transformer) => !isNullOrUndef(value) ? Maybe.just(transformer(value)) : Maybe.nothing()
});

如今咱們能夠這樣來調用maybe實現:

const maybeOne = Maybe.just(5);
maybeOne.map(x => x + 1); // Maybe.just(6);

const maybeTwo = Maybe.nothing();
maybeTwo.map(x => x + 1) // Maybe.nothing();

關鍵一點的是maybe.map返回一個新的maybe,因此咱們能夠將這些操做連接在一塊兒。回到咱們如今能夠作的最初的問題:

const a = {
    b: {
        c: "fp"
    }
};

const maybeA = Maybe.just(a)
    .map(a => a.b)
    .map(b => b.c)
    .map(c => c + " is great!");

這裏的好處是,若是鏈中的任何步驟返回null,咱們仍然會獲得結果Maybe.nothing的結果,而不是運行時錯誤。

好了,在Github上面有個maybe.js庫: A Maybe monad implementation in JavaScript:

它比Haskell的實現更加靈活,並且還附帶了一些額外的功能,考慮到
JavaScript的類型系統限制和語言的通常靈活性。

Point-free 鏈式函數

若是你看過我之前發佈的文章柯里化函數,那麼你就會想咱們能夠弄得比這更好。咱們能夠建立從對象中提取命名屬性的高階函數,以及用於追加字符串:

const prop = (propName) => (obj) => obj[propName];
const append = (appendee) => (appendix) = appendee + appendix;

這裏能夠參考知乎上的JavaScript函數式編程(一)裏面的知識點。

因此如今咱們能夠在咱們的map鏈式中使用這個功能:

const a = {
    b: {
        c: "fp"
    }
};

const maybeA = Maybe.just(a)
    .map(prop("b"))
    .map(prop("c"))
    .map(append(" is great!"));

好了,咱們如今終於到了這一步, 咱們已經處理了空檢查,並將其重構爲point-free形式。接下來須要作的就是使邏輯可重用性;咱們想要的是可以將咱們的功能傳遞給一個功能,並應用全部步驟,以便咱們能夠在許多不一樣的Maybe上從新使用提取器:

const extractor = // what we're about to make
    extractor(Maybe.just(a)); // Maybe.just("fp is great")

咱們須要的是一個須要咱們的步驟的函數,而且每一個步驟依次調用咱們Maybe.map方法。咱們將調用函數Maybe.chain,咱們能夠用reducer來實現:

const Maybe = {
    just: maybe,
    nothing: () => maybe(null),
    chain: (...fns) => (input) => fns.reduce((output, curr) => output.map(curr), input)
};

咱們如今能夠構建一個能夠應用於maybe的可用功能:

const appendToC = Maybe.chain(
    prop("b"),
    prop("c"),
    append(" is great!")
);

並將其用於各類輸入:

const goodInput = Maybe.just({
    b: {
        c: "fp"
    }
});

const badInput = Maybe.just({});

appendToC(goodInput); // Maybe.just("fp is great!")
appendToC(badInput); // Maybe.nothing()

雖然我在學習Elm以前不習慣Maybe的價值觀概念,可是他們在JavaScript中不想落後啊。若是咱們簡簡單單地去使用它,就只會使用到基礎方法而已,因此咱們要在它的基礎上添加更多的功能函數。因此我將在稍後閱讀關於使用Maybe的後續文章。

::完畢::

更多閱讀

相關文章
相關標籤/搜索