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

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

私に教えられることなら

いろいろなForth処理系でのOOP比較

自作処理系の参考にするか、とても使いやすければいっそそれを使ってしまおう、という魂胆で、いろいろなForth処理系でのOOPの構文や使い方を調べてみた。

特に重視するのは

  • Late Bindingができるか
  • Polymorphismが違和感無く行えるか
  • メッセージの送り方
  • オブジェクトの生成のしかた

の4点。

全てのサンプルコードはgithubリポジトリに上げた。

サンプルとして、二次元ベクトルを表すVec2Dオブジェクトを作り、

  • 一つのVec2Dインスタンスを取り、破壊的に加算するadd!
  • x: 10 y: 15のように成分を表示するshow

の二つのメソッドを作って使う。

gforth - objects.fs

ソース

Late Bindingはできたけど、Polymorphismは上手くいかなかった。

あとメソッドの定義の仕方が大分面倒だった。クラス定義内で以下のように定義する。

  selector get-x ( Vec2D -- )
  m: ( Vec2D -- ) x ;m overrides get-x

まあこれはdefining wordでどうにかできるだろうけど、Polymorphismの問題もあって見送り。

オブジェクトの生成とLate Bindingについては素直な使い方でよかった。

Vec2D heap-new constant v1

のようにアドレスを変数(定数だけど)に入れて、

v2 v1 add!

とメッセージを送る。この書き方が一番好きなので真似しよう。

gforth -- oof.fs

ソース

Late Bindingがまず上手くいかなくて、動かせなかった。bigForth由来のシステムらしいけど、public指定など細かな点で違うっぽい?(ちなみにbigForthはファイルが足りなくてコンパイルできなかったので試せなかった。)

gforth -- mini-oof.fs

ソース

名前からoofの更に機能縮小版かと思ったけど、メソッド定義の仕方が違うだけでそんなに変わらないっぽい?

Late BindingとPolymorphismどちらも上手くいって、オブジェクトの生成とメッセージ送信ともにobjects.fsと同じやり方で好きなカンジだった。gforthでOOPやるならこれが良さそう。

実装の解説もあった。まだ読んでないけど、:nonameで辞書にワードを作ってあとでクラスメソッドに定義してるので、クラス自体はシンプルなテーブルみたいだ。

SwiftForth

ソース

Forthの本家本元(だったはず)のFORTH, Inc.のSwiftForth。オブジェクトシステムはSWOOPというらしい。

Late BindingとPolymorphism共に問題無かった。Early Binding用とLate Binding用にオブジェクトの使い方が分かれていて、Late BindingではVec2D new value v1のように生成したあと、メッセージをv1 -> add!という形で送る必要がある。v1 add!で送れるものと比べて面倒さがあるんだけど、その代わりメソッド定義内でthisselfを省くことができる。

mini-oof.fsの場合のadd!はこう

:noname { v2 v1 -- }
  v2 x @  v1 x @  + v1 x !
  v2 y @  v1 y @  + v1 y ! ; Vec2D defines add!

thisにあたるのがv1で、全てobject messageの形になる。

SwiftForthの場合は

  : add! ( v2 -- )  locals| v2 |
    v2 -> get-x  get-x  +  to-x
    v2 -> get-y  get-y  +  to-y
    self ; \ for message chain

v2 -> get-xのときはv2へのメッセージング、ただget-xと書いてある場所では暗黙的にthisへのメッセージングとなる。

後者の方がより楽に書けるんだけど、ちょっと面倒だな。リーダを拡張してv2.get-xを読み込み時にv2 -> get-xにするとかかな。個人的には前者のアプローチの方が好きだ。

ともかく、処理系含めての使い勝手はかなり良くて流石だなあという感想。

Factor

ソース

まず自分のボキャブラリ作って動かすまでにちょっと苦労した。わかってしまえば簡単なんだけど、scaffoldで一度作ってから、git用のディレクトリに移して

"パス" add-vocab-root
USE: ボキャブラリ名

で良かった。あとはrefresh-allなどで更新しつつ試していった。

Late Binding、オブジェクトの生成、メッセージングは問題無かった。Polymorphismは試してないけど、総称関数(ワード)なので多分大丈夫でしょう。

GENERIC:で宣言したり、アクセサが自動生成されたりと、影響受けてるらしいCommon Lisp(CLOS)のフレバーを感じる。ただアクセサ名がちょっと……見づらい。

ficl

ソース

Switch-ThreadedでJITコンパイラがあったり?と結構トンガッてるっぽいficl

OOPの方も紹介文でMeta classに言及してたりと力いれてるっぽい。

前々から気になってたからこれを機に使ってみたけど、

ok> 1
ok> 2
ok> +
(スタックアンダーフローエラー)

トンガリすぎじゃない?!どうも入力行毎にスタックがクリアされてるっぽい。1 2 + .だと問題無かった。

バグなのか意図した挙動なのかわからなかったけど、ソースを読ませても同じで使いにくい。

ficlのソースをいじって調べてみると、vm.cで必ずエラーが投げられてるっぽいので、とりあえずその場所でデータスタックをクリアしないようにした。

    if (except)
        {
          //FIXED: ここでデータスタックがリセットされるので値が残らない
          // LOCAL_VARIABLE_SPILL;
        vm->exceptionHandler = oldExceptionHandler;
        ficlVmThrow(vm, except);
        }

本当に例外起きたらまずいから今回だけの修正だけど。何気に初めてオープンソースソフトウェアのコードをいじって使ってみた。C言語練習して良かった。

本題の方だけど、Late Binding、Polymorphism、オブジェクトの生成は問題無かった。Vec2D new v1でおそらくnewがトークンを読んで変数宣言する、という挙動がちょっとヌヌッ!

メソッド内でのインスタンス変数へのアクセスも含めて、全てメッセージングなんだけど、その形式がv1 --> get-xSWOOPより更に間延びしたカンジでうーむ……

感想

mini-oofやfacotrの、単純なarg object messageという形式がやっぱりいい。一方クラス定義はSWOOPやficlみたいにそのままワードを定義したい。

多分どちらもやるのは複雑になってくるのかな。最初からOOP向けに処理系を作ってしまえば楽になるかもしれない。想像だけど。SwiftForthとmini-oofで実装の解説があるので、そこらへんを読んだらそろそろ手を動かしていこう。

広告を非表示にする