Maybe 使 ECMAScript 更安全
栏目: JavaScript · 发布时间: 6年前
内容简介:ECMAScript 為 Dynamic Type Language,Function 的 Parameter 並不用指定 Type Hint,因此可以傳入任何 Type,理論上必須在 Runtime 使用VS Code 1.33.1Quokka 1.0.212
ECMAScript 為 Dynamic Type Language,Function 的 Parameter 並不用指定 Type Hint,因此可以傳入任何 Type,理論上必須在 Runtime 使用 typeof 做 Type Check,否則依賴 Type Coercion 很容易產生 Bug; Mabye 提供了另外一種方式:只會將正確 Type 進行運算,而不需使用 typeof 檢查。
Version
VS Code 1.33.1
Quokka 1.0.212
Croks 0.11.1
Number
let inc = x => x + 1; let fn = n => inc(n); console.log(fn(2));
很簡單的 inc() ,當輸入為 number 2 時,毫無懸念結果為 3 。
String
let inc = x => x + 1;
let fn = n => inc(n);
console.log(fn('8'));
但傳入改為 string '8' 時,結果為 81 ,且 81 為 string,並不是預期的 number 。
由於 ECMAScript 為 dynamic type language,因此可以傳入任何 type,而當 + 遇到 string 時,會將兩個 operand 都轉成 string , + 從原本的 add() 變成 concat() ,因此最後結果為 string 81
Undefined
let inc = x => x + 1; let fn = n => inc(n); console.log(fn(undefined));
當傳入為 undefined 時,結果為 NaN 。
81 與 NaN 都不是我們預期結果,要避免這些情形發生,唯一的方法就是確認輸入只能是 number ,其他 type 都不可輸入
typeof
let inc = x => x + 1;
let fn = n => typeof n === 'number' ? inc(n) : 0;
console.log(fn(2));
console.log(fn('8'));
console.log(fn(undefined));
為了確保輸入一定是 number ,我們當然可以在 fn() 加上 typeof 判斷,確定是 number 才執行 inc() ,否則傳回 default 值 0 。
如此結果就不會是 string 或 NaN 這些不是我們預期結果。
但這種寫法只能算是 short term solution:
- 每個 function 都必須檢查其 parameter type,弄髒了原本邏輯
- 無法在一般情況就發現錯誤
- 必須考驗 coder 的細心程度,只要不夠細心,unit test 一樣測不到
Wrap into Maybe
import { Maybe } from 'crocks';
let inc = x => x + 1;
let fn = n => n.map(inc);
console.log(fn(Maybe.Just(2)));
引進了 Maybe type,它只有兩種值: Just 與 Nothing :
Just Nothing
以本例而言,我們預期 type 是 number ,是故為 Just ;而 string 與 undefined 都不是我們預期 type,是故為 Nothing 。
import { Maybe } from 'crocks';
由 crocks import 進 Maybe object,它提供了我們使用 Maybe 所需要的 function。
第 4 行
let fn = n => n.map(inc);
Argument n 為 Maybe ,而 Maybe 自帶 map() ,可傳入 function, map() 會幫我們將 Maybe 內的 value 透過傳入的 inc() 改變之,最後回傳 Maybe 。
由於 fn() 回傳為 Maybe ,因此印出 Just 3 ,而非原本的 3 。
目前我們已經將 number 包進 Maybe ,但印出也是 Maybe ,而非原本的 number 3 ,稍後會從 Maybe 萃取出 number
Just
import { Maybe } from 'crocks';
let inc = x => x + 1;
let fn = n => n.map(x => console.log('calling inc()') || inc(x));
console.log(fn(Maybe.Just(2)));
我們雖然將 inc() 傳入 Maybe.map() ,但 inc() 真的有被執行嗎 ?
第 4 行
let fn = n => n.map(x => console.log('calling inc()') || inc(x));
特別在 Maybe.map() 的 callback 加上 console.log() ,測試 inc() 是否有被執行。
由於 console.log() 會回傳 undefined ,視為 falsy value,因此一定會執行 || 右側的 inc(x) 。
console.log() 配合 || 為 ECMAScript 常用測試 callback 手法
印出了 calling inc() ,顯示了若輸入為 Just , 真的有執行 Mapbe.map() 的 callback。
Nothing
import { Maybe } from 'crocks';
let inc = x => x + 1;
let fn = n => n.map(x => console.log('calling inc()') || inc(x));
console.log(fn(Maybe.Nothing()));
inc() 與 fn() 完全不變,只是改將 Nothing 傳入 fn() 。
沒顯示 calling inc() ,表示 inc() 根本沒執行,直接回傳 Nothing 。
只有 Just 才會執行我們 map() 的 function, Nothing 完全不執行,這確保了只有正確 type 才會執行 function,不需要我們自己做 typeof 檢查,也不會產生不是預期結果
safeNum()
import { Maybe } from 'crocks';
let inc = x => x + 1
let safeNum = v => typeof v === 'number' ? Maybe.Just(v) : Maybe.Nothing();
let fn = n => safeNum(n).map(inc);
console.log(fn(2));
console.log(fn('8'));
console.log(fn(undefined));
第 5 行
let fn = n => safeNum(n).map(inc);
由 Just 與 Nothing 我們可發現,只要我們能將 value 包進 Maybe ,儘管不做 typeof 檢查,也能確保不會有不預期結果,所以我們需要 safeNum() 將任何值包進 Maybe 。
第 4 行
let safeNum = v => typeof v === 'number' ? Maybe.Just(v) : Maybe.Nothing();
使用 typeof 判斷是否為 number ,若是 number ,則包成 Just ,否則包成 Nothing 。
Q:到底還是使用了 typeof ?
底層雖然會使用 typeof ,但即將包成 higher order function,請繼續看下去。
Higher Order Function
import { Maybe } from 'crocks';
let inc = x => x + 1
let isNumber = v => typeof v === 'number';
let safe = pred => v => pred(v) ? Maybe.Just(v) : Maybe.Nothing();
let fn = n => safe(isNumber)(n).map(inc);
console.log(fn(2));
console.log(fn('8'));
console.log(fn(undefined));
第 4 行
let isNumber = v => typeof v === 'number';
將 number 寫死在 safeNum() 當然不好,可將此部分抽成 higher order function,將來可透過 function compostion 組合出 safeNum() ,重複使用程度更高。
第 5 行
let safe = pred => v => pred(v) ? Maybe.Just(v) : Maybe.Nothing();
將 safeNum() 重構成更一般性的 safe() higher order function,可透過傳入 predicate 組合出能將各種 type 包進 Maybe 的 function。
第 7 行
let fn = n => safe(isNumber)(n).map(inc);
由 safe(isNumber) 組合出 safeNum() ,如此可安全將任何值包進 Maybe 。
Helper Function
import { inc } from 'ramda';
import { Maybe, safe, isNumber } from 'crocks';
let fn = n => safe(isNumber)(n).map(inc);
console.log(fn(2));
console.log(fn('8'));
console.log(fn(undefined));
事實上 safe() 與 isNumber() ,Crocks 都提供了,可直接使用。
連 inc() 以順便使用 Ramda 版本。
目前只剩下最後一哩路: fn() 回傳為 Maybe 需要解決
Unwrap from Maybe
import { inc } from 'ramda';
import { Maybe, safe, isNumber } from 'crocks';
let fn = n => safe(isNumber)(n).map(inc).option(0);
console.log(fn(2));
console.log(fn('8'));
console.log(fn(undefined));
Crocks 提供了 Maybe.options() ,讓我們提供當 Maybe 為 Nothing 時該如何處理,因為 Nothing 完全不會經過 inc() 運算。
fn() 的 signature 完全沒變,結果也完全沒變。
Comparison
比較這兩種寫法,我們得到了什麼 ?
Imperative
let fn = v => typeof v === 'number' ? inc(v) : 0;
我們必須時時刻刻想著要使用 typeof 檢查,但儘管粗心沒檢查, 一般情況 也不會錯,但特殊狀況就會錯,這就是 bug 來源。
Functional
let fn = n => safe(isNumber)(n).map(inc).option(0);
若使用 FP 的 Maybe ,思維就不一樣了:
- 先組合出能包進 Maybe 的 function,因此會先寫出
safe(isNumber),就算你很粗心也被逼的要寫出來,否則無法繼續 - 接下來要使
Maybe能經過運算,一定得使用map()傳入 function - 若很粗心忘記寫
option(),一般情況顯示Maybe就是錯的,一定會回來補上option(0)
也就是每個步驟都被逼著走,且在 一般情況 就會發現錯誤,不像 imperative 的 typeof 在一般情況不會錯,只有在特殊狀況才會出錯,這就是 bug 來源。
Maybe 還可讓你在寫 unit test 時不用想很奇怪的 test case,因為 Maybe 會確保不會產生不預期結果。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Orange'S:一个操作系统的实现
于渊 / 电子工业出版社 / 2009-6 / 69.00元
《Orange S:一个操作系统的实现》从只有二十行的引导扇区代码出发,一步一步地向读者呈现一个操作系统框架的完成过程。书中不仅关注代码本身,同时关注完成这些代码的思路和过程。本书不同于其他的理论型书籍,而是提供给读者一个动手实践的路线图。读者可以根据路线图逐步完成各部分的功能,从而避免了一开始就面对整个操作系统数万行代码时的迷茫和挫败感。书中讲解了大量在开发操作系统中需注意的细节问题,这些细节不......一起来看看 《Orange'S:一个操作系统的实现》 这本书的介绍吧!