Re: HaxeとTypeScriptを両方使ってみた感想

HaxeとTypeScriptを両方使ってみた感想 - ジンジャー研究室 に対してのコメント。コメント欄ではレスを書くには余白が狭すぎです。

if式について

HaxeのifはBool型しか受け付けないため、存在判定はnullとの比較が必須になる。JavaScriptからHaxeに移行するとifの度にコードが膨れ上がってしまう。
同じく、&&と||もBool型でないと使えない。これは正直とても不便だ。

JavaScriptでもたまに問題になる暗黙の型変換でfalseと判定されてしまうケースを考えると、Boolしか受け付けないことには利点があります。私はむしろHaxeの考え方の方が正しいと思っています。

// JavaScript
var i = 0;
if (i) {
    // こっちは通らない
} else {
    // こっちが通ってしまう!
}

enumとパターンマッチ

nullチェックの話とも絡みます。

当初の思惑としては、これがHaxeの大きなアドバンテージになることを期待していたのだが、結局あまり使っていないのでどっちでも良いような気がしている。
Scalaみたいなことがやりたかったのだが、Option型はnullの存在によって破綻したし、Either型はJSONシリアライズできないという弱点があった。

Haxeのnullの扱い(typedef Null<T> = T)に関しては言いたいことはわからないでもないんですが、それでもenumを使わないのは非常にもったいないです。

以下のようなものを定義してみては如何でしょうか?

// Haxe
enum Option<T> {
    Some(x : T);
    None;
}
// Haxe
// inlineを付けてインライン展開するようにしてありますが、このあたりは好み
class OptionTools {
    public static inline function isEmpty<T>(value : Option<T>) {
        return if (value == null) {
            true;
        } else switch (value) {
            case Some(_): false;
            case None: true;
        }
    }

    public static inline function getOrElse<T>(value : Option<T>, defaultValue : T) {
        return if (value == null) {
            defaultValue ;
        } else switch (value) {
            case Some(x): x;
            case None: defaultValue ;
        }
    }

    public static inline function ifSome<T>(value : Option<T>, fn : T -> Void) {
        if (value != null) switch (value) {
            case Some(x): fn(x);
            case None:
        }
    }

    // getOrThrow()とかifNone()とか派生はいくらでも
}

使用例

// Haxe
using OptionTools;

class Sample {
    static function main() {
        var value1 = Option.Some('hoge');
        value1.ifSome(function (x) js.Lib.alert(x));

        // using mixinならnullでもいけちゃう(良い例ではないですが…)
        var value2 : Option<String> = null;
        js.Lib.alert(value2.getOrElse('foo'));
    }
}

こんな感じの物を用意してしまえば、本当にnullチェックが必要な個所は生JavaScriptやDOMをたたく箇所と、Optional Arguments使ってるところぐらいでしょうか。

ついでなので、Optional Argumentsについて触れておきます。私は最近、Optional ArgumentsについてはF# のdefaultArgみたいに以下のように書いてしまっています。

// Haxe
function add(a : Int, ?b : Int) {
    var b = (b != null) ? b : 0;
    return a + b;
}

でもよく考えたら、defaultArgsみたいなinline関数作った方がなお良い気がしますね。

Optionあるならモナド

sledorze/monax · GitHub

タイミングが合わず、まだちゃんと使ったことはないですが…。

連想配列の扱い

ここでは触れないが連想配列の扱いもかなり面倒だった。(Hashクラスがあるが、連想配列との互換がない)
もちろんuntypedなどで回避は出来るが、そのたびに記述が冗長になってしまう。

JavaScript連想配列(Object)を扱いたい場合はDynamicで良いのではないでしょうか?実はHaxeのDynamicには型パラメータを指定することができるので、JavaScriptとの相互運用をする場合には実用上十分ではないかと思います。

// Haxe
var hash : Dynamic<String> = getJsHashMap(); //JavaScript側でHashMapみたく使ってるObjectを取得

var v1 = hash.hoge; // 型推論でちゃんとStringになる
var v2 = hash.fuga;

なお、Dynamic型をHashのようにkeyでループを回すには以下のように書きます。

// Haxe
var data : Dynamic = { foo: 1, bar: 2 };
for (key in Reflect.fields(data)) {
    var value = Reflect.field(data, key);
}

蛇足ですが、Hashではなくtypedef(TypeScriptではinterface)で型を定義できてしまう場合は、当然そちらの方がよいです。