はじめに
天久保 Advent Calendar 2023 の 9 日目の記事として乱入しました。
TypeScript の関数は関数か
答えは否.なぜかって? 次のコードを見てほしい.
const g = (x: NonNullable<unknown>) => x
const h = () => null
const f = () => h() && g(h())
ただし,NonNullable
は null
と undefined
を除外する組み込みの型演算子である (NonNullable<T> = T & {}
).これは短絡評価と呼ばれる技法のごく一般的な例であり,特段何も変なところはない.
しかしだ,ここで TypeScript Compiler (TSC) は g(h())
の部分で「 g
の引数に null
は取れない」と言い出すのである.
Argument of type 'null' is not assignable to parameter of type '{}'.(2345)
「いや,第一項で h()
の値が null | undefined
の場合を除外してるんだって!」と我は思った。なぜこのようなことが起こるのか。Wikipedia には TypeScript が関数型言語だと書いてあった1のに!
答えは簡単だ。JavaScript の関数は非参照透過的だからである。
エセ関西人による非難の嵐
ここで義務教育を経たエセ関西人から非難の嵐が発生した。それは次のようなものである。
エセ関西人「それは関数やない!!! 小学校のとき,関数とは $$x = y \implies f(x) = f(y)$$ を満たす二項関係であることを習ったはずだろ!!!」
そうなのである。関数であれば,第一項と第二項の h()
の返り値が異なってはならないのである。しかし、JavaScript の関数は関数ではない。関数定義で function
キーワードを用いることができるからといって、それは数学的な関数とは異なり、呼び出しごとに返り値は変わりうるのである。むしろ、手続き (procedure) のようなものなのだ。現在の TSC 5系 は関数の純粋透過性を判別できず、それが関数か手続きかを理解しない2。したがって、我々は値を代入 (assignment) してから
const g = (x: NonNullable<unknown>) => x
const h = () => null
- const f = () => h() && g(h())
+ const y = h()
+ const f = () => y && g(y)
と書かなければならない。これが JavaScript/TypeScript が純粋関数型言語ではないと言われる所以である。
-
Wikipedia contributors. (2023, December 15). TypeScript. In Wikipedia, The Free Encyclopedia. Retrieved 20:47, December 19, 2023, from https://en.wikipedia.org/w/index.php?title=TypeScript&oldid=1189970233 ↩
-
型変数
H
をtype H = ReturnType<typeof h>
とすると,type H = null
である. ↩