👓
TypeScriptで、「値があるときは関数をapply」「Nullishならそのまま」を書きたい
概要
-
T => R
な関数があるとする。 -
T
or Nullish な値があった時、T
の場合のみ関数を適用し、 Nullishならそのままにしておきたい
背景
DDDをやっているとPrimitiveを型に変換したいことが多い。
class UserId {
private value: string;
private constructor(value: string) {
this.value = value;
}
static create(value: string) {
return new UserId(value);
}
}
const userId = UserId.create('1');
その時、null
やundefined
が絡むとハンドリングが手間だし、型推論もうまく作るのが大変。
function convert(id: string | null | undefined): UserId | null | undefined {
return id === null ? null : id === undefined ? undefined : UserId.create(id);
}
const userId2 = convert('2'); // UserId | null | undefined になってしまう
Kotlinの場合
Kotlinには null
しかないので、割と簡単に書ける。
data class UserId(val value: String)
fun convert(id: String?): UserId? {
return id?.let { UserId(it) }
}
safeLet を作った
以下のようなsafeLetという関数を作った。
関数のオーバーロードをフル活用している。もっと綺麗に書けるのかもしれない。
function safeLet<T, R>(value: T, callback: (value: T) => R): R;
function safeLet<T, R>(value: null, callback: (value: T) => R): null;
function safeLet<T, R>(value: undefined, callback: (value: T) => R): undefined;
function safeLet<T, R>(value: T | null, callback: (value: T) => R): R | null;
function safeLet<T, R>(value: T | undefined, callback: (value: T) => R): R | undefined;
function safeLet<T, R>(value: null | undefined, callback: (value: T) => R): null | undefined;
function safeLet<T, R>(value: T | null | undefined, callback: (value: T) => R): T | null | undefined;
/**
* null --> null
* undefined --> undefined
* other --> callback(other)
* */
function safeLet<T, R>(
value: T | null | undefined,
callback: (value: T) => R,
) {
if (value === null) return null;
if (value === undefined) return undefined;
return callback(value);
}
値のチェック
const doubleToString = (num: number): string => `${num * 2}`
const num1 = 1
const res1 = safeLet(num1, doubleToString)
console.log(res1) // "2"
const num2 = null
const res2 = safeLet(num2, doubleToString)
console.log(res2) // null
const num3 = undefined
const res3 = safeLet(num3, doubleToString)
console.log(res3) // undefined
型のチェック
function f1(input: number) {
return safeLet(input, doubleToString) // string
}
function f2(input: null) {
return safeLet(input, doubleToString) // null
}
function f3(input: undefined) {
return safeLet(input, doubleToString) // undefined
}
function f4(input: number | null) {
return safeLet(input, doubleToString) // string | null
}
function f5(input: number | undefined) {
return safeLet(input, doubleToString) // string | undefined
}
function f6(input: null | undefined) {
return safeLet(input, doubleToString) // null | undefined
}
function f7(input: number | null | undefined) {
return safeLet(input, doubleToString) // string | null | undefined
}
まとめ
- 型パズルで疲弊する前に、型定義と実際の値の扱いを別々にやると楽なことがあるぞい
- Kotlinみたいにスコープ演算子を作っていったら楽しいかもしれない
Discussion