yaakaito.org

TypeScriptで似非Option型作るよ

Scala, TypeScript

こんにちは!うきょーです! ScalaにOptionというものがあって、これが本当に素晴らしいんですよ。 ところで、TypeScriptでもOption使いたいですよね?作りました。

何が出来るのか

Optionは要するに値があるかもしれない、という型です。 みなさんこういうコードを書くかと思いますが、

1
2
3
4
5
var hoge = fuga.getHoge();
if (!hoge) {
    hoge = "default";
}
return hoge;

これをエレガントにこう書けます

1
return fuga.getHoge().getOrElse(() => "default")

無駄な変数もifもなくて素晴らしいですね。

Scalaじゃないのでダサいところ

match

とりあえずmatchはダサいです。それっぽく書く為に適当なインターフェイスが作ってあって、

1
2
3
4
5
6
7
8
option.match({
    Some: (x) => {

    },
    None: () => {

    }
});

こう書けますが、あんまりかっこ良くないですね。

生成

ScalaのようにSomeSome.applyになるわけでもないので、普通にnewします。

1
2
new Some('a');
new None<string>();

うーん。

flatten

implicitとかないので実行時エラーですね。まあテストで分かるのでこれがそこまで問題になる事はないはず・・・。

コード

こんな感じです

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
module Katana {

    export interface IOptionMatcher<A> {
        Some?(value: A): void;
        None?(): void;
    }

    export interface Option<A> {
        get(): A;
        getOrElse(defaultValue: () => A): A;
        orElse(alternative: () => Option<A>): Option<A>;
        match(matcher: IOptionMatcher<A>);
        map<B>(f: (value: A) => B): Option<B>;
        flatMap<B>(f: (value: A) => Option<B>): Option<B>;
        flatten<B>(): Option<B>;
    }

    export class Some<A> implements Option<A> {

        constructor(private value :A) { }

        get(): A {
            return this.value;
        }

        getOrElse(defaultValue: () => A): A {
            return this.value;
        }

        orElse(alternative: () => Option<A>): Option<A> {
            return this;
        }

        match(matcher: IOptionMatcher<A>) {
            if (matcher.Some) {
                matcher.Some(this.value);
            }
        }

        map<B>(f: (value: A) => B): Option<B> {
            return new Some<B>(f(this.get()));
        }

        flatMap<B>(f: (value: A) => Option<B>): Option<B> {
            return f(this.get());
        }

        flatten<B>(): Option<B> {
            if (this.value instanceof Some) {
                // :-|
                return <Some<B>>(<any>this.value);
            }
            else if (this.value instanceof None) {
                return new None<B>();
            }
            else {
                throw new Error('Cannot prove that.');
            }
            return null;
        }

    }


    export class None<A> implements Option<A> {

        get(): A {
            throw new Error('No such element.');
        }

        getOrElse(defaultValue: () => A): A {
            return defaultValue();
        }

        orElse(alternative: () => Option<A>): Option<A> {
            return alternative();
        }

        match(matcher: IOptionMatcher<A>) {
            if (matcher.None) {
                matcher.None();
            }
        }

        map<B>(f: (value: A) => B): Option<B> {
            return new None<B>();
        }

        flatMap<B>(f: (value: A) => Option<B>): Option<B> {
            return new None<B>();
        }

        flatten<B>(): Option<B> {
            return new None<B>();
        }
    }

}

foreachとか作ってないんですが、それっぽく使えるはずです。