點燈坊

失くすものさえない今が強くなるチャンスよ

Sanctuary 之 Type Checking

Sam Xiao's Avatar 2020-04-19

ECMAScript 不需 Type Annotation 與 Type Coercion 是優點也是缺點,優點是 Codebase 很精簡,不用夾雜 Type 資訊;缺點是這些常是 Bug 來源。Sanctuary 支援 Runtime Type Checking,在 Type 上遠比 Ramda 嚴謹。

Version

Sanctuary 3.1.0
Ramda 0.27.1

Ramda

import { add } from 'ramda'

add(2)(2) // ?
add(2)(true) // ?

以 Ramda 的 add() 為例,其 type signature 明明為 Number -> Number -> Number,當你不小心傳進 true 依然可執行且有結果,因為 true 會 type corecion 成 1,因此結果為 3

type000

Sanctuary

import { add } from 'sanctuary'

add(2)(2) // ?
add(2)(true) // ?

Sanctuary 預設會啟動 type checking,因此傳入 true 會直接 runtime error,而不會 type coercion。

type001

import { create, env } from 'sanctuary'

let { add } = create({ checkTypes: false, env })

add(2)(2) // ?
add(2)(true) // ?

若想取消 type checking,則需改用 create() 建立 add(),並將 checkTypes 設定為 false

type002

Type Checking

import { create, env, Number } from 'sanctuary-def'

let def = create({ checkTypes: true, env })

let add = def('add')({})([Number, Number, Number]) (
  x => y => x + y
)

add(2)(2) // ?
add(2)(true) // ?

事實上自己寫的 function,也能借用 Sanctuary 的 type checking 保護。

第 1 行

import { create, env, Number } from 'sanctuary-def'

sanctuary-def 引入 create()env,將由 create() 定義建立 def()env 則包含 ECMAScript 原生 type 定義。

Number 則為 ECMAScript 型別,可根據使用到 type 自行引入。

第 3 行

let def = create({ checkTypes: true, env })

create() 建立 def()

第 5 行

let add = def('add')({})([Number, Number, Number]) (
  x => y => x + y
)

使用 def() 定義 add()

  • 第一個 argument 定義 function name
  • 第二個 argument 定義 typeclass constraint,若沒使用傳入 empty object 即可
  • 第三個 argument 定義 type signature
  • 第四個 argument 定義實際 function

如此自行定義的 function 也能受到 Sanctuary 的 Type Checking 保護。

type003

Conclusion

  • Sanctuary 的 type checking 解決了 TypeScript 很難使用 curry function 的老問題,天生就是為了 Hindley-Milner type system 設計,而且也支援 typeclass constraint,可以說是 ECMAScript 在 Functional Programming 下最好解決方案

Reference

Sanctuary, Type Checking
Sanctuary, sanctuary-def