M.Hiroi's Home Page
http://www.geocities.jp/m_hiroi/

JavaScript Programming

お気楽 JavaScript プログラミング超入門

[ PrevPage | JavaScript | NextPage ]

継承

前回は簡単な例題として連結リストという基本的なデータ構造を作成しました。今回は「継承 (inheritance : インヘリタンス) 」について説明します。

●継承とは?

クラスベースのオブジェクト指向では、「クラス (class) 」に「親子関係」を持たせることを「継承」といいます。子供のクラスは親クラスの性質を受け継ぐことができます。プログラミング言語の場合、引き継ぐ性質は定義されたインスタンス変数 (プロパティ) やメソッドなどになります。

プログラムを作る場合、今まで作ったプログラムと同じような機能が必要になることがありますが、継承を使うことでその機能を受け継ぎ、新規の機能や変更される機能だけプログラムする、いわゆる「差分プログラミング」が可能になります。

クラスを継承する場合、その元になるクラスを「スーパークラス」とか「ベースクラス」と呼びます。そして、継承したクラスを「サブクラス」と呼びます。この呼び方は言語によってまちまちで統一されていません。C++の場合は、元になるクラスを基本クラスといい、継承するクラスを派生クラスとか導出クラスといいます。

たとえば、クラス Foo1 を継承してクラス Foo2 を定義しましょう。クラス Foo1 にはメソッド bar() が定義されています。クラス Foo2 にメソッド bar() は定義されていませんが、Foo2 のオブジェクトに対して bar() を呼び出すと、スーパークラス Foo1 のメソッド bar() が実行されるのです。

メソッドの選択は次のように行われます。まず、オブジェクトが属するクラス Foo2 にメソッド bar() が定義されているか調べます。ところが、Foo2 には bar() が定義されていないので、スーパークラスである Foo1 に bar() が定義されているか調べます。ここでメソッド bar() が見つかり、それを実行するのです。このように、メソッドが見つかるまで順番にスーパークラスを調べていきますが、最上位のスーパークラスまで調べてもメソッドが見つからない場合はエラーになります。

継承したクラスのメソッドとは違う働きをさせたい場合、同名のメソッドを定義することで、そのクラスのメソッドを設定することができます。これを「オーバーライド (over ride) 」といいます。メソッドを選択する仕組みから見た場合、オーバーライドは必然の動作です。メソッドはサブクラスからスーパークラスに向かって探索されるので、スーパークラスのメソッドよリサブクラスのメソッドが先に選択されるわけです。

●プロトタイプチェーンによる継承の実現

JavaScript にクラスはありませんが、あるオブジェクトのプロパティやメソッドを引き継ぐことは簡単に行うことができます。その仕組みはとても簡単で、コンストラクタの prototype に継承するオブジェクトをセットするだけです。プロパティの探索はプロトタイプチェーンをたどって行われるので、プロトタイプのオブジェクトがスーパークラス的な役割をはたしていると考えることができます。

簡単な例を示しましょう。次のリストを見てください。

リスト : 継承

// 親
function Foo() {
    this.a = 10;
}
Foo.prototype.get_a = function() { return this.a; }
Foo.prototype.set_a = function(x) { this.a = x; }

// 子
function Bar() {
   this.b = 20;
}

// Foo を継承
Bar.prototype = new Foo();

// Bar 用のメソッドを追加
Bar.prototype.get_b = function() { return this.b; }
Bar.prototype.set_b = function(x) { this.b = x; }

継承のポイントは、コンストラクタ Bar の prototype に Foo のオブジェクトを生成してセットするところです。これを図に示すと次のようになります。

  Constructor
 ┌─ Foo  ─┐      ┌→┌─Object A─┐
 ├─────┤      │  ├──────┤
 │  a = 10  │      │  │get_a, set_a│
 ├─────┤      │  └──────┘
 │prototype ┼───┤
 └─────┘      │
      │             │
   new Foo()         │
      ↓             │
 ┌─Object B─┐←┐│
 ├──────┤  ││
 │     a      │  ││
 ├──────┤  ││
 │ __proto__  ┼──┘
 ├──────┤  │
 │get_b, set_b│  │
 └──────┘  │
                   │
  Constructor      │
 ┌─ Bar  ─┐────new Bar() →┌─Object C─┐
 ├─────┤    │              ├──────┤
 │  b = 20  │    │              │     b      │
 ├─────┤    │              ├──────┤
 │prototype ┼──┴───────┼ __proto__  │
 └─────┘                    └──────┘

                図 : 継承

Foo の prototype は Object A で、ここにメソッド get_a(), set_a() が格納されます。そして、new Foo() で生成した Object B を Bar の prototype にセットします。Object B のプロトタイプチェーンは Object A になります。ここで、new Bar() でオブジェクト Object C を生成すると、そのプロトタイプチェーンは Object B になります。したがって、Object C からプロトタイプチェーンをたどって、Object B に格納されているプロパティ a の値を参照したり、Object A のメソッド get_a(), set_a() を呼び出すことができます。

Bar のメソッドを定義する場合は、prototype に設定した Foo のオブジェクト Object B に追加します。リスト : 継承 では new Foo() で生成した Object B を Bar.prototype にセットし、その中にメソッド get_b(), set_b() を追加しています。もしも、空のオブジェクト { } の中でメソッドを定義して、それを Bar.prototype にセットすると、Foo のオブジェクトを継承することができなくなります。ご注意ください。

それでは実際に試してみましょう。

> a = new Foo()
Foo {a: 10, get_a: function, set_a: function}
> b = new Bar()
Bar {b: 20, a: 10, get_b: function, set_b: function, get_a: function…}
> a.get_a()
10
> b.get_a()
10
> b.get_b()
20

変数 a, b に Foo と Bar のオブジェクトをセットします。オブジェクトを生成するとき、Foo のオブジェクトにはプロパティ a に 10 がセットされ、Bar のオブジェクトにはプロパティ b に 20 がセットされます。a.get_a() はプロパティ a の値を返すので 10 になります。b.get_a() はオブジェクトにプロパティ a がないのでプロトタイプを調べます。プロトタイプは Foo のオブジェクトなので、プロパティ a の値は 10 に初期化されています。したがって、b.get_a() は 10 を返します。

●プロパティの更新

次に、a.set_a(100) でプロパティ a の値を更新します。

> a.set_a(100)
undefined
> a.get_a()
100
> b.get_a()
10

a.get_a() は 100 を返しますが、b.get_a() は 10 のままですね。a.set_a() は変数 a のオブジェクトにアクセスするのですから、他のオブジェクトのプロパティ a が書き換えられることはありません。

それでは、b.set_a(200) はどうなるのでしょうか。次の例を見てください。

> b.set_a(200)
undefined
> b.get_a()
200
> c = new Bar()
Bar {b: 20, a: 10, get_b: function, set_b: function, get_a: function…}
> c.get_a()
10

メソッド set_a() の処理内容は this.a = x なので、アクセス対象となるのは変数 b のオブジェクトです。したがって、変数 b のオブジェクトにプロパティ a が作られて、そこに 200 が代入されます。プロトタイプ (Bar.prototype) のプロパティ a の値が書き換えられるわけではないことに注意してください。このあとは変数 b のオブジェクトにあるプロパティ a の値が参照されます。

実際に、新しい Bar のオブジェクトを生成してみましょう。c.get_a() は 10 を返すので、プロトタイプ (Bar.prototype) のプロパティ a の値は書き換えられていないことがわかります。逆にいえば、プロトタイプのオブジェクトのプロパティを書き換えると、それを参照しているすべてのオブジェクトでプロパティの値が変化します。次の例を見てください。

> b1 = new Bar()
Bar {b: 20, a: 10, get_b: function, set_b: function, get_a: function…}
> b2 = new Bar()
Bar {b: 20, a: 10, get_b: function, set_b: function, get_a: function…}
> b1.get_a()
10
> b2.get_a()
10
> Bar.prototype.a = 100
100
> b1.get_a()
100
> b2.get_a()
100

このように、Bar.prototype.a の値を書き換えると、Bar で生成したオブジェクト b1, b2 のプロパティ a の値も変化します。ただし、b1.set_a(200) のようにプロパティ a の値を書き換えると、オブジェクト b1 の中にプロパティ a が作成されるため、プロトタイプのプロパティ a を参照することはできません。次の例を見てください。

> b2.set_a(1000)
undefined
> b2.get_a()
1000
> Bar.prototype.a = 500
500
> b1.get_a()
500
> b2.get_a()
1000

b2.set_a(1000) を実行すると b2 の中にプロパティ a が生成されて 1000 がセットされます。Bar.prototype.a の値を 500 に変更すると、b1.get_a() は 500 になりますが、b2.get_a() は 1000 のままです。このように、プロパティの値を更新すると、そのオブジェクトのプロパティに値が代入されるため、プロトタイプのプロパティを参照することはできなくなります。

●call() と apply()

プロトタイプのプロパティは、前回説明したように「クラス変数」的な使い方が可能ですが、インスタンス変数 (メンバ変数) として使う場合は、オブジェクトを生成するコンストラクタで初期化しておいたほうがよいでしょう。このとき、プロパティを初期化するコンストラクタを呼び出すことができると便利です。いわゆるスーパークラスのコンストラクタを呼び出すわけです。

JavaScript の場合、関数オブジェクトのメソッド call() と apply() を使うと、どんなメソッドでも呼び出すことができます。call() と apply() の第 1 引数には、操作対象となるオブジェクトを指定します。呼び出すメソッドでは、この値が this になります。グローバルな関数を呼び出す場合は null または undefined を指定します。そのあとに引数を指定します。apply() は配列の要素を引数に展開して呼び出すことができます。

たとえば Bar から Foo を呼び出す場合は次のようになります。

リスト : コンストラクタの呼び出し

function Bar() {
    Foo.call(this);
    this.b = 20;
}

Bar を呼び出すときに生成されたオブジェクト (this) を call() の第 1 引数にセットします。これで、コンストラクタ (関数オブジェクト) Foo にオブジェクトを渡して呼び出すことができます。また、サブクラスでメソッドをオーバーライドしたとき、スーパークラスのメソッドを呼び出す場合にも call() や apply() を使います。

●制限付き連結リスト

それでは簡単な例題として、連結リストを継承して、格納する要素数を制限する連結リストを作ってみましょう。コンストラクタ名は FixedList としました。プログラムは次のようになります。

リスト : 制限付き連結リスト

// コンストラクタ
function FixedList(limit) {
    this.size = 0;
    this.limit = limit;
    List.call(this);      // スーパークラスのコンストラクタを呼び出す
}

// 継承
FixedList.prototype = new List();

// オーバーライド
// データの挿入
FixedList.prototype.insert = function(n, x) {
    if (this.size < this.limit) {
        var result = List.prototype.insert.call(this, n, x);
        if(result != null) this.size++;
        return result;
    }
    return null;
}

// データの削除
FixedList.prototype.remove = function(n) {
    if (this.size > 0) {
        var result = List.prototype.remove.call(this, n);
        if(result != null) this.size--;
        return result;
    }
    return null;
}

制限付き連結リスト (FixedList) は指定した上限までしか要素を格納できません。連結リスト (List) で要素を追加するメソッドは insert() で、削除するメソッドは remove() です。この 2 つのメソッドをオーバーライドすることで、FixedList の機能を実現することができます。

コンストラクタ FixedList では、使用するプロパティ limit と size を初期化します。limit は要素数の上限値を表していて、引数 limit で指定します。size は連結リストに格納されている要素数を表します。あとはスーパークラスのコンストラクタ List を call() で呼び出すだけです。そして、FixedList.prototype に List のオブジェクトを生成してセットします。これで List のメソッドを継承することができます。

次に、insert() と remove() をオーバーライドします。プロトタイプチェーンの順番は FixedList のオブジェクト、FixedList.prototype のオブジェクト、List.prototype のオブジェクトになるので、FixedList.prototype にメソッドを追加すれば、List.prototype のメソッドをオーバーライドすることができます。

insert() は this.limit と this.size を比較して、this.size が this.limit よりも小さい場合はデータ x を挿入します。スーパークラスのメソッド List.prototype.insert() を call() で呼び出して、データを挿入できた場合は this.size を +1 します。remove() の場合、this.size が 0 よりも大きいときにスーパークラスのメソッド List.prototype.remove() を call() で呼び出します。データを削除できた場合は this.size を -1 します。これで、連結リストに格納される要素数を管理することができます。

それでは、簡単な実行例を示しましょう。

> a = new FixedList(4)
FixedList {size: 0, limit: 4, top: Cell, insert: function, remove: function…}
> for (var i = 0; i < 5; i++) console.log(a.insert(0, i))
0
1
2
3
null
undefined
> a.toString()
"(3,2,1,0)"
> for (var i = 0; i < 5; i++) console.log(a.remove(0))
3
2
1
0
null
undefined
> a.toString()
"()"

このように List を継承することで、FixedList を簡単にプログラムすることができます。


●プログラムリスト1

//
// list.js : 連結リスト
//
//           Copyright (C) 2010 Makoto Hiroi
//

//
// セル
//
function Cell(data, link) {
    this.data = data;
    this.link = link;
}

//
// 連結リスト
//
function List() {
    var cp = new Cell(null, null);
    this.top = cp;
    for (var i = 0; i < arguments.length; i++) {
        cp.link = new Cell(arguments[i], null);
        cp = cp.link;
    }
}

// メソッドの定義
List.prototype = function() {
    // n 番目のセルを返す
    function nth(cp, n) {
        var i = -1;
        while(cp != null) {
            if (n == i) return cp;
            cp = cp.link;
            i += 1;
        }
        return null;
    };
    var obj = {
        // n 番目の要素を返す
        at: function(n) {
            var cp = nth(this.top, n);
            if (cp) return cp.data;
            return null;
        },
    
        // n 番目にデータを挿入
        insert: function(n, x) {
            var cp = nth(this.top, n - 1);
            if (cp) {
                cp.link = new Cell(x, cp.link);
                return x;
            }
            return null;
        },

        // n 番目の要素を削除
        remove: function(n) {
            var cp = nth(this.top, n - 1);
            if (cp && cp.link) {
                var data = cp.link.data;
                cp.link = cp.link.link;
                return data;
            }
            return null;
        },

        // イテレータ
        each: function(func) {
            var cp = this.top.link;
            while (cp != null) {
                func(cp.data);
                cp = cp.link;
            }
        },

        // 空リストか
        isEmpty: function() { return this.top.link == null; },

        // 配列への変換
        toArray: function() {
            var ary = [];
            this.each(function(x) { ary.push(x); });
            return ary;
        },

        // 文字列への変換
        toString: function() {
            return "(" + this.toArray().join(",") + ")";
        }
    };
    return obj;
}();

//
// 制限付き連結リスト
//
function FixedList() {
    this.size = 0;
    this.limit = arguments[0];
    List.call(this);            // スーパークラスのコンストラクタを呼び出す
}

// 継承
FixedList.prototype = new List();

// オーバーライド
FixedList.prototype.insert = function(n, x) {
    if (this.size < this.limit) {
        var result = List.prototype.insert.call(this, n, x);
        if (result != null) this.size++;
        return result;
    }
    return null;
}

FixedList.prototype.remove = function(n) {
    if(this.size > 0) {
        var result = List.prototype.remove.call(this, n);
        if (result != null) this.size--;
        return result;
    }
    return null;
}

多重継承と Mix-in

継承には「単一継承」と「多重継承」の 2 種類があります。単一継承は、ただひとつのクラスからしか機能を継承することができません。これに対し多重継承は複数のクラスを継承することができます。JavaScript の場合、プロトタイプのオブジェクトは一つしか指定できないので単一継承になります。

●多重継承の問題点

多重継承は異なる性質や機能を持つ複数のクラスを継承することができるので、とても強力な機能です。ところが問題点もあるのです。たとえば、クラス Foo にはメソッド method_a() があり、クラス Bar にはメソッド method_b() があるとしましょう。この 2 つのメソッドはまったく異なる働きをします。ここで、メソッド method_a() はインスタンス変数 (プロパティ) x を使っていて、method_b() も x を使っていると、多重継承で問題が発生します。

クラス Foo と Bar を多重継承してクラス Baz を作成した場合、クラス Baz のインスタンスには x がひとつしかありません。メソッド method_a() と method_b() はひとつしかない x を使うことになります。この場合、どちらかのメソッドは正常に動作しないでしょう。これでは多重継承する意味がありません。また、Foo と Bar に同じ名前のメソッドが存在することもあります。このように、多重継承では名前の衝突が発生する場合があるのです。

それから、多重継承にはもうひとつ問題点があります。それはクラスの階層構造が複雑になることです。単一継承の場合、クラスの階層は木構造になりますが、多重継承ではグラフになります。木構造の場合、クラスの優先順位は簡単にわかりますが、グラフになると優先順位を理解するのは難しくなります。多重継承は強力な機能ですが、使うときには十分な注意が必要です。

●Mix-in

これらの問題を回避するため、インスタンス変数 (属性) を継承するスーパークラスはひとつだけに限定して、あとのスーパークラスはメソッド (実装) だけを継承するという方法があります。この方法を Mix-in といいます。具体的には、インスタンス変数を定義せずにメソッドだけを記述したクラスを用意します。属性の継承は単一継承になりますが、実装のみを記述したクラスはいくつ継承してかまいません。ひとつのクラスに複数の実装を混ぜることから Mix-in と呼ばれています。

なお、Mix-in は特別な機能ではなく、多重継承を使いこなすための方法論にすぎません。多重継承を扱うことができるプログラミング言語であれば Mix-in を行うことが可能です。ちなみに、この Mix-in という方法を言語仕様に取り込んだのが Ruby です。

Mix-in を図に示すと次のようになります。

                A
              /
            B
 Mixin A  /  \    Mixin B
    \  /      \  /
      C          D

      図 : Mix-in

クラス C はクラス B を継承していて、そこにクラス Mixin A が Mix-in されています。クラス D もクラス B を継承していますが、Mix-in されているクラスは Mixin B となります。

多重継承の問題点は Mix-in ですべて解決できるわけではありませんが、クラスの階層構造がすっきりとしてわかりやすくなることは間違いありません。Mix-in は多重継承を使いこなす優れた方法だと思います。

●Enumerable

Mix-in はクラスのないプロトタイプベースのオブジェクト指向でも簡単に実現することができます。Mix-in は簡単にいえばクラスにメソッドを追加することです。JavaScript の場合、プロトタイプのオブジェクトにメソッドを追加することで Mix-in を実現することができます。

それでは Mix-in の例題として、Mix-in 用のオブジェクト Enumerable を作ってみましょう。Enumerable は配列や連結リストなどのような複数のデータを格納するオブジェクトに高階関数を Mix-in します。これは Ruby のモジュール (Mix-in 用のクラス) Enumerable を参考にしました。追加するメソッドを下表に示します。

表 : Enumerable のメソッド
名前機能
obj.member(func)func が真となる要素を返す
obj.position(func)func が真となる要素の位置を返す
obj.count(func)func が真となる要素の個数を返す
obj.mapcar(func)要素に func を適用した結果をリストに格納して返す
obj.remove_if(func)func が真となる要素を削除したリストを返す
obj.fold_left(func, init)すべての要素を func を用いて結合した結果を返す

プログラムは次のようになります。

リスト : Enumerable 

Enumerable = function() {
    // throw 用のオブジェクト
    function Value(value) {
        this.value = value;
    }
    // 内部関数
    function _each(obj, func) {
        try {
            obj.each(func);
        } catch(e) {
            if (e instanceof Value) {
                return e.value;
            } else {
                throw e;
            }
        }
    }
    // メソッドの定義
    var obj = {
        // 探索
        member: function(func) {
            var result = _each(this, function(x) {
                if (func(x)) throw new Value(x);
            });
            return result != undefined ? result : false;
        },

        // 位置を返す
        position: function(func) {
            var n = 0;
            var result = _each(this, function(x) {
                if (func(x)) throw new Value(n);
                n++;
            });
            return result != undefined ? result : -1;
        },

        // 条件を満たす要素をカウントする
        count: function(func) {
            var n = 0;
            var result = _each(this, function(x) {
                if(func(x)) n++;
            });
            return n;
        },

        // マッピング
        mapcar: function(func) {
            var a = [];
            _each(this, function(x) {
                a.push(func(x));
            });
            return a;
        },

        // フィルター
        remove_if: function(func) {
            var a = [];
            _each(this, function(x) {
                if (!func(x)) a.push(x);
            });
            return a;
        },

        // 畳み込み
        fold_left: function(func, init) {
            var a = init;
            _each(this, function(x) {
                a = func(a, x);
            });
            return a;
        }
    }
    return obj;
}();

オブジェクトの要素はイテレータ each() で取り出します。each() は Mix-in するオブジェクトで定義することとします。つまり、each() を定義さえすれば、どんなオブジェクトにも Enumberable を Mix-in することができるわけです。実際には、内部関数 _each() で each() を呼び出します。

each() を呼び出す場合、途中で処理を中断して値を返すことができると便利です。これは「例外処理」を使うと簡単に実現できます。JavaScript の例外処理はC++とよく似ていて、throw で例外を送出して、それを catch で受け取ります。値を返すときは Value のオブジェクトに格納して、それを throw で送出します。catch で例外を受け取り、それが Value のオブジェクトであれば、そこに格納されている値を return で返します。オブジェクトの種別 (タイプ) は演算子 instanceof で判別することができます。そうでなければ、他のエラーなので throw で例外を再送出します。

あとは、Enumerable のメソッドで _each() を呼び出して、結果を返すだけです。たとえば member() の場合、匿名関数を _each() に渡して呼び出します。匿名関数で member() の引数 func を呼び出して、結果が真であれば要素 x を Value に格納して throw で返します。真となる要素が見つからない場合、_each() は undefined を返すので、その場合 member() は false を返します。mapcar() と remove_if() の場合は、結果を配列 (Array) に格納して返すことにします。

最後にメソッドを Mix-in する関数を作ります。

リスト : Mix-in

function mixin(src, dst) {
    for(var prop in src){
        dst[prop] = src[prop];
    }
}

関数 mixin() はオブジェクト src にあるプロパティをオブジェクト dst にコピーします。プロパティの取得は for 文を使うと簡単です。dst[prop] = src[prop] とすれば、src にあるプロパティを取り出して dst に追加することができます。

たとえば、List に Enumerable を Mix-in する場合は次のようにします。

mixin(Enumerable, List.prototype)

これで、List のオブジェクトから Enumerable のメソッドを呼び出すことができます。簡単な実行例を示しましょう。

> mixin(Enumerable, List.prototype)
undefined
> a = new List(1,2,3,4,5,6,7,8)
List {top: Cell, at: function, insert: function, remove: function, each: function…}
> a.toString()
"(1,2,3,4,5,6,7,8)"
> a.member(function(x){ return x % 2 == 0; })
2
> a.position(function(x){ return x % 2 == 0; })
1
> a.count(function(x){ return x % 2 == 0; })
4
> a.mapcar(function(x){ return x * x; })
[1, 4, 9, 16, 25, 36, 49, 64]
> a.remove_if(function(x){ return x % 2 != 0; })
[2, 4, 6, 8]
> a.fold_left(function(x, y){ return x + y; }, 0)
36

正常に動作していますね。また、既存のオブジェクト、たとえば配列 (Array) にも Enumerable を Mix-in することができます。

リスト : 配列に Enumerable を Mix-in

// イテレータの定義
Array.prototype.each = function(func) {
    for(var i = 0; i < this.length; i++) func(this[i]);
}

簡単な実行例を示しましょう。

> mixin(Enumerable, Array.prototype)
undefined
> a = [1,2,3,4,5,6,7,8]
[1, 2, 3, 4, 5, 6, 7, 8]
> a.member(function(x){ return x % 2 == 0; })
2
> a.position(function(x){ return x % 2 == 0; })
1
> a.count(function(x){ return x % 2 == 0; })
4
> a.mapcar(function(x){ return x * x; })
[1, 4, 9, 16, 25, 36, 49, 64]
> a.remove_if(function(x){ return x % 2 != 0; })
[2, 4, 6, 8]
> a.fold_left(function(x, y){ return x + y; }, 0)
36

このように、複数のクラスで共通の操作 (メソッド) を定義したい場合、Mix-in はとても役に立ちます。


●プログラムリスト

//
// list.js : 連結リスト
//
//           Copyright (C) 2010 Makoto Hiroi
//

//
// セル
//
function Cell(data, link) {
    this.data = data;
    this.link = link;
}

//
// 連結リスト
//
function List() {
    var cp = new Cell(null, null);
    this.top = cp;
    for (var i = 0; i < arguments.length; i++) {
        cp.link = new Cell(arguments[i], null);
        cp = cp.link;
    }
}

// メソッドの定義
List.prototype = function() {
    // n 番目のセルを返す
    function nth(cp, n) {
        var i = -1;
        while(cp != null) {
            if (n == i) return cp;
            cp = cp.link;
            i += 1;
        }
        return null;
    };
    var obj = {
        // n 番目の要素を返す
        at: function(n) {
            var cp = nth(this.top, n);
            if (cp) return cp.data;
            return null;
        },
    
        // n 番目にデータを挿入
        insert: function(n, x) {
            var cp = nth(this.top, n - 1);
            if (cp) {
                cp.link = new Cell(x, cp.link);
                return x;
            }
            return null;
        },

        // n 番目の要素を削除
        remove: function(n) {
            var cp = nth(this.top, n - 1);
            if (cp && cp.link) {
                var data = cp.link.data;
                cp.link = cp.link.link;
                return data;
            }
            return null;
        },

        // イテレータ
        each: function(func) {
            var cp = this.top.link;
            while (cp != null) {
                func(cp.data);
                cp = cp.link;
            }
        },

        // 空リストか
        isEmpty: function() { return this.top.link == null; },

        // 配列への変換
        toArray: function() {
            var ary = [];
            this.each(function(x) { ary.push(x); });
            return ary;
        },

        // 文字列への変換
        toString: function() {
            return "(" + this.toArray().join(",") + ")";
        }
    };
    return obj;
}();

//
// 制限付き連結リスト
//
function FixedList() {
    this.size = 0;
    this.limit = arguments[0];
    List.call(this);            // スーパークラスのコンストラクタを呼び出す
}

// 継承
FixedList.prototype = new List();

// オーバーライド
FixedList.prototype.insert = function(n, x) {
    if(this.size < this.limit) {
        var result = List.prototype.insert.call(this, n, x);
        if (result != null) this.size++;
        return result;
    }
    return null;
}

FixedList.prototype.remove = function(n) {
    if(this.size > 0) {
        var result = List.prototype.remove.call(this, n);
        if (result != null) this.size--;
        return result;
    }
    return null;
}

//
// Enumerable
//

// Mix-in
function mixin(src, dst) {
    for(var prop in src) {
        dst[prop] = src[prop];
    }
}

Enumerable = function() {
    // throw 用のオブジェクト
    function Value(value) {
        this.value = value;
    }
    // 内部関数
    function _each(obj, func) {
        try {
            obj.each(func);
        } catch(e) {
            if (e instanceof Value) {
                return e.value;
            } else {
                throw e;
            }
        }
    }
    // メソッドの定義
    var obj = {
        // 探索
        member: function(func) {
            var result = _each(this, function(x) {
                if (func(x)) throw new Value(x);
            });
            return result != undefined ? result : false;
        },

        // 位置を返す
        position: function(func) {
            var n = 0;
            var result = _each(this, function(x) {
                if (func(x)) throw new Value(n);
                n++;
            });
            return result != undefined ? result : -1;
        },

        // 条件を満たす要素をカウントする
        count: function(func) {
            var n = 0;
            var result = _each(this, function(x) {
                if(func(x)) n++;
            });
            return n;
        },

        // マッピング
        mapcar: function(func) {
            var a = [];
            _each(this, function(x) {
                a.push(func(x));
            });
            return a;
        },

        // フィルター
        remove_if: function(func) {
            var a = [];
            _each(this, function(x) {
                if (!func(x)) a.push(x);
            });
            return a;
        },

        // 畳み込み
        fold_left: function(func, init) {
            var a = init;
            _each(this, function(x) {
                a = func(a, x);
            });
            return a;
        }
    }
    return obj;
}();

Copyright (C) 2010-2015 Makoto Hiroi
All rights reserved.

[ PrevPage | JavaScript | NextPage ]