Jintrick.netagenda2008年02月アーカイブ → 2008年02月06日

Javascriptのstring型とboolean型、oneOfメソッド他

Javascriptの特殊演算子inをPython風に利用する (agenda)のTupleクラスはあまりにも無駄が多く、潜在的な用途に対して実際に可能な用途が無意味に限られてしまうので、Object.prototype.oneOf(arg, arg2..) で「厳密な」比較を行なえるようにと考えてみた。

しかしまだ型だのオブジェクトだののJavascriptの基本部分で混乱することがあるとは。Javascriptの文字列リテラルはstring型なのに、Stringインスタンスであるかのようにプロパティアクセスが可能だ。

型なのにプロパティ
"文字列".length;
"文字列".link();

しかしこの時、メソッド内のthisキーワードは、自身を参照していない。これが型がプロトタイプチェインを通じてプロパティにアクセスできる仕掛けと大いに関係がありそうだが、別に興味はないのでそこは深入りしない。

thisは何
Object.prototype.equals = function(arg){
    return this === arg;
};
alert("文字列".equals("文字列")); // false

調べてみると型が違う:

string型の場合
Object.prototype.tellMyType = function(){
    return typeof this;
};
alert(typeof "文字列"); // string
alert("文字列".tellMyType()); // object

boolean型についても同じ:

boolean型の場合
alert(typeof true); // boolean
alert(true.tellMyType()); // object

仕方ないから両者のみ上書き。

equalsメソッドをオーバーライド
String.prototype.equals = Boolean.prototype.equals = function(arg){
    return this == arg;
};

脳log[2008-02-15] Re: Javascriptのstring型とboolean型、oneOfメソッド他 (agenda)にて、つっこみを頂いていました。その通りだと思います。ありがとうございました。

そしてObject.prototype.oneOfメソッドを書いた。引数が一つしかない場合、Arrayインスタンスであると看做す。

Object.prototype.oneOfメソッド
Object.prototype.oneOf = function(arg /*, arg2, arg3, .. */){
    if (arguments.length >= 2) {
        for (let i=0; i<arguments.length; i++)
            if (this.equals(arguments[i]))
                return true;
        return false;
    }
    
    // 引数一つのみなので、Arrayインスタンスであると推測し、forEachを試す
    try {
        arg.forEach( function(e){
            if (this.equals(e))
                throw StopIteration;
        }, this );
    } catch (err if (err instanceof TypeError)) {
        // forEachメソッドを持っていない := Arrayインスタンスではないと看做す
        return this.equals(arg);
    } catch (err if (err instanceof StopIteration)) {
        // 一致する要素が見つかった
        return true;
    }
    return false;
};

しかし書いてみれば欲が出てくるもので、oneOfの引数が一つでしかもそれがジェネレータ(またはイテレータ)だったら、yieldされるオブジェクト(next()の戻り値)との比較を行なうよう変更したくなった。ジェネレータも__iterator__プロパティを持っていることから、このプロパティを見て判別すれば良さそう。しかもジェネレータは自分自身がイテレータらしく、次の比較はtrueだ。

ジェネレータ自身イテレータと看做せる
function gen(){
    yield null
}
var g = gen();
g === g.__iterator__(); // true

で、改定。

Object.prototype.oneOfメソッド・改
Object.prototype.oneOf = function(arg /*, arg2, arg3, .. */){
    if (arguments.length > 1) {
        for (let i=0; i<arguments.length; i++)
            if (this.equals(arguments[i]))
                return true;
        return false;
    }
    
    
    try {
        // 引数一つのみなので、Arrayインスタンスであると推測しforEachを試す
        arg.forEach( function(e){
            if (this.equals(e))
                throw StopIteration;
        }, this );
    } catch (err if (err instanceof TypeError)) {
        // forEachメソッドを持っていない := Arrayインスタンスではないと看做す
        try {
            // イテレータを試す
            for (let e in arg.__iterator__())
                if (this.equals(e))
                    return true;
        } catch (err if (err instanceof TypeError)) {
            // Arrayでなくイテレータもないので、引数自身と比較する
            return this.equals(arg);
        }
    } catch (err if (err instanceof StopIteration)) {
        // 一致する要素が見つかった
        return true;
    }
    return false;
};

そんなこんなで完成し、便利に使っている。例えばDOMではNode.oneOf(_NodeList)でノードリスト中にノードがあるかどうかの判別が可能に。ただし_NodeListは自作のイテレータだけど。"xhtml".oneOf("html", "xml", "xhtml")のような文字列比較もよく使うが、Python使いにお馴染みのList Comprehensions(Javascript1.7ではArray Comprehensions)と併せて使うときに本領発揮。例えばURL.resolve(url, base)というコードで基底URL(base)に基づきurlを絶対URLに解決できるとする。この時、"dontEnum"というクラス名を持つ要素を除いた文書中の全てのhref属性値のうち、あるURL(url)を参照しているものがあるかどうかは次のように書ける。

oneOfとArray Comprehensions
url.oneOf(
    [URL.resolve(e.href, location.href) for (e in document.selectNodes("/descendant::*[@href]") )
        if (!e.isClassOf("dontEnum")) ]
);

実際にはもっと読みやすいように工夫して書くわけだが、それはコンテクスト依存なので使いやすさは(Python使い以外には)中々伝わらないかもしれない。別にどうでもいいけど。


webmaster@jintrick.net
公開: 2008年02月06日
カテゴリ: Javascript