Heaven's Kitchen

○ JavaScriptで無限リスト

IT戦士amachangのこのエントリーを見てJavaScriptで無限リストをやってみたくなった。amachangのやりたいことをあんまり理解してないので,関係ないかも知れないけど…。

無限リストは先頭要素の値と,n番目の要素の値からn+1番目の要素を計算する関数の組で表現できる。

JSにはタプルはないので,配列でこの組を保持することにして1から始まる自然数の無限列は次のような感じになる。

var nat = [1, function (x) { return x + 1; }];

このnatという無限列の10番目を取り出すには

function next(n) {
    return [n[1](n[0]), n[1]];
}

function pos(ix, n) {
    for (var i=0;i<ix;i++) {
	n = next(n);
    }
    return n[0];
}

こんな感じのpos関数を作っといて

print(pos(9, nat)); // 10

とかやればいい。

調子にのってHaskellのtakeやmap風のこともやってみる。

function take(n, inflist) {
    var ary = [];
    for (var i=0;i<n;i++) {
	ary.push(inflist[0]);
	inflist = next(inflist);
    }
    return ary;
}

function map(func, inflist) {
    var n = inflist;
    return [func(n[0]), 
	    function(x) {
		n = next(n);
		return func(n[0]);
	    }];
}

var evens = map (function(x) { return x*2; }, nat);

print(take(10, nat)); // 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
print(take(10, evens)); // 2, 4, 6, 8, 10, 12, 14, 16, 18, 20

素数の列を

var prime = [2, function (prime) {
	prime++;
    outloop:
	while(true) {
	    for(var i=2;i<=Math.sqrt(prime);i++) {
		if (prime % i == 0) {
		    prime++;
		    continue outloop;
		}
	    }
	    return prime;
	}
    }];

と作っておけば,

print(pos(99, prime)); // 541 (100番目の素数)
print(take(100, prime)); // 100個まで素数を列挙

みたいな感じで遊べる。

プログラミング in OCamlっていう本を参考にしているので,考え方はOCaml風(?).もっとJavaScript風の書き方がありそうだなぁ。

○ JavaScriptで遅延評価

amachangさんが試行錯誤しているのを見て,ついかっとなってやってしまった。

少し長いけど,ようするにフィボナッチ数列をHaskell風に

cons(0, cons(1, zipWith(add, fib, cdr(fib))))

と書きたかった。

以下が実装。

(* 16:00 修正: ひろきのだいちくんの指摘により,selfだったところをidentityに変えました。 ついでに,使ってなかった関数を消してすっきりさせました。*)

function Cons (car, cdr) {
    if (car == null || cdr == null) {
	throw "illegal object.";
    }
    this.car = car;
    this.cdr = cdr;
}

function Action (act) {
    if (act == null || !(act instanceof Function)) {
	throw "illegal object.";
    }
    this.action = act;
}

function Thunk (any) {
    if (any instanceof Thunk) {
	this.value = any.value;
	this.evaled = any.evaled;
    } else {
	this.value = any;
	this.evaled = !(any instanceof Action);
    } 
}

var _ = function (f) { return new Thunk(f); };
var action = function (f) { return _(new Action(f)); };
var nil = function () { };
var identity = function () { return this; };

Cons.prototype.run = identity;
Number.prototype.run = identity;
String.prototype.run = identity;
Boolean.prototype.run = identity;
Function.prototype.run = identity;
nil.run = identity;
nil.toString = function () { return "nil";};
Action.prototype.run = function () { 
    return this.action(); 
};

Thunk.prototype.run = function () {
    if (this.evaled) {
	return this.value;
    }
    this.value = this.value.run();
    while (this.value instanceof Thunk) {
	this.value = this.value.run();
    }
    this.evaled = true;
    return this.value;
};

Thunk.prototype.toString = function () {
    return this.run().toString();
};

Cons.prototype.toString = function () {
    var str = "(";
    var car = this.car.run();
    var cdr = this.cdr.run();
    while (cdr instanceof Cons) {
	str += (car == nil) ? "" : car;
	car = cdr.car.run();
	cdr = cdr.cdr.run();
	if (car != nil) str += " ";
    }
    str += ((car == nil) ? "" : car);
    str += ((cdr == nil) ? "" : " . " + cdr);
    return str + ")";
}

/*** premitives ***/

function cons (car, cdr) {
    return action(function () { return new Cons(car, cdr) });
}

function car (val) {
    return action(function () {
	    val = val.run();
	    if (val == nil) {
		return nil;
	    }
	    if (!(val instanceof Cons)) {
		throw "cons applyed to non-Cons object.";
	    }
	    return val.car;
	});
}

function cdr (val) {
    return action(function () {
	    val = val.run();
	    if (val == nil) {
		return nil;
	    }
	    if (!(val instanceof Cons)) {
		throw "cons applyed to non-Cons object.";
	    }
	    return val.cdr;
	});
}

/*** operators ***/

function decr (n) {
    return action(function () {
	    n = n.run() - 1;
	    return n;
	});
}

function incr (n) {
    return action(function () {
	    n = n.run() + 1;
	    return n;
	});
}

/*** list ***/

function nth (n, xs) {
    return action(function () {
	    if (n.run() <= 0) {
		return car(xs);
	    }
	    return nth(decr(n), cdr(xs));
	});
}

function dup (lst) {
    return action(function () {
	    if (lst.run() == nil) {
		return nil;
	    }
	    return cons(car(lst), dup(cdr(lst)));
	});
}

function append (xs, ys) {
    return action(function () {
	    if (xs.run() == nil) {
		return dup(ys);
	    }
	    return cons(car(xs), append(cdr(xs), ys));
	});
}

function take (n, lst) {
    return action(function () {
	    if (lst.run() == nil || n.run() == 0) {
		return nil;
	    }
	    return cons(car(lst), take(decr(n), cdr(lst)));
	});
}

function zipWith (op, xs, ys) {
    return action(function () {
	    if (xs.run() == nil || ys.run() == nil) {
		return nil;
	    }
	    return cons(op.run()(car(xs).run(), car(ys).run()), 
			zipWith(op, cdr(xs), cdr(ys)));
	});
}

/*** infinity ***/

function cycle (lst) {
    return action(function () {
	    return append(lst, cycle(lst));
	});
}

function from (n) {
    return action(function () {
	    return cons(n, from(incr(n)));
	});
}

ここまで作れば後は

var fib = action(function () {
	var add = function (x, y) { return x + y; };
	return cons(0, cons(1, zipWith(add, fib, cdr(fib))));
    });

でフィボナッチの無限リストが!!

というわけで実行してみる。

print(take(10, cycle(cons(1,cons(2,nil))))); // (1 2 1 2 1 2 1 2 1 2)
print(take(10, from(3))); // (3 4 5 6 7 8 9 10 11 12)
print(take(10, fib)); // (0 1 1 2 3 5 8 13 21 34)
print(nth(99, fib)); // 218922995834555200000

あってるかどうかわからんけど,これやるのに一晩かかったので解説は後日気が向いたら…。

○ 続JavaScriptで無限リスト

一個前のエントリーの続き。

ひろきのだいちくんの例を見てから,改めて自分の例についてちょっと考えた.

n番目からn+1番目を作る関数が引数としてn番目の値しか受け取れなくて,フィボナッチとか書きたいときに,なんかスマートじゃない。

次の値を計算する関数が,値と一緒にさらに次の値を計算する関数を返せばいい。てか参考にした本

isbn:4774132640

では最初からそうなってた(爆

自然数の場合は現在の値から次の値を作り出すには常に1を足すだけでよいから簡単。

var nat = [1, function (x) { return [x + 1, arguments.callee]; }];

フィボナッチだと次の値を作り出すために,現在の値とひとつ前の値が必要なので少し複雑。

var fib = [0, function (x) { 
	var succ = function (x) {
	    return function(y) {
		return [x+y, succ(y)];
	    };
	};
	return [x+1, succ(x)];
    }];

で,pos, take, mapはこんな感じか。

function next(n) {
    return n[1](n[0]);;
}

function pos(ix, n) {
    for (var i=0;i<ix;i++) {
	n = next(n);
    }
    return n[0];
}

function take(n, inflist) {
    var ary = [];
    for (var i=0;i<n;i++) {
	ary.push(inflist[0]);
	inflist = next(inflist);
    }
    return ary;
}

function map(func, n) {
    var succ = function (n) {
	return function (_) {
            n = next(n);
	    return [func(n[0]), succ(n)];
	};
    };
    return [func(n[0]), succ(n)];
}

実行してみる。

print(take(10, fib)); //0,1,1,2,3,5,8,13,21,34
print(pos(99, fib)); // 218922995834555200000

結局全然JavaScript風じゃないっていう。

元のamachangのエントリーの弾さんのコメントの意味が全然わからないよぅ(汗

○ 世界のナベアツ問題

via. 日本野望の会ブログ: http://www.yabooo.org/archives/60

「3の倍数と3の付く数字だけアホになり、8の倍数だけ気持ち良くなります」

というやつ。

Haskellで剰余も文字列マッチも使わずにやってみた。

import Data.List

mul_of_3 = cycle [False, False, True]

mul_of_8 = cycle [False, False, False, False, False, False, False, True]

include_3 = iter ([False, False, True] ++ (replicate 7 False)) 1
    where
      expand = concatMap (replicate 10)
      iter bs n = let rest = drop n $ iter (expand bs) (n*10)
                  in bs ++ (zipWith (||) (cycle bs) rest)

merge x y z n = show n ++ 
                (if x || y then "~~~" else "") ++
                (if z then "ooOOo" else "")

nabeatsu = zipWith4 merge include_3 mul_of_3 mul_of_8 [1..]

main = mapM_ putStrLn $ take 40 nabeatsu

ghcじゃ日本語リテラル使えないorz

○ JavaScriptでカプセル化

またしても野望の会ブログに反応してみる。

http://www.yabooo.org/archives/53

JavaScriptではC++やJavaと違ってprivateなインスタンス変数や,メソッドが定義できないのでなんとかしましょうという話。

同じようなことがPerlでもやられていて,Perl Hacksでは裏返しオブジェクトというのが紹介されています。上記のエントリーと同じ発想で,クロージャを使うわけですがオブジェクトの保存場所が微妙に違います。JavaScriptでやるとこんな感じ?

var Class = {
    'create': function (tmpl) {
	var repos = [];
	var klass = function () {
	    this._id = repos.length;
	    repos.push({});
	    tmpl.initialize.apply(repos[this._id], arguments);
	};
	for (var i in tmpl) {
	    if (i == 'initialize') continue;
	    if (typeof tmpl[i] == 'function')
		klass.prototype[i] = function () {
		    return tmpl[i].apply(repos[this._id], arguments);
		};
	}
	klass.extend = function (obj) {
	    for (var i in obj) {
		klass.prototype[i] = function () {
		    return obj[i].apply(repos[this._id], arguments);
		};
	    }
	};
	return klass;
    }
};

var Foo = Class.create({
	'initialize' : function () {
	    this.x = 100;
	},
	'getX' : function () {
	    return this.x;
	}
    });

var foo = new Foo;

print(foo.getX()); // 100
print(foo.x);      // undefined

メソッドは全部パブリックだし,いろいろ手抜きだけどとりあえず隠蔽はできてます。クラスのコンストラクタそのものをクロージャにしてしまうとこがポイントですね。

メソッドはprototype経由で呼び出されるので,オブジェクトをnewする度に発生する,メモリ消費のオーバヘッドもありません(たぶん

さらに,あとからメソッドを追加したくなった場合は

Foo.extend({
	'printX' : function () {
	    print(this.x);
	}
    });

foo.printX(); // 100

とかできて,うれしい気持ち…。

○ Perlの罠1

テスト用のコードでA.pm, B.pm, C.pmとか作っていろいろやっていたら,どうも挙動がおかしい。

で小一時間悩んで思い出した。Bモジュールって標準モジュールに同名のやつがいる!!

Valid XHTML 1.0! Valid CSS!