Maybe 使 ECMAScript 更安全
栏目: JavaScript · 发布时间: 5年前
内容简介: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
會確保不會產生不預期結果。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
深入理解 Flask
[美]Jack Stouffer / 苏丹 / 电子工业出版社 / 2016-7-1 / 79.00
Flask 是一种具有平缓学习曲线和庞大社区支持的微框架,利用它可以构建大规模的web应用。学习上手Flask非常轻松,但要深入理解却并不容易。 本书从一个简单的Flask应用开始,通过解决若干实战中的问题,对一系列进阶的话题进行了探讨。书中使用MVC(模型-视图-控制器)架构对示例应用进行了转化重构,以演示如何正确地组织应用代码结构。有了可扩展性强的应用结构之后,接下来的章节使用Flask......一起来看看 《深入理解 Flask》 这本书的介绍吧!
CSS 压缩/解压工具
在线压缩/解压 CSS 代码
html转js在线工具
html转js在线工具