Jewel-mmo開発日記

RubyでMMORPGを作る過程を記録する日記。 Yokohama.rb 発起人。
2010-11-10

とあるスクリプト言語に配列が欲しい

昨日の話はclassを導入しなくてもプロトタイプベースでオブジェクト指向をやるという方向性もあるのかもしれないと思った。

プロトタイプベースというのをよく知らなかったんだけど。 あれこれ頭ん中で考えているとそういうのが自然と分かってくるね。

考えるだけでずいぶんと勉強に。 普ものだなあ段何気なく使ってたものについてほんと考えさせられる。

さて今日は配列に期待することを書き出してみるぞ!

リテラル

Rubyっぽい書き方に慣れているのでRubyっぽく書いてみる。

a = [1, 2, 3]
a[0] // => 1

もちろん最後の「,」は省略可能だが書いてもいい。

空の配列も作成可能。

a = []

アクセス

a = [1, 2, 3]
a[0] // => 1
a[3] // => nil

インデックスは0からがいい。ところでなんでLuaは1からなんだろうか。 範囲外(というか未代入)にアクセスするとnilが返る。

こういう書き方も許して欲しい。

[1, 2, 3][1] // => 2

配列に代入できるもの

事なる種類の値を同じ配列に入れることもできる。

a = [1, "hello", [1, 2, 3]]
a[2][2] // => 3

もらちろん多次元配列も!

配列操作関数

以下のような関数を用意したい。 言い方を変えれば配列はこのような操作ができるものであってほしい。

size

a = [1, 2, 3]
size(a) // => 3
size([1, 2]) // => 2

each

a = [1, 2, 3]
each(a, puts) // => 1
              // => 2
              // => 3

上記は以下と同じ意味。

a = [1, 2, 3]
each(a, fanction(e) { puts(e) })

map

a = [1, 2, 3]
a = map(a, to_s)
p(a) //=> ["1", "2", "3"]

flatten

a = [1, 2, [3, 4, 5]]
a = flatten(a)
p(a) //=> [1, 2, 3, 4, 5]

その他

  • map!, flatten! ... 破壊的に
  • push, pop, shift, unshift, delete ... これも破壊的か
  • sort, reverse, sort!, reverse!

ほんとはこう書きたいけどこれは難しいだろうしまた別の話やね。

a.size()
a.map(to_s)
2010-11-09

とあるスクリプト言語のオブジェクト指向への道のり

職場に独自の小さなスクリプト言語とそれを動かすVMがある。

ゲームの組み込みに使われているんだけど、VMで動作する特性上使い勝手がよく、 文法もJavaScriptによく似ていて取っ付き易い。

そして素晴らしいことに要望を出すとすぐに改善されるのだ。 先日は0を真に変えてもらったりもした(偽になるのはnilとfalseだけとなった)。

まだ配列とハッシュがないのだけど、これも近々サポートされることになりそう。 そこで、その先にある(と思われる)オブジェクト指向言語化について考えてみた。

次の3ステップでオブジェクト指向化できないかな? ツッコミどころはありそうだけど、たたき台ということで。

※ここから先興味のある方はJavaScriptをイメージしてもらうといいかも

その1。「値.関数名()」と書いたらレシーバを第1引数として渡す

次のようにメンバ変数(ハッシュ)に関数を代入して呼び出すと、 強制的に第1引数に自分自身が渡される。

value.method = function(self, a) { ... }
value.method(a)

考えたのだけどハッシュに関数を入れた場合、 selfが欲しいケースの方が断然多いんじゃないかな。

だたしselfが不要な場合もあるかもしれないので、 そのときは次ように「!」を使って呼び出す方法も用意しておく。

value!method(value, a)

これは負荷対策以外に意味はないけど。

その2。メンバ関数(インスタンスメソッド)の定義方法

関数側も引数の定義でselfと毎回記述するのはウザイ。

function method(self, a) {
  puts(self.keys)
}

そこで次のように書けたらどうだろうか。

function method(@, a) {
  puts(@keys)
}

関数内の@は自動的に「self.」に置き換えられる。

関数定義は次のような形の方がよりいいかな。

function @method(a) {
  puts(@keys)
}

なんかインスタンスメソッドを定義しているように見えていい感じだ。

しかしどうせならclassキーワードを導入したらどうだろう。

class Klass {
  function method(a) {
    puts(@keys)
    @method2()
  }
  function method2() {
  }
}

おぉこれでいいじゃないか!

もちろん@は「self.」に展開されるだけなので、上の@method2()のように関数呼び出しにも使える。

さてここまで来ればオブジェクト指向言語まであと一歩。

その3。ミックスイン

Klassクラスを次のように定義したとする。

class Klass {
  function method1() { ... }
  function method2() { ... }
}

これをオブジェクト(ハッシュオブジェクト)に関連付けなくてはいけない。 ここでmixinという新たな仕組みを用意する。

obj = {}
mixin(obj, Klass)
obj.method1()
obj.method2()

mixinはKlassクラスが持っている関数のポインタそれぞれをobjのメンバに代入する関数だ。 イメージとしては次のような感じ。

obj.method1 = Klass.method1
obj.method2 = Klass.method2

あくまで静的にメソッド呼び出しを解決したいので、 mixinするクラスに階層などはない。 同じ関数名があればあとからmixinしたものが優先される。

class型という新しい値が必要になっちゃうかな。

ちなみにmixinは次のように書けた方がいいかもしれない。

obj = {}
obj.mixin(Klass)

でもここまできたらこう書きたいよね!

obj = new(Klass)

またはこうかな。

obj = Klass.new