iolanguage最初の一歩 (対話モード編)

はじめに

・個人的な学習メモです、そのため言語のバージョンアップに伴う仕様変更や、見落とし、間違い等あるかもしれません。
・ 外堀を少しづつ埋めながら進んでいきます。

教科書

 すべてがオブジェクト、式は、文ではなく、すべてがメッセージで構成されている。
 スロットとは何ですか? slot―【名】【C】自販機等の料金差し入れ口. 何かを差し込んだりする場所や位置のこと。
Iolanguageのスロットは「何かを差し込んだりする場所や位置のこと」という意味が近い。家にある郵便受けのようなもの?
 
(▼クリックで続きを読む)

▼iolanguageの構成

※公式ガイドでは、iolanguageの文法は、BNF記法で書かれています。 

【式の構造】
                    語 − 句 − 節 − 文       (日本語の場合)

             修飾語 +引数節  −   指示節    −  式(文)      ≠    構文
          symbol [arguments] − message  −  expression  ≠ statement    (Iolanguageの場合)

symbol は以下の3つに分類される。 identifer 識別子 = ユーザ/システムが作成した、機能を表す語( 変数名、メソッド名、オブジェクト名等 ) number 数詞 = 数 string 文字列 = 文字 Operator 演算子 +-*/ Comment コメント

expression (exp)【構成要素】

式、Iolanguageにおける最も大きな構造。 { message | terminator } 式は、メッセージ(messages) と、区切り(terminator)から構成される。 ひとつの式は、terminator つまり「 ; 」 または 改行 「\n」で終わる(区切られる) ようになっている。 セミコロン「;」で区切った場合、一行に何個も式を書ける。 改行で区切れば、何行でも式を書ける。
メッセージ;メッセージ;メッセージ メッセージ メッセージ

message【構成要素】

メッセージ 、指示節 Iolanguageは、変数も何もかも、すべてメッセージでできています。 メッセージは、シンボル(symbol) と 引数(arguments)から構成されています。、 symbol [arguments] 引数あり → メソッドか何か ユーザー作成メソッド、またはシステム予約済みメソッド(for , while, if 等)を実行している 引数なし → 変数かなにか ユーザーが作った変数、またはシステム予約済みのメソッド(println, exit等)を実行している
println ← 引数がない例 for( i , 1 , 10 , i println )← 引数がある例 a := if( a ==1 , c+1 )
println や for 、if がシンボルに相当、その隣の括弧()でくくられた部分が引数です。 引数については、欠点もあります、長くなるのでここでは割愛します。 ※見分け方

arguments【構成要素】引数

引数は、メソッドに渡すパラメータを記述可能なリストで、小括弧()でくくられています。  変数や条件や戻り値、あらゆるものを、区別なく内包できます。 "(" [exp [ { "," exp } ]] ")" exp つまり「式」をいくらでも内包できます。式は カンマ「,」によって区切られます。
if( a ==1 , c+1 )
この欠点は、どれが条件なのか、何が内容なのか、見分けがつかないことです。 コメントをつけるか、改行やインデントで読みやすく整形するしかありません。 特に、制御構文のようなことを記述しようとすると、 括弧の入れ子状態になります。 Iolanguageには構文(statement)は存在しないので、 改行やインデントによって構造を表現しなければいけません。 ※JavaScriptや C言語のように、内容を定義する 中括弧 { } がありません。 Iolanguageでは、なにもかも 全てひっくるめて括弧( ) でくくられます。

【その他】各オブジェクトのスロットのAPIに依りますI。 Image , Sound , Color, Object 等のオブジェクトを作る場合 Clone
Cairo_draw := Object clone
File , Path, Directory URL の場所(ファイルバス)を表す場合は with
Path with(System launchPath, "images/lotus.png")
初期化方法はAPIによって違う
surface := ImageSurface create(FORMAT_ARGB32, 256, 256)

【オブジェクトの状態】 slot の状態を調べる
【method とblockの違い】 Io における method と block の違い に書いてあった事を試したら本当だった。とても参考になった。
Io> animal := Object clone ==> Object_0x544dc0: Io> animal dog := block("called block baw-baw ");animal cat := method("called method mew-mew"); Io> animal cat Io> animal dog call

【式の送り方】 sender と receiver を理解する。 (非同期メッセージ) sender を表示する方法。
forward := method( write("sender = ", call sender, "\n") write("message name = ", call message name, "\n") args := call message argsEvaluatedIn(call sender) args foreach(i, v, write("arg", i, " = ", v, "\n") ) )
 
【methodの戻り値】
  Io> a := method( /*身長 : */ a , /*体重: */ b , a+b "aaaaa" // <---- 最後に書いた、この行のメッセージが戻り値になる。 ) Io> a(10,11) ==> aaaaa Io>
 
【しょうもない実験】
Io> dog := Object/*の*/clone/*です*/ ==> Object_0x5be190: dog/*とは*/:= Object/* に */clone/* というメッセージを送った結果である */ Io> a := method(/* arg1 */a,/*arg2*/b,a+b) ==> method(a, b, a + b Io> a := method( /*身長 : */ a , /*体重: */ b ,a+b) ==> method(a, b, a + b
Io> dog := method("bawww") Io> (3+8) dog ==> bawww
Io> cat := method("mewww") Io> dog := method("bawww") Io> dog cat ==> mewww Io> write(dog,cat,"\n" ) bawwwmewww ==> nil

はじめてみよう

いきなりテキストエディタを開いて、プログラム書く前に、まず対話モード(Interaction mode)を試してみましょう。

対話モードって何?

例えば日本語にもコミュニケートの手段として、文章と会話が存在します。
そして、文字だけの表現だけでは伝わらないものがあるのも確かです。表情や感情、発音、声色などです

これと同じことが、 プログラミング言語にもあてはまります。
Iolanguageにも、文章(プログラムが書かれたファイル)と会話(対話モード)という手段が存在します。
(このときの、会話の相手はコンピュータとなります)

そして、言語で書かれたコード(文章)だけを読んで、その裏にあるメモリの動きやオブジェクトの派生関係(表情や感情のように文章から分かりにくいもの)を感じ取るのも、難しいです
そこで対話モードの出番です。

対話モードを使えば、Ioの内部で何がどのように動いているかの表情を、実行と同時に 様々な切り口から対話的に探ることができるので、
その言語を使う/知る上での有用なヒントになります。 テキストエディタでコードを書く傍ら、対話モードを辞書のように使ってみてもいいかもしれません。

また、対話モードの利点として、トライ&エラーに対するフットワークが軽いという理由があります。
何かちょっと試すだけなのに、コードの隅から隅まで目を光らせて、エラーと格闘する必要もありません。

対話モードへの入り方

Ioをインストールしていれば、ターミナル上でio と打つと、Ioの対話モードに入ることができます。
何かコマンドを打って、その反応を見るのが基本です。
モードを終了しない限りは、変数やオブジェクトに対する変更は追加は蓄積されていきます。
一行一行の会話からコードを引き出しながら、プログラムを少しづつ織っていくようなものです。

●対話モードの操作方法。
・なにかコマンドを打ってリターンキーを押せば実行。
・今まで入力したコマンドが記憶されているので、矢印キーの↑↓で過去に入力した内容(ヒストリ)を呼び出すことができます。
・lolanguageの対話モードでは、コピーペーストによる複数行を一気に入力することも可能です。

対話モードでIoを起動
usr$ io  
Io 20080120
Io> 
いきなりですが終了してみましょう
Io>exit
usr$  
exit と打ち込むと。Ioの対話モードが終了し、 無事にターミナルの元の画面に戻るようです。
何のことは無いけど、安心してシステムを使う上で大事なことです。

これから何をするのか?

対話モード画面で、表示されているものについて、出来る限り調べる。

対話モードで ・オブジェクトを調べる(Methodなど)
・スロットの内容を調べる
・オブジェクトを呼び出す
・オブジェクトの種類を調べる(Object_0x5e8a00, List_0x5e8a00, File_t_0x5e8a00 など)
・オブジェクトに代入する
・アドオンを導入すると、どんなオブジェクトが追加されるのか

非対話モードで
・オブジェクトやスロットの内容を調べる

Fluxアドオンで、簡単なウインドウアプリケーションを作ってみる。
・このとき、対話モードと同じように、オブジェクトやスロットの状態を調べたりできるようにする。

対話モードで、オブジェクトの中身を調べよう

出来る限り、表示されるもの全てを調べたいと思います。基本的に同じパターンです。
何か変数やメソッドを追加する → slotSummary メソッドを使って、スロットの状態を確認する。


あらためて、対話モードでioを起動
usr$ io  
Io 20080120
Io> 
これは何も変数やメソッドがなにも定義されておらず、実行されてない状態です。

別のモードで言い換えると、何も命令が書かれてない Io言語のプログラム
ファイルを実行した時も同じ状態ということになります。

このとき、Ioがどのような状態になっているのかを調べるために、
中身を覗いて見てみましょう
Io> slotSummary
と打ち込むと、オブジェクトの中身を調べられます。
するとIoから結果が返ってきました。
なんらかのリストが表示されてます。
公式ドキュメントによると、オブジェクトは スロットとProtos というリストから出来ていると書かれている。 確かに、Lobby とか、 Protos、exit といった名前が、スロットとしてリストされてますね
Io> slotSummary
==>  Object_0x50c5b0:
  Lobby            = Object_0x50c5b0
  Protos           = Object_0x50c0c0
  exit             = method(...)
  forward          = method(...)
これから、上のリストが何を表すのか一緒に調べていきましょう。

Object って何だろう? → 今slotSummary で見てるもの。
Object_0x50c5b0 が今実行中の、一番大元(おおもと)にあるオブジェクト名。
たぶん0x50c5b0というのは、このオブジェクトのある「場所」だと思われる。


また、スロットは、名前(キー) と 値 から構成されるデータで、値は オブジェクト、メソッド、数字、文字など なんでも表すことが出来るそうです。

Lobbyって何だろう?
さっきのことを踏まえれば、Lobby の値である中身は Object_0x50c5b0。 これは オブジェクト自身を指している…ということになります。 これが何を意味するのかは置いといて、次にいってみましょう。

Protosって何だろう?
なんでしょう???
また 、最初から作り付けの exit や foward というメソッドも、あるなぁということも分かります。
= method(...) というのは 種類を表しているので、実際どんな効果があるのかは
使ってみるしかありません。さて、次にいってみましょう。

exit って何だろう?
一番最初に対話モードでexitと打つと終了していたことに気がつきましたか?
Io>exit
usr$  
exit というメソッドは、まさにこのことだったのです!

ということは、対話モードで スロットにある名前、例えば、「Protos」 と打てば、スロットに登録してあるProtos オブジェクトの内容を表示したりメソッドを 実行できるんじゃないでしょうか

再び・Protosって何だろう?
さっき気がついたことを元に、やってみましょう
Io> Protos
==>  Object_0x50c0c0:
  Addons           = Object_0x50c820
  Core             = Object_0x50c580
なるほど、中にはAddon とか、Core が入っていることがわかります。
Coreは実行の中枢にかかわるものが入っている。そして おそらく拡張機能を宣言した場合、Addonに専用メソッドなんから追加されるんでしょうね。


Addons の中身を見てみましょう。このオブジェクトは Protosオブジェクト の中にあったので、中身を見るには Protos Addons と打てば見れます。
Io> Protos Addons
==>  Object_0x50c820:
  EditLine         = Object_0x574220
  ReadLine         = Object_0x111cc20

Io> Addons
==>  Object_0x50c820:
  EditLine         = Object_0x574220
  ReadLine         = Object_0x111cc20
普通に Addons と入れても見れますね。

入っているAddon は EditLine と ReadLine 。
exit の件といい、これも辻褄がつきます。

EditLine は
対話モードにおいて、 Io> に入れるコマンドを入力したり編集する機能を提供。

ReadLineは
対話モードにおいて、 入れたコマンドを解釈する機能を提供。
と予想できます。

ちなみに、対話モード中に、矢印カーソルの上キーを押すことで、過去に打ち込んだコマンド履歴をたぐり寄せることが出来ます。 おそらくこの機能もAddonによって提供しているのかな?さて、次ぎにいきましょう。

forwardって何だろう?
メソッドを実行しても 反応がないです、
これはまた後で調べてみましょう。

スロット名の一覧を取得する

slotNames を使います
Io> slotNames
==> list("forward", "exit", "Responder", "View", "Protos", "app", "OSWindow", "Lobby", "GLObject", "Application")

変数を作ってみよう

何か作るたびにスロットに追加されます。

次に、変数を作ってみましょう
Io> a := 100
と打ち込んで、再び slotSummaryしてみる。
Io> slotSummary
と打ち込むと、オブジェクトの中身がどうなっているかを調べられます。

( := はスロットの作成と同時に値を入れてしまいます
もし、スロットだけ用意したければnewSlot("a") と書いてください。
このときは内容は nilになります。
また、それをフォローするsetAというメソッドも自動的に用意されます。)
slotSummaryを使って今の状態を調べます
このとき、Io 内部ではどうなってるか?
Io> slotSummary
==> 
  Lobby            = Object_0x50c5b0
  Protos           = Object_0x50c0c0
  a                = 100  <------------------こいつに注目。
  exit             = method(...)
  forward          = method(...)


スロットに a という オブジェクト(プロパティ)が 差し入れられたことがわかります。
a には 100 という値を入れたので、値は100となっています

値を見るにはどうすればいいでしょう?

変数を呼び出してみよう

変数や、オブジェクト、メソッド等も同じやり方

さきほど作った変数 a を呼び出してみよう。
Io> a

Iolanguageではオブジェクトや値、メソッド等は、単独、もしくは(それぞれスペースで区切られた)複数の「メッセージ」という単位で表されます。

これらは途中セミコロン「;」や改行で区切られない限りは、ひとつのグループとして処理されます。 例えばa というのは、aそのものを表示せよという、メッセージです。
スペースで区切られた場合、 a println; のように。メッセージは左「a」から右へ「println」へ、連鎖するように受け渡されます。
結果

Io> a
==> 100

このように、プログラムへ変数やメソッドを書けば書くだけ 変数やメソッドといった オブジェクトが スロットへどんどん追加、 されていくと予想がつきます。

また、スロットにあるオブジェクトa を呼び出す場合、
a と書けば 100が返ってくることが分かります。

これはメソッドでも同じで、プログラム中にメソッドを書けば、
スロットのほうにメソッドを追加されます。

例えばexit と書く (exit メソッドを呼び出す)と、どうなるでしょう?


スロットの新規作成、設定、更新

Iolanguageには「::=」 「 :=」 「 =」 の3種類の演算子メッセージがあります。
o> Obj := Object clone
==>  Obj_0x1123b50:
  type             = "Obj"

Io> Obj a ::= 1
==> 1
Io> Obj b := 2
==> 2
Io> Obj c = 3

  Exception: Slot c not found. 
  Must define slot using :=
 operator before updating.
  ---------
  message 'updateSlot' in 
  'Command Line' on line 1

Io> Obj a = 3
==> 3
Io> Obj a = 1
==> 1

Io> 
Io> Obj
==>  Obj_0x1123b50:
  a                = 1
  b                = 2
  setA             = method(...)
  type             = "Obj"

Io> 


Io> Obj setB(5)
==>  Obj_0x1123b50:
  a                = 5
  b                = 2
  setA             = method(...)
  type             = "Obj"

Io> 
「::=」 「 :=」 「 =」 の違い。
/ 概要 初期化に...
::= aに1が代入されるのは「:=」と一緒。
違うのは 「setA」というメソッドも自動生成される点です。

このメソッドを特別にセッター(Setter)と呼びます。 セッター名は、set+係るスロット名の大文字として決められます。 使い方はふつうのメソッドと同じで:setA(5) すると aに5を代入できます。

意図的にSetterを利用するのでなければ、あまり使いません。
newSlot("a", 1);と同じ
初期化に使えます
Io> Obj a ::= 1
==> 1

slotSummary して内容を
確かめてみて下さい。
:= aに1が代入される。 この時内容も初期化されます。
たいていはこのタイプが使われています。
setSlot("b", 2);と同じ
初期化に使えます
Io> Obj b := 2
==> 2
= 既に存在するスロットの内容を更新する時に使われます。
d := method( x = x+1;
                        y:=y+1; 
                        x println ;
                        y println
                        );
x:=10;y:=10;
==> 10
Io> d
11
11
==> 11
Io> d
12
11
Io> y println
10
Io>  x println
12
updateSlot("c", 3);と同じ
但し、初期化に使おうとするとエラー。
Io> Obj c = 3
  Exception: Slot c not found. 
  Must define slot using 
  := operator before updating.
  ---------
  message 'updateSlot'
 in 'Command Line' on line 1

オブジェクトのクローン

クローンは Lobby オブジェクト に追加される

オブジェクトのクローン(複製)を作ってみよう
dog というオブジェクトを作ってみましょう。(※"Dog"の場合はどうなるかな?)
 Io> dog := Object clone
==>  Object_0x5bbf40:
slotSummary を使ってObjectの状態を確認
Io> slotSummary
slotSummaryを使って今の状態を調べます
あたらに、dog というオブジェクトが追加されました。

Io> slotSummary
==>  Object_0x50c5b0:
  Lobby            = Object_0x50c5b0
  Protos           = Object_0x50c0c0
  a                = 100
  dog              = Object_0x111a5c0 <----こいつに注目
  exit             = method(...)
  forward          = method(...)


クローン元とどういう関係なのか調べてみる?
dog というオブジェクト、今あるObject のクローン。 その証拠に、dogから a を呼び出すことができます。
Io>  dog a  
==>  5

クローンを作るときの注意《重要》

Iolanguageでは、様々なオブジェクトの型があります。Objectや、文字列、数字、リスト、ファイルetc...
ユーザーが独自の型を作ることができますが、クローンとなるスロット名の命名に注意して下さい。

スロット名が英小文字で始まる場合の型は、大元のObject型を継承するクローンとして生成されます。
反対にスロット名が英大文字で始まるクローンを作成した場合、 独自名のオブジェクト型を生成します。
このオブジェクトには、typeという情報をもつSlotも一緒に作成されます。 これを対話モードで確かめると下のような結果になります
Io> aaa := Object clone
==>  Object_0x5e0f30:

Io> Aaa := Object clone
==>  Aaa_0x1115ad0:
  type             = "Aaa"

Io> 
上の例は、Object型、下の例は Aaa型のオブジェクトである事が分かります。
メッセージの連鎖のことを考えると、クローンを作るときは、名前に注意した方が良いです。
クローン以外の場合は、大文字でも小文字でも問題ありません。
例えば固有のクローンのslot名は大文字、それ以外のslotは小文字から始まる名前を付けると混乱せず済むかもしれません。

クローンを作るときの注意《initスロットについて》

init slotは、clone時に初期化を行なう特殊なスロットです。
これは定義した時点ではなく、次のclone を作る時にはじめて実行されます。
Io> Aaa  := Object clone do(
                init := method("Huzzaaah!!" print)
                )
==>  Aaa_0x1146970:
  init             = method(...)
  type             = "Aaa"

Io> d := Aaa clone
Huzzaaah!!==>  Aaa_0x11055a0:
Huzzarh!! と表示されるのは、Aaa が作成された時ではなく、 d が作成されたとき。
init スロットは do(...) 内にありますが、すぐに実行されるわけではありません、

クローンに追加してみる

クローンオブジェクトのスロットに変数やメソッドが追加される

 
クローン(dog)に何か追加するとどうなる?
クローンされたオブジェクト dog を調べよう
Io>  dog slotSummary
==>  Object_0x111a5c0:
しかし、元オブジェクトにある変数 a は dog slotSummary しても リストされません。

変数を追加してみよう
一方、dog 上で新たに b という変数を作った場合は、 しっかりdog のスロットに bがリストされてます。
Io> dog b := 7
==> 7
これは dogだけが使える変数です。
dog のスロットはどうなっているのか調べてみます
dog に変数 bを追加後の状態
Io> dog slotSummary
==>  Object_0x111a5c0:
  b                = 7   <-----こいつに注目

// dog の 変数 b にアクセスしてみよう。
Io> dog b
==> 7

// dog 以外から b を呼び出そうとするとエラー
Io> b
  Exception: Object does not respond to 'b' 
  ---------
  Object b                             Command Line 1
クローン元から bを呼び出すことはできません。 ということは、dog のスロットは dog 固有の変数やメソッドだけが登録されるようです。


では、クローン元に何か追加された場合どうなるか?
例えば、この後、クローン元で c という変数が作られた場合
dog から dog c で呼び出することができます。
またクローン元にも同じ名前の変数があった場合、
代入や作成が行なわれた場合、オブジェクト固有のものが優先されるようです。

メソッドを追加してみる

最初に定義しておいて、あとで何度も呼び出せる、 サブルーチン的なものです。

dog にメソッドを追加してみよう
この犬はwanwan と吠えることができます。
 Io> dog bark := method("wanwan" print)
==> method(
    "wanwan" print

追加したメソッドを実行してみましょう
barkは dog だけが使えるメソッド。
Io>dog bark
==>wanwan
今現在の dog のスロットの状態を調べます
Io> dog slotSummary
==>  Object_0x111a5c0:
  bark            = method(...) <-----こいつに注目
  b                = 7
bark というメソッドが、dogのスロットに追加されている

メソッドと引数

メソッドには、引数が有るものと、無いものを作る事ができます。今回の bark というメソッドは後者です。
引数arg は、コンマで区切りられ、一番最後にあるmessage がメッセージ(本体)として機能します。 method_name := method(arg,arg, message ... )
method_name(5,8);

メソッドと戻り値

Iolanguageのメソッドの構造は次のようになっています。
method_name := method(arg,arg, message ... )

この、message の部分には、スペースや改行によって連なるメッセージである
aaa bbb ccc
とか
aaa
bbb
ccc
という書式があてはまります。

Iolangageのメソッドでは、基本的にメソッドの最後にあるメッセージ(ccc)がメソッドの戻り値として認識されるという特徴があります。

return メッセージ
ユーザーがメソッドに任意のメッセージを戻り値に指定することもできます
message中でreturnメッセージを使います。
return value;
hogehoge

すると、メソッドのvalueという値が戻り値になります。
このとき 最後にあるメッセージhogehogeは戻り値にはなりません。

複数の

複数の戻り値を返すには、今思いつくのは、Listオブジェクトを使って返すくらい?

特定のメソッドの内容を知りたい

getSlot を使います。
Io> dog getSlot("bark")

//結果
method("wanwan" print)
Io>
メソッドの内容(コード)を出力します。

ここまでのまとめ+α

メソッドの上書き注意。 さっき作った bark というメソッドに、誤って値を代入してしまいました。
dog bark = 5
すると、5という値で上書きされて、 以降 dog bark は、5 という値を返すようになります。
 Io>dog bark
 ==>5

スロットの並び順について

アルファベット大文字小文字昇順

Objectのクローンから カメと魚を作ってみましょう。
 
 Io> turtle := Object clone
==>  Object_0x5f4e10:
 
 Io> fish := Object clone
==>  Object_0x5d5120:
 
slotSummaryを使って今の状態を調べます
 Io> slotSummary
==>  Object_0x50c5b0:
  Lobby            = Object_0x50c5b0
  Protos           = Object_0x50c0c0
  a                = 5
  c                = 8
  dog              = Object_0x111a5c0
  exit             = method(...)
  fish             = Object_0x5d5120 <------注目
  forward          = method(...)
  turtle           = Object_0x5f4e10<------注目
このことから、スロットには
英大文字ABC〜XYZ、小文字abc〜xyzの順番で
名前が並ぶことが分かりました。

クローンのクローン

クローンのローカルスコープにある変数はどうなるか

カメのクローンからミュータント・タートルを作ってみましょう。

クローンとして作り出された関係は
Object → カメ → ミュータントタートル
となっています。
 
Io> mutantturtle := turtle clone
==>  Object_0x595570:
 
Io> slotSummary
==>  Object_0x50c5b0:
  Lobby            = Object_0x50c5b0
  Protos           = Object_0x50c0c0
  a                = 5
  c                = 8
  dog              = Object_0x111a5c0
  exit             = method(...)
  fish             = Object_0x5d5120
  forward          = method(...)
  mutantturtle     = Object_0x595570 <----こいつに注目
  turtle           = Object_0x5f4e10
同名の変数があったらどうなるか?
変数 b には root_b という文字列が入っていますが、 dog オブジェクトでは既に変数 b は宣言されています。

このとき、dog にある変数 b, fish にある変数 b , mutantturtleにある変数 b にアクセスすると それぞれどんな結果が返ってくるでしょう?
// 大元のオブジェクトで 変数 b を作る。

Io> b := "root_b"
==> root_b


// それそれの結果は?
Io> dog b
==> 7
Io> fish b
==> root_b
Io> mutantturtle b
==> root_b
Io> 
dogの例を見ると、クローン元と同じ名前の変数に代入、作成が行なわれた場合、 オブジェクト固有の変数の作成/読取のほうが、優先されるようです。

その他便利な技

Iolanguage のインタラクティブモードでは、キーボードから一行づつ入力して、反応を伺うの基本ですが、数行にわたるメソッドを入力するとなると大変です。
このような時は、テキストエディタに書いたコードを、ターミナルのIolanguage端末へコピーペーストすることで、複数行を一気に入力することができます。
文法が合っていれば、改行やコメントアウト行を含んでいてもでも認識されます。

以下のforwardメソッドを使ったテストにも、この技が利用できます。

forwardメソッド

エラー等に反応し、メッセージを返す特殊なメソッド。カスタマイズして使う。

forwardメソッド とは?
ここで最初のほうで登場したforward メソッドの謎にせまります。
これはexit メソッドと一緒で少し特殊なメソッドです。

Iolanguageのリファレンスページの例を元に、 Object のクローンである dog オブジェクトの forward メソッドを上書きし、エラーを詳細にフォローするログを書き出させるよう カスタマイズしてみましょう。
dog forward := method(
    write("sender = ", call sender, "\n")
    write("message name = ", call message name, "\n")
    args := call message argsEvaluatedIn(call sender)
    args foreach(i, v, write("arg", i, " = ", v, "\n") )
)
 
forwardメソッドはどう動くか?
ここで、Object のクローンである fish と dog に わざと、存在しないメッセージ bar を送ります。 (fish のfowardメソッドには何も細工していません。)
fish は普通のエラーメッセージが返ります。
Io> fish bar

  Exception: Object does not respond to 'bar'
  ---------
  Object bar                           Command Line 1
一方 forward メソッドをカスタマイズした dog はどうでしょう?
Io> dog bar
sender =  Object_0x50c5b0:
  Lobby            = Object_0x50c5b0
  Protos           = Object_0x50c0c0
  a                = 85
  cat              = Object_0x5b0e60
  dog              = Object_0x5bbf40
  exit             = method(...)
  forward          = method(...)
  mutantdog        = Object_0x533d10

message name = bar
==> nil
Io> 

Object って何

Object って何? さて、dog も turtle も fish も、あらゆるオブジェクトも
hoge := Object clone
のように、Object のクローンとして作られていますが、 Object とは一体なんでしょう?
Ioの対話モードを使って大元のオブジェクトの内容を見ることができます。。。
Objectの内容を見たいなら単純に打つだけです。
Io>Object  
==>  Object_0x5003d0:
                   = Object_()
  !=               = Object_!=()
  -                = Object_-()
  ..               = method(arg, ...)
  <                = Object_<()
  <=               = Object_<=()
  ==               = Object_==()
     :
               (中略)
     :
  while            = Object_while()
  write            = Object_write()
  writeln          = Object_writeln()
  yield            = method(...)

予約済みの命令がリストされています。
if や else のような制御構文や、print 、for 、while とか演算子まであります。

Object には、プログラムに必要な if やfor 、print というメッセージ(命令)があらかじめ用意されいることがわかります。
オブジェクトがどんなメッセージを扱えるかを把握するにはここを見るとよいでしょう。

参考として、オブジェクトにはどんなデータ型があるか調べる方法
>Protos Core

Iolanguageにおけるメソッドの特徴。それに伴うメッセージングの連鎖(チェイン) 。 selfもあるよ。

ここでは、Iolanguageのメソッドの使い方とその作用、それに伴うメッセージングについて説明します。
メソッドは、ある言語での関数に相当します。

「メッセージでやり取りする」とはどういうこと?
Iolanguageのメッセージは、結果が、ドミノ倒しやバケツリレーのように、次にあるメッセージに連鎖する性質を持っています。
そして、各メッセージにはそれぞれオブジェクトが(あらゆるもの…メソッドやシンボル、算術記号さえも)関連づけられています。
そしてメッセージを集配するのが io の役割です

これが分かれば、コードを読むのが少し楽しくなるかもしれません。
animal := Object clone;
animal food := 5
Object clone も animal food もメッセージを送受信する関係です。
ここでもメッセージの連鎖が発生します。連鎖はセミコロン「;」または 改行 まで 続きます。
■→■→■→■→■→■→■→■→■→■→■→■→■→

(※これはDiagrams of Io Conceptsの図を参考にしています。)
self がないメソッド場合は、値が受け渡されないこともあるようです

メソッド内の場合は、メソッドの最後に書かれたメッセージが、そのまま戻り値として次へ連鎖します。

メソッドの特徴

  Iolanguage のメソッドは、いくつか異なる形態を持つことが知られています。
animal dog := method(a, a+5);
animal kennel := method(food/2);
animal cat := method( self *8 ); ← respond できないので使うとエラー( 下のとは異なるオブジェクトになる  )
animal Number cat :=method(self*8); ← 正解( ただし、これは animal オブジェクトのスロットには現れません)

● 引数 arg を取るメソッド dogの例


↓→は、メッセージの方向を表します。
   arg
   ↓
dog(...)→ 
animal dog := method(a, a+5);
Io> animal dog(1) dog(1) dog(1)  ×エラー 
Io> animal dog(1) animal dog(1) animal dog(1)  //これなら一応動く
==> 6
//この場合、メッセージの連鎖はあっても結果の引継ぎがないので、
最後のメソッドの結果=6 だけが残ります。

Io> animal dog( animal dog(animal dog(0) ) )
==> 15
//メソッド同士をどうしても 連携させたい場合は、 このように入れ子で書けば、結果が引継がれます。

●特定のオブジェクトを、selfから受け取るメソッド cat の例

self はローカルオブジェクトを表すはずですが、メッセージ文脈においては次のように機能するようです。
メソッド文脈中に登場する self には、直前のオブジェクトのmessage結果が引継がれるようです。
message→cat(self)→ 
selfを受け取るメソッドは、型を意識しなければいけません。
型は、例えば数字ならNumber 、 文字列の場合 は Sequence 、リストの場合は List です。
そのため、メッセージの前後のオブジェクトの型が違ったり、対象スロットが存在しなければ連鎖は失敗します。

間違った例
animal Number cat :=method(self*8); 
Io> cat cat cat  // 最初のオブジェクト(Number型)が存在しないので エラー
Io>"Tom" cat   // オブジェクトの型が違うのでエラー  

次に、正しく動作する例を挙げます。
値は、メッセージを介して、複数の関数へ連鎖的に受け渡すことができます。
animal dog := method(a, a+5);
animal Number cat :=method(self*8); 

Io> 2 cat cat cat // 最初のオブジェクト「2」は Number型 なので連鎖が成立。(2*8*8*8 =1024)
==> 1024
Io> 

Io> animal dog(1) cat // dog はNunber型を返すから OK( 1+5*8=48 )
==> 48

Io>  result := animal 12 cat println; // 他のメソッドprintln と組み合わせることもできます。
96
==> 96

もちろんメッセージの連鎖を利用せずに、分けて書くこともできます。
Io>result :=animal dog(1);
result := result cat;
result println;
==> 48


●今度は、両方の特徴をもつメソッド の場合

        arg
        ↓
message→add(... , self)→ 
このメソッドadd は、上のようなメッセージング構造を持ちます。
 a := 10
 b := 20
Number add := method( a ,  self *2+a );

Io> a add(2)
==> 22
Io> a add(0) 
==> 20
Io> b add(2)
==> 42
Io> 10 add(5)
==> 25

●上記の応用、 文字列と数字を扱うself型メソッド

 a := 10
 b := 20
Number add := method( a ,  self *2+a ); 
Sequence concat := method(a, self .. a )
Io> "abc" concat("bbb")
==> abcbbb

// メッセージをうまく連鎖させるために、間に asStringを挟んで数字を文字列にする。
Io> a asString concat("foobar")
==> 10foobar

// ちょっと変えてみる。asStringの前の 括弧は必要です。
Number add := method( a ,  (self *2+a) asString); 

// 途中で、オブジェクト型が数値→文字列に変化しています。
Io> 10  add(5) concat("foobar")

a

●任意の オブジェクトを渡せるタイプのmethod

このタイプのself は、直前に来る任意のオブジェクトを表し、様々な用途で利用されます。
 A := Object clone // 例によって、オブジェクト名の最初の文字は大文字にします。
 B := Object clone

Io> A
==>  A_0x5ee2f0: // ← Io 側には、正しく オブジェクトA と認識されている
  type             = "A" 


次のメソッドmethは、あるオブジェクトに対し、複数のslotを追加することができます。  A meth すると オブジェクトA に seq, num , lis が追加されます。 
 meth := method( 
		self seq := "aaa";
		self num :=5;
		self lis:=list(); 
		self //次のメッセージに渡すために必要。
  )
(※ 特定のオブジェクトを指定せずに、単独で meth すると大元のobject上にslotが作成されてしまうので注意が必要です。)

次の メソッドmeth2は、前回のと似てますが、渡すオブジェクトをAのみに制限してます。 これらメソッドを組合せ A meth meth2 と書く事でメッセージのチェインを行なうこともできます。 meth は後ろの meth2 に 「A」 を渡すので、文脈がそのまま継続します。
 meth2 := method( 
	//  オブジェクトA以外なら、すぐにメソッドを終了。
	if(self type != "A" ,   "オブジェクトの型がちがう〜!" print;  return  )

		self seq :=  "hoge"
		self num := num* 10;
		self lis:=list(1,2,3,4,5); 
		self //次のメッセージに渡すために必要。
  )

// 一方こちらはBlock 、  blo call で呼び出せますが、使い道が分からない...
 blo := block(  
		self seq := "bbb";
		self num :=1000;
		self lis:=list(); 
		self
  )


実際に使ってみましょう。 オブジェクトA専用のメソッド meth に Bを渡そうとすると 意図した通り却下されます。
Io> B meth2
オブジェクトの型がちがう〜!==> nil
// 従って 値は変更されません。


次の例では オブジェクトAに対して、2つのメソッドmeth , meth2を逐次実行
メッセージのチェインが発生し、 最終的に A num の値は50になる。
Io> A meth meth2
 ==>  A_0x5ee2f0:
  lis              = list(1, 2, 3, 4, 5) 
 num              = 50  
 seq              = "hoge" 
 type             = "A"
これらメソッドの特徴を把握しておけば、ある程度のiolanguageのコードを読解することができると思います。

メッセージング2

メッセージそのものを調べることもできます。これはデバッグ用途に
前後のメッセージを調べたり、任意のメッセージを調べたり出来ます。
詳細はこのあたり

obj := Object clone

// 引数型メソッドに渡されるメッセージを調べる
obj wtf := method(  a ,

call sender   println
"L-------------- is sender"   println
" "  println
call message println
call message name println
/*
call message previous  println
call message next println
call message next next println
call message next next name println
*/
call message argAt(0) println
"L------------- is message, etc"  println
" "  println
call activated   println
"L-------------- is activate" println
" "  println
call slotContext   println
"L-------------- is contex" println
" "  println
call target   println
"L-------------- is target" println
" "  println
)
// self型メソッドに渡されるメッセージを調べる
obj := Object clone
obj Number wtn := method( 

call sender   println
"L------------- is sender"   println
" "  println
call message   println
call message argAt(0) println
"L------------- is message"  println
" "  println
call activated   println
"L------------- is activate" println
" "  println
call slotContext   println
"L------------- is contex" println
" "  println
call target   println
"L------------- is target" println
" "  println

self +1
)
/ 引数型メソッド self型メソッド
コード obj wtf(obj b)    2 wtn println
call sender Lobby Lobby
call message wtf(obj b) wtn println
call message
call message name
call message At(0)
wtf(5)
wtf
5
なし
call message
call message name
call message At(0)
wtf(obj b)
wtf
obj b
なし
call message previous
call message next
obj
なし
2
println
call activated wtfメソッド の内容 wtn メソッド の内容
call contextSlot Objオブジェクト
Object_0x5bbe40:
b = 1
wtf = method(a, ...)
0
call target Objオブジェクト
Object_0x5bbe40:
b = 1
wtf = method(a, ...)
2

メソッド その他

●各メッセージ実行体の性質
/ do method block
スタイル do( ... ) method( ... ) block( ... )
置かれる場所 Lobby? proto self
挙動 すぐやる メッセージが
届けばやる
"block名 call"
で、初めて実行。

リスト

Io> lists := list(1, 2, 3, 4, 5, 6)
==> list(1, 2, 3, 4, 5, 6)

リスト中のn番目の要素にアクセスするなら at(n) を使う。
インデックスは0番から始まる。
Io> lists at(1) 
==>2

リストの要素数を調べるなら size
Io> lists size
==>6

文字列からリストを作る場合は Sequenceの splitメソッドを使う。 対話モードで使えば、Listオブジェクトを記述するのに、各要素を引用符で囲む手間が省けるかもしれない。
Io>  "aaa bbb ccc dde" split
==> list("aaa", "bbb", "ccc", "dde")

Range はある一定の範囲を扱うオブジェクトです。(※最初にRangeアドオンを有効にしないと動作しません。)
例えば1〜100までの数字を用意するのに、いちいち1,2,3,4,5 〜99 と書き出すのは大変です。
Rangeオブジェクトを使うと 1 to(100) と書く事ができます。
Rangeオブジェクトから Listを作るには Rangeの asListメソッドを使う。
Range//Rangeアドオンを有効にする
 1 to(5)  asList
==> list(1, 2, 3, 4, 5)

まとめ

・プログラムは、手紙に手順を書いて送る手段にすぎない。メッセージはテキストファイルに書かれてます。
・対話モードでは、手順を断片的に、場当たり的に実行。書いたメッセージは残りません。

しかし、どちらにも共通しているのは、手段は違っていても
オブジェクトのデーターベースを見ていて、
その引き出しに値を入れたり、取り出したりしています。

引き出しには単純に物が入っていたり、仕掛けが施されたものがあります。
また、既存の引き出しを元に追加したり増設したりもできます。

Io言語の場合、巨大なオブジェクトのデータベースに、
現在の自分の使えるメソッドや、値などを全て見ることが出来ます。
そして、そこにメソッドを追加、複製して枝を拡げたり、書き換えたりできます。

 

ウインドウ付きのアプリケーションを作るには?

Flux アドオンは、 Iolanguage に付属のアドオン(拡張機能)のひとつで、GUIを伴ったウインドウアプリケーションを作れるらしい。
Flux アドオンを使うには、OpenGLアドオン, Image アドオン、Cairo アドオン等、関連するアドオンもインストールしておかなければいけません。
例えば、GUIのボタンはOpenGLで描画されています。

【ウインドウが出るだけで何もしないアプリケーションを作る方法】
Flux アドオンをロード → Application オブジェクトのクローンを作成  → それを run する。

アドオンで何かしてみる

Flux アドオンをロードする

 
 Io> Flux
==>  Flux_0x1196810:
  Views            = Object_0x11967d0
  fluxPath         = "/usr/local/lib/io/addons/Flux"
  fluxSource       = "/usr/local/lib/io/addons/Flux/io/Flux"
  fluxViews        = "/usr/local/lib/io/addons/Flux/io/Flux/Views"
  loadAll          = method(...)
  type             = "Flux"

 
これで、読込まれました。

これはプログラムモードでの
#!/usr/bin/env io
Flux

というコードと同じです。

このとき、Addons スロットには、Flux に関係するアドオンのオブジェクトが読込まれます。
Io> Addons
==>  Object_0x50c820:
  Box              = Object_0x5ff0d0
  Cairo            = Object_0x5eeb90
  EditLine         = Object_0x56e9a0
  Flux             = Object_0x11daea0
  Font             = Object_0x11999f0
  Image            = Object_0x5c53a0
  OpenGL           = Object_0x5f9120
  ReadLine         = Object_0x111d130

//しかし slotには変化は無い。
Io> slotSummary
==>  Object_0x50c5b0:
  Lobby            = Object_0x50c5b0
  Protos           = Object_0x50c0c0
  exit             = method(...)
  forward          = method(...)

どちらにしろ、今の時点では、
アプリケーションウインドウは表示されません。

今度は Application オブジェクトを作成します。
これは、ウインドウやスクリーン、などのオブジェクトが整理されてタイプのウインドウアプリケーションを構築します。(←→GLApp)
注意!! :最初に Flux アドオンを読込まないと、Application オブジェクトを作ることはできません。
Io> fooApp := Application clone ==> Application_0x11d1250: mainWindow = OSWindow_0x11b7600 timers = Map_0x11d32b0 windows = list(OSWindow_0x11b7600)
オブジェクトを作るとスロットに変化が
現れます→

参考:Application オブジェクト
Io> Application
==>  Application_0x2838670:
  addWindow        = method(w, ...)
  appDidStart      = method(...)
  currentWindow    = method(...)
  display          = method(...)
  firstResponder   = nil
  fonts            = Object_0x117f680
  init             = method(...)
  keyboard         = method(key, x, y, ...)
  mainWindow       = nil
  motion           = method(x, y, ...)
  mouse            = method(b, s, x, y, ...)
  newWindow        = method(...)
  reshape          = method(w, h, ...)
  run              = method(...)
  screenHeight     = method(...)
  screenWidth      = method(...)
  setFirstResponder = method(...)
  setMainWindow    = method(...)
  setStarted       = method(...)
  setTimers        = method(...)
  setTitle         = method(title, ...)
  setWindows       = method(...)
  special          = method(key, x, y, ...)
  started          = false
  timer            = method(id, ...)
  timers           = Map_0x116a1e0
  title            = ""
  type             = "Application"
  windows          = list()
  
  
  
fooApp というアプリケーションオブジェクト が 作られました。
Io> slotSummary ==> Object_0x50c5b0: Lobby = Object_0x50c5b0 Protos = Object_0x50c0c0 Application = Application_0x117c960 <--------注目 GLObject = GLObject_0x5608b0 <--------注目 OSWindow = OSWindow_0x11c0320 <--------注目 Responder = Responder_0x1165690 <--------注目 View = View_0x11b2890 <--------注目 fooApp = Application_0x11d1250 <------- fooApp exit = method(...) forward = method(...)
ウインドウ等のオブジェクトが用意されたことを意味します。
これで プロパティ(ウインドウ表示位置や、タイトルバーの文字列など)を設定することがきます。

Applicationから作ったクローンオブジェクト をrun して
初めてウインドウが表示される。


先ほど、Io> fooApp := Application clone
したものを実行してみます
Io>fooApp run
これはプログラムモードでの
#!/usr/bin/env io
Flux
fooApp := Application clone
fooApp run

というコードと同じです。
すると内容がないウインドウが現れました。


Ioのサンプルプログラムを見ると分かりますが、
すべての処理の記述の一番最後に、この run メソッドが呼ばれています。  

プログラムモードで、getSlot や slotSummary等 を使う方法
対話モード以外でも、オブジェクトやスロットの様子を知りたい。

writeメソッドで、ターミナルへ出力することができます。

test.io
#!/usr/bin/env io
 
  write( getSlot( "Addons" ) , "\n")
   write( getSlot( "DrawText context" ) , "\n")
   write( slotNames , "\n")
   write( slotSummary , "\n")
   
Flux で ウインドウ アプリケーションを作った場合も この方法で、オブジェクトを把握することが出来ます。

FluxアドオンのGUI部品

Ioのソースファイルから、addons/Flux/samples/Calculatorディレクトリを当たって下さい。
Calc.io は簡単な電卓アプリケーションです。
ソースを見ると、TextField や Button など見慣れないオブジェクトが見つかります。
これらはどこで定義されているのでしょうか?


どこで定義されてるか調べる(これは他のアドオンでも同じです)
Ioのソースファイルから、addons/Flux/Io/Flux/Viewディレクトリを当たって下さい。
すると、BusyView.io、AlertPanel.io、Button.io、Browser.io 等ファイルがあります。
これらのファイルはIoのFluxアドオンのViewオブジェクトを構成するファイルで、Iolanguageで書かれたソースファイルです。

ファイル名がオブジェクトに対応している
これらは 各々インターフェースを担当しています。
TextFieldを知りたいなら TextField.io ソースを見てみます。

このオブジェクトは、色や背景、テクスチャ、フォント等のプロパティの他、
実際の文字列をセットする setStringsメソッド、 initメソッドが存在します。

ソースを読んで何がなんだか分からない場合は、メソッド名(&引数)やプロパティだけでも把握しておけばOK

例えば
 // まず、アプリケーションオブジェクト fooApp を作成しておきます。
 fooApp := Application clone
 
 // TextFieldオブジェクトを作成する
text := TextField clone do (position set(10, 210); size set(190, 32))

// フォントはOpenGL のテクスチャとして描画されます。
//しかしながら日本語の表示はデフォルトでは対応してません。
text font := TextField fonts system mono 

text textColor set(0.8 , 0.8 , 0.8, 1)// 文字色を指定しないと真っ黒に
text setString("Hello World" asString)// 表示する文字列

// addSubviewメソッドでTextField オブジェクトを取込めば画面に表示される
// これは他のGUI部品にもいえます。
fooApp mainWindow contentView addSubview(text) 
TextFieldのクローン オブジェクトが生成されると、init メソッドが実行されます。
その後、クローンに備わっているプロパティを設定したりメソッドを実行することができます。
 

【ボタンイベントの仕組み】


ここでは、簡単なイベント処理を取り上げます。
適当な例は、 Fluxアドオンの samples の test ディレクトリにある main2.io の動作を見ると分かります。



オブジェクトから Controller という名前のクローンを作る
Controller に push という名前のメソッドを作る

ボタンオブジェクト b を作ったら、
seActionTarget で、バインドするオブジェクト名を指定(Controller )
setAction で、そのメソッド名を指定(push)


これでボタンを押した時に、 Controller オブジェクトの push メソッドが実行される。
サンプルそのままだとなぜか真っ黒なので文字色等を変更することにした。

main2.io
#!/usr/bin/env io

Flux

// バックと文字色を設定し直した。
Controller := Object clone do(
	push := method(button,
		button setTitle("Please don't push this button again.")
	)
)

app := Application clone

app appDidStart := method(
    app mainWindow setTitle("Test")
    b := Button clone
    b setTitle("Foo")
    b setWidth(300)
    b setActionTarget(Controller)
    b setAction("push")
 	b textColor = Color clone set(1, 1, 1, 1)
	app mainWindow addSubview(b)
	app mainWindow backgroundColor set(0.5,0.5,0.5, .1)
)

app run

その他のGUI部品

配置
GUI部品は、 position set メソッドで表示指定します。このとき座標の起点(0,0)はウインドウの左下になります。  
//ボタン定義
btn := Button  clone do(
			position set(5, 400) // ウインドウの左下が(0,0)
			 size set(48, 32)
			font := TextField fonts system mono
		   	roundingSize := 40
			selectedColor := Color clone set(.3,.3,.3, 1)//選択された時の色
		    setAction("pushButton")
			textColor set(1,1,1,1) //必ず文字色を指定する。
			setTitle("btn") // ボタンの文字
 
		)
fooApp mainWindow contentView addSubview(btn) //ウインドウに追加
ダイアログ

   alert := AlertPanel clone 
   alert position set(40,51)

	alert text = "Warning Texts"
	alert okButton setTitle("OK")
	alert cancelButton setTitle("Cancel")
	 //okAction 
	 //cancelAction

	 fooApp mainWindow contentView addSubview(alert)


    gui_obj := Label clone
    gui_obj resizeHeight := 031
    gui_obj resizeWidth := 031
    gui_obj size set(80, 80)
    gui_obj position set(40,51)
     gui_obj title = "hogehoge"
     fooApp mainWindow contentView addSubview(gui_obj)

スクリーン

Screen と Subviewが用意されています。
Subviewはメイン画面とは別の描画領域で、イメージや、ボタンなどのGUI部品に利用されます。
その実体はOpenGLのテクスチャオブジェクトです。

GUI部品の配置

placeAbove(  objname )  
placeBelow(  objname )  
placeRightOf(  objname )  
placeLeftOf(  objname )  
placeLeftOf(  objname )
メソッドが用意されています。
最初のオブジェクトは、position set で指定し、次はplace系メソッドで、その隣に配置、上に配置等します。

イベント内部処理のややこしい部分は、ユーザから隠されている

Fluxによれば、GUIイベントの内部処理は、Responderオブジェクト(とそのクローンである、OSWindow → VIew → Button )は、イベント発生時の現場処理をする役目であるFirstResponderをリレーしながら、アクション(ボタンを押した等)が行なわれるらしいです。
ボタン押下時のグラフィックの変更や、クリック座標やアクションの取得が行なわれています、これらの仕組みは、Flux のシステム部分に隠蔽されているので、ユーザーは深く考える必要はありません。


Cairo アドオン

これは図形や文字を描画するCairoライブラリのアドオンです。
ベクター形式なのでいくら拡大しても粗が目立ちません。SVG,PDF,PNG形式で出力することができます。
和文TrueTypeフォントも認識し、日本語のテキストも書けます。

現時点でのIolanguageのアドオンは言語中にOpenGL やCairo文脈を混在できますが、 使い勝手にはあまり期待できません。そのためアドオン提供元となるライブラリの使い方も把握しておく必要があります。 Cairoがどういう仕組みで動作するのか把握する手がかりとして、Ruby で Cairo を使う方法が書かれた記事を見つけました。 この記事を参考に、IolanguageにおけるCairoアドオンの挙動について探っていこうと思います。

Rubist Magazine cairo: 2 次元画像描画ライブラリ
http://jp.rubyist.net/magazine/?0019-cairoによると Cairoは次のような仕組みで動作するようです。
   1. サーフェス (Cairo::Surface) を作成する。
   2. 作成したサーフェス用のコンテキスト (Cairo::Context) を作成する。
   3. コンテキストに対して描画処理を行う。
   4. サーフェスに終了メッセージを送る。
  -- 引用終わり--
つまりIoにおいても同じような作法に従えばよいことになります。
確かに、IoのCairoアドオンのサンプルプログラムにも、Context やらSurface というオブジェクト名がありました。

大事なこと

Cairo のサーフェスは3種類ほどあり、用途に応じて createする必要があります
注意しないといけないのは、これらサーフェスの描画手続きがフォーマットによって全く違うことです。
(Cairoの将来的なバージョンアップで改善されるとよいですが…)
例えば、PNGの場合は、writeToPNGメソッドを使ってファイル名を指定します、 それに対してPDFの場合は、PDFSurface オブジェクトをcreateする段階でファイル名を指定し、さらに showPage, finish メッセージを実行しないと書き出されません。 どうして指定方法がまちまちなのかはCairoライブラリの謎です。

コンテキストの構造
オブジェクト: = Object clone do(
		<<Cairoのprotoを追加>>

			サーフェス surface := Image/PDFSurface create(   サーフェスのサイズ  )   

					
					コンテキスト Context create(surface) do( 
							<<Cairoのprotoを追加>>
							<<描画コマンド>> 
					  		showPage
					 )  

			surface finish --- PDFの場合 サーフェスに終了メッセージを送る。
	)


iolanguage+CairoアドオンでPNGファイルを書き出してみよう

iolanguage付属のデモは、動作が分かりにくかったので、単純で分かりやすいサンプルプログラムを作ってみました。
IoCairo_makePng.io
#!/usr/bin/env io

 Cairo_draw := Object clone
 Cairo_draw context := do(  
	appendProto(Cairo) //Cairoの機能を用意する 

	surface := ImageSurface create(FORMAT_ARGB32, 256, 256)    // サーフェスを作成することができる(格納形式、縦、横)

		Context create(surface) do(  //サーフェス用のコンテキストを作成

					appendProto(Cairo) //このコンテキストでもCairoの機能を使えるようにする
					
					//以下、サーフェスのコンテキスト上で描画指定する。
					scale(256, 256)
					setLineWidth(0.04)

					// システムにあるフォントをロード
					selectFontFace("Apple_osaka_unicode", FONT_SLANT_NORMAL, FONT_WEIGHT_BOLD)

					setFontSize(0.25)
					moveTo(0.04, 0.53)
					showText("あぅ〜")

			getTarget writeToPNG("aaa.png") //IoCairo_makePng.ioと同じディレクトリに  PNG画像で出力
			)
 
)
	

CairoでPDFファイルを出力する場合、なぜか書式が違います。

iolanguage+CairoアドオンでPDFファイルを書き出してみよう

Ioアドオンのソースを探ったり、他言語のコードを参考にして作ったもの

IoCairo_makePDF.io
#!/usr/bin/env io

 Cairo_draw := Object clone
 Cairo_draw context := do(  
	appendProto(Cairo) //Cairoの機能を用意する、これで↓
	
 // PDF用のサーフェスを作成 (大きさは W400xH200ピクセル) 出力ファイル名もここで設定する
		surface := PDFSurface create("output.pdf" , 400,200)   

		Context create(surface) do(  //サーフェス用のコンテキストを作成

					appendProto(Cairo) //このコンテキストでもCairoの機能を使えるようにする
					
					//以下、サーフェスのコンテキスト上で描画指定する。

					scale(256, 256) //倍率
					setLineWidth(0.04)

					// システムにあるフォントをロード
					selectFontFace("Apple_osaka_unicode", FONT_SLANT_NORMAL, FONT_WEIGHT_BOLD)

					setFontSize(0.12)
					moveTo(0.04, 0.13)
					showText("Hello World こんにちわ。")
					moveTo(0.04, 0.23)
					setFontSize(0.08)
					showText("日本語の文章をPDF形式で、書き出し。")

					showPage // これもやっておきます。

			)
  					surface finish // これが無いとPDFがうまく出力されません。

)
	

これを Flux アドオンを組合わせて…例えばGUIのImageViewオブジェクトとして Cairoのイメージデータを表示するといった方法は、 まだ分かりません。わかりました。

CairoのSurface描画内容を Flux のGUI要素と一緒に表示

今まではベクトル形式の絵や図、文字を描画するCairo の使い方と、IolanguageでGUIを表示するFlux の使い方をみていきました。 しかし、アプリケーションを作るには、この両者を組合わせて使わなければいけません。

iolanguage Cairo と Fluxアドオンの連携(フォントの表示)

今回の目標は、Cairoと Flux を組合わせ、Iolanguageのウインドウアプリケーション上に、日本語の文章(の画像)を 表示させることです。

setDataWidthHeightComponentCountメソッドを使って、 CairoのImageSurfaceの内容を、Imageオブジェクトを通して、 FluxのImageViewオブジェクトへ渡しています。 これは最終的にはOpenGL のテクスチャとして描画されています。
 
IoCairo_and_Font.io
#!/usr/bin/env io

Flux
ResourceManager
ImageManager addPath(Path with(".","resources/themes/Neos"))

 fooApp := Application clone

 Cairo_draw := Object clone
 Cairo_draw context := do(  
	appendProto(Cairo) //Cairoの機能を用意する、これで↓


//	 surface:= ImageSurface createFromPNG(Path with("data", "romedalen.png")) //試験用
surface := ImageSurface create(FORMAT_ARGB32, 256, 127)    // サーフェスを作成することができる(格納形式、縦、横)


		Context create(surface) do(  //サーフェス用のコンテキストを作成

					//このコンテキストでもCairoの機能を使えるようにする
					appendProto(Cairo) 

					//以下、サーフェスのコンテキスト上で描画指定する。
					
					// Cairo Surfaceの前処理(これが無いとダメ)
						scale(256,256) // 表示比率は  1:1 にする。 これが無いと、アルファチャンネルが反転して真っ赤になる(原因不明)。
						
						
					// setSourceRGB(0.5, 0.5, 1) // サーフェス背景塗りつぶし用の色
					// rectangle(  0,0,256,100   ) // 塗りつぶしが無ければ、背景は透明になる
					 // paint  // 背景塗りつぶし用


					//以下、文字を描画    moveTo(x,y)
						setSourceRGB(1, 1 ,1) //文字色
					setLineWidth(0.04)
					selectFontFace("Apple_osaka_unicode", FONT_SLANT_NORMAL, FONT_WEIGHT_BOLD)//フォント指定

					setFontSize(0.12)
					moveTo(0.08,0.14)
					showText("CairoSurface上の")
					moveTo(0.04,0.23)
					setFontSize(0.08)
					showText("文字をFluxのGUIへ描画。")

					 setSourceSurface(surface ,0,0)	// これがないと表示されません
		 
			)

				// Flux の ImageViewオブジェクトを作成
				// これはGUI上でイメージを表示するためのオブジェクト
				
				 img :=  ImageView clone  do( 
				 			position set(50, 15);  //  実際のウインドウ上に表示される位置(左下隅がX:0,Y:0 )
							size set(256, 127)
						) 
				
							// イメージオブジェクトを作成(作業用)
							//setData〜Count メソッドを使い Cairo Surface データを、Image オブジェクトへ渡す。
								outd := Image clone
								outd  setDataWidthHeightComponentCount( surface getData,surface getWidth, surface getHeight,4)

							// Imageオブジェクトをさらに  ImageViewオブジェクトへ渡す。
							img image = outd 

				// img setImage(Image clone open(Path with(".", "hogehoge.png"))) //←普通に画像を表示させる場合

				fooApp mainWindow contentView addSubview(img )// ウインドウ上に表示させる。
)

fooApp mainWindow size set(350, 200)
fooApp mainWindow backgroundColor set(0.5 , 0.5 , 0.5 , 0.1)
 fooApp run


その他


lanchPath が返らない場合の処方。
System launchPath を使う
self setImagePath(Path with(System launchPath, "images/lotus.png"))

カレントディレクトリにする。
Path with(".", "images/lotus.png")

selectFontFaceメソッドを使えば、 MacOSX の場合、(システムorユーザーどちらでも) ライブラリ/Fonts にインストールされているフォントがロードされます。 フォント名は、PostScript名を指定します。 フォントファイル名が日本語名 ならば OSX付属の Fontbook.app で読込み、情報を見るで、PostScript名を指定。 みかちゃん.ttf → mikachan

※ PostScript名が英文以外のもは認識されません。
  半角ひらがなフォント(なつみかん等)、どせいさんフォント


OpenGL アドオン(途中)

OpenGL アドオンは Fluxアドオン , GLFW アドオンに影響を与えています。
特に、FluxアドオンのGUI部品のテーマ描画は、OpenGL のResourceManager が担当しています。

このアドオンはGLAppオブジェクトという、glutウインドウアプリケーションを作成することができます。(←→Flux のApplication)
これは、面倒な処理を一部Flux アドオンに任せており、Flux アドオンと併用することもできます。(Fluxアドオンのサンプル Slides.io参照)

2つのアプリケーションオブジェクトの比較
FluxアドオンのApplicationオブジェクト OpenGLアドオンのGLAppオブジェクト
どちらのタイプも、GLUTウインドウアプリケーション立ち上げまでは自動でしてくれます。
Flux アドオンで定義されている
(Iolanguage のFlux GUITool Kit、GUIを含め、高度なことができる)
OpenGLアドオンで定義されている
(シンプルなGLUTウインドウアプリケーション)
timerスロットあり
mouseスロットあり
keyboadスロットあり
timerスロット:=nil
mouseスロット:=nil
keyboadスロットあり
オブジェクトごとに機能が分担されている
(OSwindows.io,Screen.io, Views.io)
便利なメソッドも用意されている
(Screen.io) set3dPerspective
set2dPerspective

Screen.io のほうで初期化
glutKeyboardFunc
glutSpecialFunc
    glutMotionFunc
glutMouseFunc
GLApp.ioで、すべて定義
GLUTの初期化に必要な手続きは自動で行なわれます。
glutKeyboardFunc
glutSpecialFunc
    glutMotionFunc
glutMouseFunc

必要な処理は自分で追加しなければいけません。
 
ResourceManager
ResourceManagerからは、2つのマネージャがクローンとして派生しています。 ImageManager  認識する拡張子(png,jpg,tiff)が、addSuffixで登録されています。 addPath メソッドでは、OSX, Milk , Neosの三種類のテーマから設定されます。 Imageアドオンの機能も利用されます。 なお、Flux アドオンのインターフェース要素であるチェックボックスは、イメージ画像の取得にこれを使っています。 FontManager   認識する拡張子(ttf)が、addSuffixで登録されています。 Fontアドオンの機能も利用されます。 これは、Ioアドオンのソースファイルを見るとわかります。 /stevedekorte/addons/OpenGL/io/ResourceManager.io
 

IoAddon のサウンド周辺(未検証部分多し)

サウンド関係(特にコーデック関連)は私の環境ではビルドできませんでした、よってこの情報の信憑性は低いです。
下の図は、ソースを辿って関連性を洗い出そうとしたものです。

バージョンは2008年版なので、Iolanguage側で仕様が変わる可能性もあります。

その他

オブジェクトのチェイン − 複雑なコードを読み解く

これらのコードの断片は、IoのAddonサンプルコードから適当にもってきたものです。
複雑そうに見えても、各単語が何を意味して、どこへ係り結ばれているのかが分かれば、理解することができます。

このコード断片はどんな処理を表しているのでしょうか?

メッセージが、いくつも連なっています。すべてスペースで区切ってあるから分かりにくいですが、問題を整理して考えて行きましょう。 以下のコードは、左から右へと読んで行って下さい。
  
Context create(outputSurface) scale(w / width, h / height) setSourceSurface(inputSurface, 0, 0) paint
いきなり登場した Context が何なのか、まずはその出所から調べてみましょう。Io の対話モードの出番です。 Io> Cairo するか、Io> Cairo Context して、slot の内容が調べると、Context は、Cairo アドオンを有効にすると登場するオブジェクトだと判明します。 さらに、Io> Cairo Context すると、scaleも、setSourceSurface も pain も Context に係るメソッドだと分かります。 よって、この文章は、 Context に対して、 outputSurface を create してから、 高さと幅をそれぞれw,hで割った縮尺に scale してから 、 iputSurface の0,0 へ画像データを setSourceSurface してから、 paint する。と読み解けます。 (※チェインの矢印はオブジェクトとメソッド関係を表すものです、実際の処理は左から右へと順番に行なわれます)

次のケースは少し複雑です。

連鎖の過程で対象が、数種類のオブジェクトに変化しています。
Directory with(Flux fluxSource) files select(name endsWithSeq(".io")) foreach(f, self doFile(f path) )
このコード断片は一体どんな処理を意味しているのでしょうか?
Io> Directory すると、with と files は Directory のメソッドであることがわかります。

続けて周辺のコードに遡って、適当にピックアップしたものを、対話モードで実行してみましょう。
  
Io> Flux Io> Importer addSearchPath(Flux fluxSource) ==> list("", "/Users/usr", "/usr/local/lib/io/addons/Flux/io/Flux", "/usr/local/lib/io/addons/Flux/io/Flux/Views") Io> Directory with(Flux fluxSource) files ==> list(File_0x11bc100, File_0x11bb0e0, ...略... , File_0x11d6b40 ) Io> Directory with(Flux fluxSource) fileNames ==> list(".DS_Store", "Action.io", "Application.io", "Keyboard.io", ...略... , "TextureSet.io", "View.io") Io> Directory with(Flux fluxSource) files select(name endsWithSeq(".io")) foreach(f, self doFile(f path) )
時間があれば、対話モードを使って、コードがどのように動作するのかを、感覚的につかむことができます。
(※チェインの矢印はオブジェクトとメソッド関係を表すものです、実際の処理は左から右へと順番に行なわれます) (※ select() や foreach()メソッドは様々なオブジェクトからも利用することができますが、 この図では、連鎖後の結果から select()とforeachをListオブジェクトのメソッドとして分類しています。)

●なぜ、select()メソッドに渡すメッセージが name なのか、またなぜ最後に path メッセージを利用しているのか?
この「リストオブジェクト」の中にリストされているのが「Fileオブジェクト」だから です。
( リストオブジェクトは様々なタイプのオブジェクトを格納することができます。)
また、lolanguageの公式ドキュメントで Fileオブジェクトで使えるメソッドを探せば、「name」 と「path」というメソッドのがみつかるはずです。

もう一度最初から説明すると…。
●Directory with〜 files メソッドは何を返すか?
  
//今回のDirectory with〜 files メソッドは、リストオブジェクトを返しますがリストの中に入っているのは、実はFileオブジェクトです 。 
// 試しに、リストから1要素を取り出してみましょう。

Io> Directory with(Flux fluxSource) files at(1)
==>  File_0x11dbc20:
●name はメソッド
File_0x11dbc20← このことから、中に入っているのは Fileオブジェクトだとわかります
よってFileオブジェクトにおいてname メソッドが利用できます。(→Io リファレンス マニュアル)

File name メソッドというのは、Fileオブジェクトを Sequence オブジェクト(ファイルパス文字列)にしたものを返す機能を持ちます。
そのため、返ってきたオブジェクトに対してSequence オブジェクトの endsWithSeq(".io") も適用できる、というカラクリです。

おそらくこれは ローカルオブジェクトとして渡されるので、元データのリストオブジェクト内のFileオブジェクトには全く影響は無いと思います
(これは後のforeachメソッドでもあてはまると思われます)。
  
Io>Directory with(Flux fluxSource) files at(1) name
==>Action.io
●path も Fileオブジェクトのメソッド
さらにここでFileオブジェクトにおいてpath メソッドが利用できることがわかります。(→Io リファレンス マニュアル)
ためしに先ほどのFileオブジェクトに対してpathメソッドを使ってみます。メッセージの最後に path メソッドを追加するだけです。
  
Io> Directory with(Flux fluxSource) files at(1) path
==> /usr/local/lib/io/addons/Flux/io/Flux/Action.io
File pathメソッドはFileオブジェクトを、ファイルパス文字列(Sequenceオブジェクト)に変換する機能を持つことがわかりました。
ObjectオブジェクトのdoFileメソッドの引数には、場所を文字列として渡す必要があるため、最後にFile pathメソッドを使い文字列へ変換しなければいけないのです。

●ファイルパス情報に関する、Fileオブジェクト←→Sequence(文字列)オブジェクト の相互変換
  
 //指定したファイルパスを、 ファイルオブジェクトにするFile with(...)メソッド
 a := File with("File/path/String.ext")
==>  File_0x5a51a0:

//逆に元の文字列に戻すFile pathメソッド(メッセージ)
a  path
==>  "File/path/String.ext"
 
●Fileオブジェクトのメソッドは一般的なファイルの出入力担当。

Iolanguageのコルーチン

並列処理(Concurrency)の為の機能。
各コルーチンはスケジューラに従い、実行権限を互いに譲り合いながら、並行的に実行することができます。(コルーチンが2つの場合)

Iolanguageの公式ガイドでは、並列処理についてコルーチン、アクタ、Future 、スケジューラについて、さらりと説明してますが、いまいち分からないので調べてみました。
 アクタ(Actor)の持つ機能
  • (他の)アクタに有限個のメッセージを送信する。
  • 有限個の新たなアクタを生成する。
  • 次に受信するメッセージに対する動作を指定する。
(Wikipediaより引用)

似たような機能は、Iolanguageのメッセージング機能として既に実装されています。
ということは、少し手を加えるだけで、lolanguageで普通に行なっているメッセージング感覚でアクタを設定する事ができるはずです(後述)

●iolanguageで並列処理を作成する方法は、2種類くらい見つかってます。
  1. アクタを設定する方式
  2. coroDoLater()に渡してコルーチン化する方式
2つの方式の相違点
どちらもスケジューラを活用してコルーチンを管理する仕組みは共通です。
違うのは、アクタ方式が最終的な結果をFutureオブジェクトへ渡すという点です 。


●関連用語の整理 ●現実的なたとえ話
  1. 現在進行中の仕事として、コードを書いているとする
  2. 途中、電話がかかってきたら応対、または取次ぎしなければいけない。
  3. 途中、画像を直せと言われたら画像編集ソフトを立ち上げて編集し、相手へ渡す。
  4. 途中、来客があれば応対する。
  5. 営業に行けと言われたら、他の仕事を中断して外回りへ。
これらの1〜5の仕事(コルーチン)を並行的にこなすことは、
無意識のうちに、意識の切替えを行なっているようにも見える。
普段はモニターを注視して手を動かしている

時々、電話が鳴らないか気にかける

なんか音がしたので(来客かな?)、玄関のほうを見る。

疲れてきたので、ちょいと伸びをする。

再びモニターに目を向ける。
どんな仕事にも、小さな区切りがあります、意識を切り替えて一息つける瞬間があります
それが"yield" を置くべきタイミングと一致します。
とは限らない場合もあります。 どこにyieldをおくべきか?
●どういう順番で、コルーチンが実行されるか?(???検証の必要あり???)
Iolanguageのコルーチンの実行は、スケジューラの「キュー」よって管理されていて、最初に入れたものから順に処理します。???
このとき後から入れたものは最初の処理が終わるまで待機します(= FIFO : First In First Out)。
ただしyieldがあればその時点で他のコルーチンに実行権限が移ります

●アクタ化
普通のメッセージングをアクタ化するために、あらゆるオブジェクトにおいて、メッセージの頭に @ または @@ を付けることができます。

アクタを使う方法(簡単なほう)

●Iolanguageで、単純な並列処理を作る手順
  1. コルーチンにしたいメソッド(2つ以上)を普通にを作る (メソッドをどこに作成するかが重要)
  2. 1.のコルーチンの、処理単位の終わりに yield メソッドを配置する。yield を介し、別のコルーチンへ実行が切り替わります。
  3. 1.を呼び出すメッセージの頭に@または@@を付けてアクタ化
  4. スケジューラをセットして起動。並列処理が開始する。
 
●ここまでの事を、もう一度整理して考えると、次のように書くことができます。
Iolanguageにおいては、Future, Coroutine,Actorは、別々に使うのではなく密接に関連しています。
また、スケジューラを実行して初めて、並列処理が開始されます。
 // メソッドの所属するオブジェクトに注意する。
 obj1 := Object clone do(
# Future    Coroutine
# ↓                ↓
 a := method( list("o","a","o","u","o","a","i","a","u") foreach(a,a print;yield)  )
)

 obj2 := Object clone do(
# Future    Coroutine
# ↓                ↓
b := method( list("","h","y","","g","z","","m","s") foreach(a,a print;yield)  )
)

 obj1 @@a; obj2 @@b;   /* @a;@b の結果も Future。アクタをセットした段階では実行が保留されています = nil  */
#↑
# Actor 

while(Scheduler yieldingCoros size > 0, yield); // スケジューラの起動により並列処理が開始。訂正
#↑
# Scheduler 
# ↓ whileを使わなくても Scheduler yield を使えばコルーチンを任意に進行させることができます。 
Scheduler yield

 
スケジューラの管理下で、非同期メッセージによって、メソッドAとBが交互に行なわれ、最終的にFutureの内容が確定した段階で結果が表示されるようです。

アクタを使ったコルーチンの実験

2つのメソッドに対してコルーチンによる並列処理を行なうなら、それぞれ異なるオブジェクトにあるメソッドをアクタとして指定する必要があります。
目標「ohayogozaimasu」(おはようございます)というメッセージを出力できれば成功。
 
//普通のメッセージの結果

a := method( list("o","a","o","u","o","a","i","a","u") foreach(a,a print)  )
b := method( list("","h","y","","g","z","","m","s") foreach(a,a print)  )

Io> a
oaouoaiau==> u
Io> b
hygzms==> s
Io> a;b
oaouoaiauhygzms==> s
Io> @a;@b
oaouoaiauhygzms==> s


//実験1 //メソッドにyeild を入れてみた。 a := method( list("o","a","o","u","o","a","i","a","u") foreach(a,a print;yield) ) b := method( list("","h","y","","g","z","","m","s") foreach(a,a print;yield) ) Io> a; oaouoaiau==> nil Io> b; hygzms==> nil Io> a;b oaouoaiauhygzms==> nil Io> Io> @a;@b oaouoaiauhygzms oaouoaiauhygzms==> nil // a→b , b→a コルーチン同士を二回渡っている?
//並列処理を行なうにはスケジューラを使う必要がある。 // 失敗例 // Object直下では、スケジューラはうまく動作しないようです。 a := method( list("o","a","o","u","o","a","i","a","u") foreach(a,a print;yield) ) b := method( list("","h","y","","g","z","","m","s") foreach(a,a print;yield) ) @a;@b; ooaouoaiauaouoaiauhygzms==> nil while(Scheduler yieldingCoros size > 0, yield) ==> nil
// それなら、Objectのクローンオブジェクト上に、コルーチンを作ってみたらどうか? // 失敗例 // スケジューラは起動、しかし意図したようには動作しません…。 obj1 := Object clone do( la := list("o","a","o","u","o","a","i","a","u"); lb := list("","h","y","","g","z","","m","s"); a := method( for(a, 0, 8 , la at(a) print;yield) ); b := method( for(b, 0, 8 , lb at(b) print;yield) ); ) obj1 @@a; obj1 @@b;nil; while(Scheduler yieldingCoros size > 0, yield); ooaaoouuooaaiiaauuhhyyggzzmmss==> Coroutine_0x5755e0: parentCoroutine = Coroutine_0x118b120 result = false runLocals = Object_0x1149ed0 runMessage = future setResult(self doMessage(futur... runTarget = Object_0x1149ed0 Io>
//今度は異なるオブジェクトObj1、Obj2 にあるメソッドを、コルーチンとして並列処理してみた。 // 成功例 // やっと意図したように動作しました。 obj1 := Object clone do( la := list("o","a","o","u","o","a","i","a","u"); a := method( for(a, 0, 8 , la at(a) print;yield) ); ) obj2 := Object clone do( lb := list("","h","y","","g","z","","m","s"); b := method( for(b, 0, 8 , lb at(b) print;yield) ); ) obj1 @@a; obj2 @@b;nil; while(Scheduler yieldingCoros size > 0, yield); ohayougozaimasu==> Coroutine_0x5fb2d0: parentCoroutine = Coroutine_0x5e6850 result = false runLocals = Object_0x1112b40 runMessage = future setResult(self doMessage(futur... runTarget = Object_0x1112b40
※コルーチンが終了しても、coroDoLater() や @ を再度やり直せば、コルーチンはもう一度有効になります。

コルーチンの情報

While では Scheduler yieldingCoros の値が 1より大きい場合に繰り返すように設定してあります。 その内容を詳しく調べることにしました。

Scheduler yieldingCoros で返される リストオブジェクトは、2個のオブジェクトを格納しているようです。(これがスタック?)
またそれらが何を表しているのかをCoroutine backTraceString メソッドで調べました。
コルーチンがどのような過程を経ているのかが分かります。
 
 obj1 := Object clone do(
 la :=  list("o","a","o","u","o","a","i","a","u");
a := method( for(a, 0, 8 ,  la at(a) print;yield)  );
)

obj2 := Object clone do(
lb :=  list("","h","y","","g","z","","m","s");
b := method( for(b,  0, 8 ,  lb at(b) print; yield)  );
)
    obj1 @@a; obj2 @@b;nil;
while(Scheduler yieldingCoros size > 0,
		writeln("---------- Yielding Output ---------- ");
  yield
		writeln("--> ", Coroutine yieldingCoros size, " \n",  Coroutine yieldingCoros map(backTraceString) ); 
);
  

結果:何か表示された
---------- Yielding Output ---------- 
o--> 2 
list("  ---------
  Coroutine yield                      Actor.io 89
  Object yield                         Command Line 3
  Object b                             Command Line 1
  Future setResult                     Actor.io 121

", "  ---------
  Coroutine yield                      Actor.io 89
  Object yield                         Command Line 3
  Object a                             Command Line 1
  Future setResult                     Actor.io 121

")
---------- Yielding Output ---------- 
ha--> 2 
list("  ---------
  Coroutine yield                      Actor.io 89
  Object yield                         Command Line 3
  Object b                             Command Line 1
  Future setResult                     Actor.io 121

", "  ---------
  Coroutine yield                      Actor.io 89
  Object yield                         Command Line 3
  Object a                             Command Line 1
  Future setResult                     Actor.io 121

")
  :
 (略)
  :

---------- Yielding Output ---------- 
--> 0 
list()
==> nil
  

アクタを使わないコルーチンの制御

任意のタイミングで、コルーチンの動作を進行させる方法。
メソッドを、coroDoLater() に渡してコルーチン化し、それらをスケジューラに登録します。
(※coroDo()はすぐに実行されてしまうので、今回は使いません)
obj1 a とobj2 b 2つのメソッドがうまく協調すると「ohayougozaimasu」(おはようございます) と表示されるはずです。
 obj1 := Object clone do(
 la :=  list("o","a","o","u","o","a","i","a","u");
a := method( for(a, 0, 8 ,  la at(a) print;yield)  );
)

obj2 := Object clone do(
lb :=  list("","h","y","","g","z","","m","s");
b := method( for(b,  0, 8 ,  lb at(b) print; yield)  );
)

coro1 := coroDoLater(obj1 a )//コルーチン化
coro2 := coroDoLater(obj2 b )

Scheduler  setYieldingCoros( list( coro1,coro2 ) );//スケジューラにコルーチンを登録

Scheduler yield  //スケジューラを起動
 
後は、Scheduler yield する度にコルーチンが交互に、1回づつ呼び出されています。
「o_」→「ha」→「yo」→「u_」
《実行結果》
Io> Scheduler yield 
o ==>  Coroutine_0x559f80:
  runLocals        = Object_0x50c5b0
  runMessage       = obj2 b
  runTarget        = Object_0x50c5b0

Io> Scheduler yield 
ha ==>  Coroutine_0x559f80:
  runLocals        = Object_0x50c5b0
  runMessage       = obj2 b
  runTarget        = Object_0x50c5b0

Io> Scheduler yield
yo ==>  Coroutine_0x559f80:
  runLocals        = Object_0x50c5b0
  runMessage       = obj2 b
  runTarget        = Object_0x50c5b0

Io> Scheduler yield
u ==>  Coroutine_0x559f80:
  runLocals        = Object_0x50c5b0
  runMessage       = obj2 b
  runTarget        = Object_0x50c5b0
  
《参考》
coro1 pause コルーチン1を停止
coro1 resumeLater コルーチン1を再開(スタックに再登録される)

コルーチンの一時停止と、再開

片方のコルーチンを一時停止するとどうなるか、実験してみた。
※停止したコルーチンを再開するのに、resumeメソッド を使うと、対話モードごと強制終了してしまいました。
それで、resumeLaterメソッドを使うことにしました。
// アクタを使わないで、コルーチンを制御。

 obj1 := Object clone do(
 la :=  list("o","a","o","u","o","a","i","a","u");
a := method( for(a, 0, 18 ,  "a" print;yield)  );  // このコルーチンに来たら "a" と言う文字を表示する
)

obj2 := Object clone do(
lb :=  list("","h","y","","g","z","","m","s");
b := method( for(b,  0, 18 , "b" print; yield)  );  // このコルーチンに来たら "b" と言う文字を表示する

)

coro1 := coroDoLater(obj1 a )//コルーチン1を生成
coro2 := coroDoLater(obj2 b )//コルーチン1を生成


Scheduler  setYieldingCoros( list( coro1,coro2 ) );//スケジューラにコルーチンを登録

//Scheduler yield スケジューラを進める。
//coro1 pause  コルーチン1を停止
//coro1 resumeLater コルーチン1を再開(スタックに再登録される)


//余計な表示を出さないように、一括メッセージにしています。
"Coros ----Start " println Scheduler yield Scheduler yield Scheduler yield coro1 pause "\n coro1 pause----" println Scheduler yield Scheduler yield Scheduler yield Scheduler yield Scheduler yield Scheduler yield "\n coro1 resumeLater----" println coro1 resumeLater Scheduler yield Scheduler yield Scheduler yield Scheduler yield Scheduler yield


《実行結果》
Coros ----Start //並列動作  交互に呼び出されている。
bababa
 coro1 pause (a) //ここで、コルーチン1(a)を停止すると b だけが呼び出される。
bbbbbb
 coro1 resumeLater (a)// コルーチン1(a)を復活させると、再び交互に呼び出されている。
ababababab==>  Coroutine_0x1167750:
  runLocals        = Object_0x50c5b0
  runMessage       = obj1 a
  runTarget        = Object_0x50c5b0

Io> 
  

違い

アクタ方式とcoroDoLater方式の違いは、Future setResult オブジェクトの有無にあります
二つの方式を比較して、それぞれスケジュールされている内容をトレースした結果が以下の通りです。
@(アクタ) を使用したコルーチン coroDoLater()を使用したコルーチン
writeln("--> ", Coroutine yieldingCoros size, " \n",
  Coroutine yieldingCoros map(backTraceString) ); 

ha--> 2 
list("  ---------
  Coroutine yield                      Actor.io 89
  Object yield                         Command Line 3
  Object b                             Command Line 1
  Future setResult                     Actor.io 121
", "  ---------
  Coroutine yield                      Actor.io 89
  Object yield                         Command Line 3
  Object a                             Command Line 1
  Future setResult                     Actor.io 121

")
アクタの最終結果は、 Futureオブジェクトが受け取る。
Io> Coroutine yieldingCoros map(backTraceString)
==> list("  ---------
  Coroutine yield                      Actor.io 89
  Object yield                         Command Line 3
  Object b                             Command Line 1

", "  ---------
  Coroutine yield                      Actor.io 89
  Object yield                         Command Line 3
  Object a                             Command Line 1

")
  
参考:SchemeとActor理論に関するメモ


また、Iolanguageのソースを見てみると、アクタは次のようになっています。
 Actor.io 63〜65行目
		waitingCoros append(Scheduler currentCoroutine)
		Scheduler currentCoroutine pause

 Actor.io 89行目
Object do(
	yield := method(Coroutine currentCoroutine yield)

  
 Actor.io 121行目付近
		loop(
			while(future := actorQueue first,
				e := try(
					future setResult(self doMessage(future runMessage)) ←121行目
  

Futureであることの利点
Io 文書Io Documentation
>Io の Future は透過的だ。すなわち、その結果が使えるようになると、Future は結果自身になる。
> 透過的な FutureTransparent Futures
>自動デッドロック検知Auto deadlock detection --引用終わり--

Iolanguageのアクタの仕組み(仮説)

(このように動いていると思われます、まだ未知の部分が多いです)

コルーチンの全体の動作を把握しよう。

@ と @@の違い

@@ 演算子でスタックに溜められますが、実行は一時停止しています。これらは Scheduler yield メソッドを実行する度に、それぞれスタックから実行されます。
@ 演算子の場合は、即座にコルーチンの実行を開始します、また今までためたスタックからも矢継ぎ早にコルーチンが実行されます。

目で見るコルーチンの並行動作

(調査結果を元にした図)

複数のコルーチンの動作

@@や@で登録したコルーチンはどのような順番で実行されますか?FIFO (最後に入れたものから順に実行される)なので最後に入れたものから実行されます。
(しかしソース内部では Queue という言葉を使ってたりしている、ややこしい。)

例えば、
@@a(4);@@b(3);@@c(2);@@d(1);
  
の順番で定義されていた場合。
コルーチンA〜Dの実行は D→C→B→A の順番を1周期として繰り返されます。

yieldはどこへ置いたらいいか?

yield を for ループの終わりに置いた場合、forループの途中で復帰するため、コルーチンが終了する時に 表示-(CoroA Fin)-のタイムラグが発生します。
例:
a3 d3 c3--(CoroA Fin)-- 
  
全部のコルーチンが終わってから、コルーチンAの終了を表示してしまいます。 このような時は、forループの直後にyieldを置けば、コルーチンの最後の実行と一緒にメソッドも終了できます。


《コード&出力例について》
A @@a(3);B @@b(5);C @@c(2);D @@d(1); ← 位置について
while(Scheduler yieldingCoros size >0, yield; if( Scheduler yieldingCoros size % 1 , "" println) ); ← スタート!
  • d1--(CoroD Fin)-- c1 b1 a1← 第1周期目  
  • c2--(CoroC Fin)-- b2 a2 ← 第2周期目 
  • b3 a3--(CoroA Fin)--← 第3周期目  
  • b4
  • b5--(CoroB Fin)-- ←第5周期目 

複数のコルーチンがどう動くか調べてみた

//コルーチンとなるメソッドabcdは それぞれ 数を並行してカウントする。 yield位置は forループの最初。
A := Object clone do(  a := method( m1 , for(i, 1,m1, yield;  " a" print; i print); "--(CoroA Fin)--" print );  );
B := Object clone do(  b := method( m2 , for(i, 1, m2,yield; " b" print; i print); "--(CoroB Fin)--" print);  );
C := Object clone do(  c := method( m3 , for(i, 1, m3,yield; " c" print; i print); "--(CoroC Fin)--" print);  );
D := Object clone do(  d := method( m4 , for(i, 1, m4 ,yield; " d" print; i print); "--(CoroD Fin)--" print);  );
 
//「Scheduler yieldingCoros size  % 1」 これはキューの残り1毎に 改行することを意味します。

//D→C→B→Aの順番で残る
A @@a(4);B @@b(3);C @@c(2);D @@d(1);while(Scheduler yieldingCoros size >0, yield; if(  Scheduler yieldingCoros size  % 1  , ""  println)     );Scheduler yield
d1--(CoroD Fin)-- c1 b1 a1
 c2--(CoroC Fin)-- b2 a2
 b3--(CoroB Fin)-- a3
 a4--(CoroA Fin)--
 
 //A→B→C→Dの順番で残る
A @@a(1);B @@b(2);C @@c(3);D @@d(4);while(Scheduler yieldingCoros size > 0, yield; if(  Scheduler yieldingCoros size  % 1  , ""  println)     );Scheduler yield
 d1 c1 b1 a1--(CoroA Fin)--
 d2 c2 b2--(CoroB Fin)--
 d3 c3--(CoroC Fin)--
 d4--(CoroD Fin)--


//AとDが1回目で停止 、CとBが3回目で停止
A @@a(1);B @@b(3);C @@c(3);D @@d(1);while(Scheduler yieldingCoros size > 0, yield; if(  Scheduler yieldingCoros size  % 1  , ""  println)     );Scheduler yield
 d1--(CoroD Fin)-- c1 b1 a1--(CoroA Fin)--
 c2 b2
 c3--(CoroC Fin)-- b3--(CoroB Fin)--


//bだけ最後まで残る
A @@a(3);B @@b(5);C @@c(2);D @@d(1);while(Scheduler yieldingCoros size > 0, yield; if(  Scheduler yieldingCoros size  % 1  , ""  println)     );Scheduler yield
 d1--(CoroD Fin)-- c1 b1 a1
 c2--(CoroC Fin)-- b2 a2
 b3 a3--(CoroA Fin)--
 b4
 b5--(CoroB Fin)--

// これはどうかな?
A @@a(  B @@b(       C @@c(     D @@d(4)    )));while(Scheduler yieldingCoros size > 0, yield; if(  Scheduler yieldingCoros size  % 1  , ""  println)     );Scheduler yield
エラーでした。
  

Last modified
07.18 2つ以上あるコルーチンの場合を追加、またスタックに残っているコルーチンが最後まで実行されなかった不具合を修正。Scheduler yieldingCoros size > 1 → Scheduler yieldingCoros size > 0
07.12 アクタに関するメモを追加
06.26 メソッドに関するメモを追加
06.11 コルーチンに関するメモを追加
06.05 コルーチンに関するメモを追加
05.30 複雑なチェインに関する間違いに気がついたので、図(io-method_chain1.png)と文を加筆訂正。
04.26 Cairo → Image → ImageView へ渡せる事が分かった。
2009.04.15 初版
 

Tsukubado