第 11 回 FLOSS 桜山勉強会発表資料 ― 2008年04月02日 01時37分
先日名古屋で開かれた FLOSS 桜山の第 11 回勉強会で、JavaScript 1.7 で導入されたジェネレータに関する話をしてきました。発表資料などは以下の通りです。
- スライド「JavaScript のジェネレータ」
- Haskell のリストを模倣する JavaScript ソース
- 上記の JavaScript ソースをその場で実行できるようにしたもの (Firefox 3 Beta など、JavaScript 1.8 に対応した環境が必要)
補足 (質疑応答で出た内容も含む)
ジェネレータ、配列内包、分割代入は JavaScript 1.7 から、関数の省略表記、ジェネレータ内包、正規表現の先頭固定 (sticky) オプションは JavaScript 1.8 からの対応です。Firefox 2 は JavaScript 1.7 に、Firefox 3 は JavaScript 1.8 に対応していますが、いくつかの機能を使うためには <script type="application/javascript; version=1.7">
のように明示的にバージョン指定をしなくてはいけません。
「Haskell のリストっぽいもの」でやってることは、「無限リストと遅延評価」の「ジェネレータを使った無限リスト」および「JavaScript でカリー化、再び」の続きです。curry 関数を関数オブジェクトのメソッドとし、以下のような cons 関数を使っています。
const cons = function (head, tail) {
yield head;
for (var i in tail)
yield i;
}.curry();
print 関数は、複数の引数を与えるとそれらをスペース区切りで出力する関数です。配列内包との組み合わせは以下のように解釈されます。
var list = cons(1, cons(2, cons(3, nil())));
print.apply(null, [i for (i in list)]);
// ↓
print.apply(null, [1, 2, 3]);
// ↓
print(1, 2, 3)
// => 1 2 3
JavaScript のジェネレータ機能は基本的に Python のそれと同じですが、ジェネレータイテレータオブジェクトの close メソッドの動作が異なります。close メソッドが呼び出されたとき、Python ではジェネレータの実行を再開し、再開地点で GeneratorExit 例外を投げますが、JavaScript ではジェネレータの実行を再開し、再開地点で return 文に出会ったかのようにジェネレータの実行を終了します。また、Pyothon では GeneratorExit、StopIteration 例外が close メソッドの呼び出し元に伝播しませんが、JavaScript ではジェネレータ内で投げられたすべての例外が close メソッドの呼び出し元に伝播します。
# Python 2.5
def gen():
try:
yield
except:
print "in except/catch clause"
finally:
print "in finally clause"
raise StopIteration
g = gen()
g.next()
g.close()
# => in except/catch clause
# => in finally clause
// JavaScript 1.7
function gen() {
try {
yield;
} catch (ex) {
print("in except/catch clause");
} finally {
print("in finally clause");
throw StopIteration;
}
}
var g = gen();
g.next();
g.close();
// => in finally clause
// => uncaught exception: [object StopIteration]
配列内包およびジェネレータ内包の式部分には、関数呼び出しやオブジェクトの生成などを含む任意の式を掛けます。内包表記自体も式なので、入れ子にして使うこともできます。
[[j for (j in enumFromTo(1, i))]
for (i in enumFromTo(1, 4))].toSource();
// => [[1], [1, 2], [1, 2, 3], [1, 2, 3, 4]]
// enumFromTo 関数については後述
配列内包もジェネレータ内包も、for 節を重ねたり if 節を付加したりできます。
print.apply(null,
[x.toSource()
for (x in ([i, j] for (i in enumFromTo(1, 4))
for (j in enumFromTo(1, 4))
if (i < j)))]);
// => [1, 2] [1, 3] [1, 4] [2, 3] [2, 4] [3, 4]
正規表現の先頭固定オプションは、字句解析のときなどに使えるんじゃないかと思っています。ほかの言語を使ったりほかの手法を使ったりした場合に比べて、速度的にどうこうというのは調べていません。
現時点での JavaScript 1.7/1.8 の使いどころは、Firefox の拡張機能内部などに限られてくると思います。Web 上で気軽に使えるようになるのは果たしていつのことやら。
おまけ (関数合成)
本題からずれているような気がしたのと時間の都合で発表からははずしましたが、Haskell の .
演算子のようなことをする compose 関数も作ってみました。
const compose = (function (f, g)
(function () f(g.apply(null, arguments))).lengthIs(g.length).curry()
).curry();
lengthIs メソッドがやっていることは「JavaScript でカリー化、再び」の setParameterLength 関数と同じです。引数 i と j が与えられると、i から j までの値を収めたリストを返す enumFromTo 関数なども定義して、Haskell での階乗を求める関数の書き方を模倣してみます。
-- Haskell
fact = product . enumFromTo 1
const enumFromTo = function (from, to) {
if (from > to) return;
yield from;
for (var i in enumFromTo(from + 1, to))
yield i;
}.curry();
const foldl = function (folder, initial, list) {
try {
return foldl(folder, folder(initial, list.next()), list);
} catch (ex if ex instanceof StopIteration) {
return initial;
}
}.curry();
const mul = (function (a, b) a * b).curry();
const product = foldl(mul, 1);
const fact = compose(product, enumFromTo(1));
print.apply(null, [fact(i) for (i in enumFromTo(1, 5))]);
// => 1 2 6 24 120
別の書き方も試してみます。
-- Haskell
fact n = (foldl (.) id [\x -> x * k | k <- [1..n]]) 1
const id = (function (x) x).curry();
const fact = function (n)
foldl(compose, id, (mul(i) for (i in enumFromTo(1, n))))(1);
print.apply(null, [fact(i) for (i in enumFromTo(1, 5))]);
// => 1 2 6 24 120
ちなみにこの enumFromTo 関数で無限リストを作ることもできます。
print.apply(null,
[i for (i in take(10, enumFromTo(1, Infinity)))]);
// => 1 2 3 4 5 6 7 8 9 10
コメント
トラックバック
このエントリのトラックバックURL: http://nanto.asablo.jp/blog/2008/04/02/2927294/tb
コメントをどうぞ
※メールアドレスとURLの入力は必須ではありません。 入力されたメールアドレスは記事に反映されず、ブログの管理者のみが参照できます。
※投稿には管理者が設定した質問に答える必要があります。