読者です 読者をやめる 読者になる 読者になる

レガシーコード生産ガイド

私に教えられることなら

JavaScriptでクロージャのみでリストを作る

JavaScript

カッコの少ないSchemeを作る - 標高+1mの例がとてもおもしろかったのでJavaScriptでやってみた。

JavaScriptの場合f(g)(h)(car)(cdr)みたいに繋げていけたら面白そうだ。というわけでそういう構造にした。

全コード

どういうレベルを対象にしていいか謎なので説明は省く。自分用のメモ。

consリスト

ほとんど同じなんだけど、list(1)(2)(3)(cdr)(car)(print)で2が出力できたりすると楽しそうだ、ということでcarを値ではなく関数を返すようにして、またconsで作る関数は、受け取ったものが関数ではない場合、本来のcons的な動きをするようにした。

    function car (x, xs) { return function (f) { return f(x); }; };
    function cdr (x, xs) { return function (f) { return xs; }; }

    function cons (x, xs) {
        return function (f) {
            if (typeof f !== "function") {
                return cons(f, cons(x, xs));
            }
            return f(x, xs);
        };
    }

    function list (x) {
        return cons(x, null);
    }

    function print (x) { console.log (x); return x; }

これで、例えばlist(3)(2)(1)(cdr)(cdr)(car)(print)で3が出力される。consされていくので1が先頭。

map, reduce

mapreduceも、全体を囲むのではなく、繋げて書けるようにする。map(inc)で全体をインクリメントしたリストを返す関数を返す(ような動きをする)。

    function map (f) {
        return function loop (x, xs) {
            return cons(f(x),  (xs === null) ? null : xs(loop));
        };
    }

    function reduce (f, init) {
        function result (x) {
            return function (f) {
                return f(x);
            };
        }

        function looper (acc) {
            return function loop (x, xs) {
                if (xs === null) { return result(f(acc, x)); }
                acc = f(acc, x);
                return xs(loop);
            };
        };

        return looper(init);
    }

    function inc (x) { return x + 1; }

reduceで無駄なことしてそうだ……

さらにリストに対して何か関数を適用したあと、その結果をスルーして元のリストをそのまま返すthruと、セパレータ出力用の関数を定義する。

    function thru (f) {
        return function (x, xs) {
            f(x, xs);
            return cons(x, xs);
        };
    }

    var sep = thru(function () { console.log("========================================"); });

すると、次のような変なコードを書けるようになる。

    list (3) (2) (1)
    (map(print))
    (sep)
    (map(inc))
    (map(print))
    (sep)
    (cdr)(cdr)(car)(print) ;

    list (3) (2) (1)
    (reduce( function (acc, x) { return acc + x; }, 0))
    (sep)
    (print) ;

この書き方で自動セミコロン挿入がされないのか不安だけど、とりあえずChromeだと意図した結果になった。次のように出力される。

1
2
3
========================================
2
3
4
========================================
3
========================================
6

iterator, take

無限リストの生成もできる。特にtakeで頭ふにゃふにゃになった。どれが関数で何を取るのか、一つ一つ追っていったらできた。めんどくさがらずに紙に書いたほうが良かった。

    function iterator (from, step) {
        from = (from === undefined) ? 1 : from;
        step = (step === undefined) ? 1 : step;

        function iter (f) {
            from += step;
            return f(from, iter);
        }

        return cons(from, iter);
    }

    function take (n) {
        function end (x) {
            return function (f) {
                return f(x, null);
            };
        }

        function taker (x, xs) {
            n--;
            if (n < 1) { return end(x); }

            return cons(x, xs(taker));
        }

        return taker;
    }

これで次のようなコードを書ける。3から始まる公差2の等差数列の最初の10個を取り、全てに1足して出力する。結果は4 6 8 10...と出力される。

    iterator(3, 2)
    (sep)
    (take(10))
    (map(inc))
    (map(print));

かんそう

  • JavaScriptじゃないみたいな変なコード書けて面白い
  • 頭の体操になって面白い
  • コメントや解説書きたくない、書くの面倒感がすごい
広告を非表示にする