いくつかコードを書き散らし、なんとなく要領が判ってきた感じがす るので、いよいよRuby/Tkに挑む。
知る限り、Ruby/Tkの文献は少ない。千数百円を投ずるのが厭わしく ない人で、かつ256倍文体に免疫のある人は、『Rubyを256倍使うための 本・界道編』を買うのもいいだろう。ぼくもこれを参考にした。
実際の作業とはちょっと前後するが、立志編 で作ったtanshi.rbに手を入れる。あれでは「日本の短詩」の基底 クラスだけを定義していたが、短歌や俳句も含めることにする。蟲が見 つかったのでそれも直した。
tanshi.rbに短歌や俳句のクラスを入れて実験したら、短歌の筈なのに
五七五が出てくる。どうも、フォーマットを保持するクラス変数
@@fomroがいけないようだ。ものの本を見ると「クラス変数はサ
ブクラスで新しく作られることはなく、スーパークラスの変数が使われ
まっす」と書いてある。なるほど。最後に定義したクラスのフォーマッ
トが短詩クラス一族全体のフォーマットになってしまったんだな。
では、定数を使うべきなのか、「クラスのインスタンス変数」を使う か、クラスインスタンスのインスタンス変数か? 試行錯誤の末定数を 使うことにしたが、そうすると今度は短歌も俳句も何も生成されない。 基底クラスTanshiで空の配列にしているのが災いしているらしい。どー すりゃいいんだ?
ということで、次のようになった。
1: # tanshi.rb -- rafinado de programo generanta japanajn mallongajn
2: # poemojn
3: #
4:
5: require 'jcode'
6: $KCODE = "sjis"
7:
8: # 短歌、俳句など日本の短詩のスーパークラス
9: class Tanshi
11: Cand = "あいうえおかきくけこさしすせそたちつてとなにぬねの" +
12: "はひふへほまみむんもやゆよらりるれろわをん" +
13: "がぎぐげござじずぜぞだぢづでどばびぶべぼぱぴぷぺぽゐゑ"
14:
15: Formo = [] # その詩の連ごとの語数の配列(俳句なら5, 7, 5)
16:
17: # 内部メソッド: 次の一文字を選びとる
18: def sekva()
19: nth = rand(Cand.jlength) * 2
20: ## DEBUG printf "deb %d %s in %d\n", nth, s[nth..nth+1], s.jlength
21: Cand[nth..nth+1]
22: end
23:
24: # 短詩をひとつ捻り出す。引数はその詩型のフォーマット(韻律)
25: def generi(formo)
26: uta = ""
27: for n in formo do # nは第x連の文字数を表す
28: 1.upto(n){|m| # 悪いけどmに用はない
29: uta << sekva()
31: }
32: uta << " " # 連の区切り
33: end
34: uta.sub(/ $/, "") # 末尾の全角空白を取り除く
35: end
36: end
37:
38: # 短歌クラス
39: class Tanka < Tanshi
30: Formo = [5, 7, 5, 7, 7]
41: # generiを再定義し、親クラスのgeneriをフォーマットを与えて呼び
42: # 出す
43: def generi
44: super(Formo)
45: end
46: end
47:
48: # 俳句クラス
49: class Haiku < Tanshi
50: Formo = [5, 7, 5]
51: # 短歌と同様
52: def generi
53: super(Formo)
54: end
55: end
56:
57: # end of file
generi自体は親クラスの手続きを呼び出して済ませたいのだが、 Tanshi#generiの中で参照しているフォーマットFormoは親クラスのもの が使われる(Rubyは静的なスコープを持つので、考えてみればこれは当 然だ)。そこで、Tanshi#generiに引数としてフォーマットをとるよう にし、サブクラスではgeneriを再定義した。
考えてみれば、それならフォーマットは定数として持つ必要もなく、
class Tanka
def generi
super([5, 7, 5, 7, 7])
end
end
だっていいことになる。この方がよいかも知れないかも知れないかも。
それにしても再定義というのはなんだか気持が悪い(どーしてだろ う?)。こんな風にしたっていい筈である。
class Tanshi
def initialize(formo)
@formo = formo
end
def generi
for n in @formo
...
end
end
# 短歌はインスタンスだ
tanka = Tanshi.new([5, 7, 5, 7, 7])
tanka.generi
...
このコードは机上の作品で未実験だけれど、仮にこれでうまくいくと しても、これはまた気分が悪い。「和歌」というのはやはりひとつの類 型であろう。だからクラスとして定義したい。問題は「各詩型のフォー マットを各サブクラスで保持しつつ、親クラスのメソッドgeneriで参照 するにはどうするか」という点なのだが……でも深く考え込むのはやめ ておこうっと。
これまでRuby/Tkでちょっとしたお遊び程度のものは書いたが、具体 的に何かするものを書くのは初めてで(ハナモゲラ和歌もお遊びなんだ が)、どうするものかしばらく考えていたわけで。
それにしても、昔むかしはGUIプログラミングといえばおいそれとは できないものだった。リソースエディタで部品を作ったり(マッキントッ シュのResEditで遊んだのを思い出すな)、それをCプログラムから操っ たり、それはもう大騒ぎだった。それが今やスクリプトで気楽に書くこ とができる。それ自体はすごい進歩であり、天国である(GUIにうっか り手を出すと大変な目に遭うので、その意味では地獄もまた身近になっ たのだが)。
というわけだから、気楽に書いてみよう。
Tcl/Tkのバージョンによっては、以下のプログラムは動かなかったり 変な振舞いをする可能性がある。ぼくはバージョン8.3.4というのを使っ ている。
1: #! c:/loka/ruby/bin/ruby
2:
3: require 'tk' # Tkを使うときのお約束
4: require 'tanshi' # わがハナモゲラ短詩クラス
5:
6: tanka = Tanka.new # 短歌オブジェクトを作る
7:
8: font = TkFont.new(['mincho', 12]) # (1)
9:
10: txt = TkText.new(nil, 'font' => font, # (2)
11: 'width' => 40,
12: 'height' => 12
13: ).pack
14:
15: TkButton.new(nil, 'text' => '詠む'){
16: command(
17: proc{ # (3)
18: isshu = tanka.generi
19: isshu << "\n\n"
20: txt.insert("end", isshu) # (4)
21: })
22: pack
23: }
24: TkButton.new(nil, 'text' => 'もう止める',
25: 'command' => proc{TkRoot.new.destroy}).pack
26:
27: Tk.mainloop # (5)
試行錯誤というよりは勉強と実験を繰り返して辿り着いたのが上のコー ド。
なお、「もう止める」ボタンを押したときに、TkRoot.new.destroy
と、TkRootにdestroyメッセージを送っている。単純に
exitでもいい筈なのだが、ぼくの今の環境(MS-Windows Me + tcsh)
では具合が悪くなることがあるようなので(こういう曖昧な言い方をし
なければならないところがシアトルものの奥ゆかしいところ)このよう
にした。
それから、ふたつのボタン(「詠む」と「もう止める」)で、 command を書く場所が違っている。「詠む」ではnewメソッドに渡すブ ロックの中で、「もう止める」ではnewメソッドの引数として、それぞ れ書いているが、効果はどちらも同じだそうだ。
使い方はなんとなく判ったので、一気にステップアップを図る。
上に挙げたことは大概タネ本に出ているのでそんなに悩むことはない。 でも本に出ているままをやって「できたできた♪」と喜ぶのも芸がない ので、いろいろいぢってみる。
1: #! c:/loka/ruby/bin/ruby
2:
3: require 'tk'
4: require 'tanshi'
5:
6: tanka = Tanka.new
7:
8: # ファイル名をつくるために
9: $default_fname = Time::new.strftime("waka%y%m%d.txt")
10: print $default_fname, "\n"
11:
12: # 直前に書込されたファイル名
13: $jxus_skribita = $default_fname
14:
15: # タイトルバーをそれらしく
16: $root = TkRoot.new # (A)
17: $root.title('へらさけの はなもげらわか あのよろし')
18:
19: font = TkFont.new(['mincho', 12])
20:
21: # メニューバーとメニュー
22: TkFrame.new{ |f| # (B)
23: pack('fill' => 'x', 'side' => 'top')
24: # Fileメニュー
25: TkMenubutton.new(f, 'text' => 'File'){|mb| # (C)
26: pack('side' => 'left')
27: mb.menu(menu = TkMenu.new(mb, 'tearoff' => false))
28: menu.add('command', 'label' => 'New') # (D)
29: menu.add('command', 'label' => 'Open...',
30: 'command' => proc{do_file_open})
31: menu.add('separator')
32: menu.add('command', 'label' => 'Save...',
33: 'command' => proc{do_file_save})
34: menu.add('separator')
35: menu.add('command', 'label' => 'Quit',
36: 'command' => proc{do_quit})
37: }
38:
39: # Editメニュー。マッキントッシュのご作法を忘れてしまた。
40: # 選択範囲とクリップボードの合わせ技になるので今は手が回らない。
41: # 後の楽しみにとっておこう。
42: TkMenubutton.new(f, 'text' => 'Edit'){|mb|
43: pack('side' => 'left')
44: mb.menu(menu = TkMenu.new(mb, 'tearoff' => false))
45: menu.add('command', 'label' => 'Undo') # (E)
46: menu.add('separator')
47: menu.add('command', 'label' => 'Cut')
48: menu.add('command', 'label' => 'Copy')
49: menu.add('command', 'label' => 'Paste')
50: menu.add('separator')
51: menu.add('command', 'label' => 'Clear All',
52: 'command' => proc{do_clear_all})
53: menu.add('separator')
54: menu.add('command', 'label' => 'Select All')
55: }
56: }
57:
58: # (F)
59: def do_file_open
60: Tk.getOpenFile
61: end
62:
63: def do_file_save
64: fname = Tk.getSaveFile('initialfile' => $jxus_skribita)
65: if fname == '' then return end
66: # ここで参照するテキストウィジェットは広域変数でなければならな
67: # い。メニューコマンドの定義をテキストウィジェット生成の後にす
68: # れば局所変数でも大丈夫なのだろうが。
69: begin
70: open(fname, "w") do |f|
71: f.write($txt.get('1.0', 'end'))
72: end
73: ensure
74: if(fname != $jxus_skribita) then
75: $jxus_skribita = fname
76: end
77: end
78: end
79:
80: def do_quit # (G)
81: # ダイアログを作りたい。TkFrameの応用でできないかな?
82: v = TkVariable.new('momo')
83: dlg = TkToplevel.new(nil){ |w|
84: TkLabel.new(w, 'text' => 'Cxu vi vere deiras?').pack
85: TkButton.new(w, 'text' => 'Jes'){
86: # 本当はボタンが押されてこのブロックを抜け出た後でdestroy
87: # したいのだが、うまく行かないのでここで呼んでしまう。
88: # ちょと乱暴。
89: command(proc{
90: $root.destroy
91: })
92: pack('side' => 'left')
93: }
94: TkButton.new(w, 'text' => 'Ne'){
95: command(proc{
96: v.value = "no"
97: w.destroy
98: })
99: pack('side' => 'right')
100: }
101: }
102: # 上のコードだけでは「ダイアログの中に制御が留まる」は実現でき
103: # ず、下のprint文が実行されてしまう。何らかの仕組みが必要。
104: print "#{v.value}\n"
105: end
106:
107: def do_undo
108: end
109:
110: def do_cut
111: end
112:
113: def do_copy
114: end
115:
116: def do_paste
117: end
118:
119: def do_clear_all # (H)
120: $txt.delete('1.0', 'end')
121: end
122:
123: def do_select_all
124: end
125:
126: # 短歌表示領域を乗せるフレーム
127: frm1 = TkFrame.new.pack('side' => 'top', 'anchor' => 'n')
128:
129: # 短歌表示領域は上に。スクロールバーをつける (I)
130: sb = TkScrollbar.new(frm1).pack('fill' => 'y', 'side' => 'right')
131:
132: $txt = TkText.new(frm1, 'font' => font,
133: 'width' => 40,
134: 'height' => 20
135: ).pack('side' => 'right')
136:
137: # これらはそれぞれのnewメソッドに渡すブロック内で書いても怒られ
138: # ないが、実行時エラーになる。sbなりtxtなりはそれぞれのブロック
139: # 内スコープで束縛されるので未定義シンボルになるためと思われる
140: sb.command(proc{|args| txt.yview *args})
141: $txt.yscrollcommand(proc{|first, last| sb.set(first, last)})
142:
143: # フレームを使ってボタンを左右に配置 (J)
144: frm2 = TkFrame.new.pack('side' => 'bottom', 'anchor' => 's')
145:
146: TkButton.new(frm2, 'text' => '詠む'){
147: command(
148: proc{
149: isshu = tanka.generi
150: isshu << "\n\n"
151: # ここではtxtはローカル変数でも参照できた。トップレ
152: # ベルのローカルスコープに入っているからか
153: $txt.insert('end', isshu)
154: $txt.see('end')
155: print "--> #{isshu}" # デバッグ文
156: })
157: pack('side' => 'left')
158: }
159:
160: Tk.mainloop
161:
162: # end of file
ついこの間Rubyに触り始めたのに加え、Tcl/Tkも詳しく知らない状態 では、この辺りが限度。
しかし見方を変えると、Tcl/Tkのまともなリファレンスが手許にあれ ば、後は見よう見まねと試行錯誤でなんとかやっていけるのではないか と思える。「挑戦してみよう」という気持になりやすいのはうれしい。
Ruby/Tkといっても、流儀のようなものがいくつかあるそうだ。ひと つはここに紹介したように「TkのウィジェットをRuby風に操る」方法で、 ご覧のようにメソッド名やウィジェットの属性がTkであることを物語っ ている以外はすっかりRubyである。tk.rbというライブラリを使う。も うひとつは、Tck/Tkのコマンドをほぼそのまま書いて呼び出す流儀で、 tcltklib.rbというライブラリを使う。
ぼくは初めは後者の方法、つまり「Tcl/Tkの文を書いて、Rubyから呼 び出す」というので始めようと思っていた。どうせTcl/Tkのウィジェッ トを使うのならTcl/Tkが見えてもいいだろうと思ったし、隠されること によって自分で細かいことをやりたくなった時に途方にくれるのは厭だ し、コードが短くなりそうだし、動作もこの方が速い(感じたのは立ち 上がり時。実行速度は判らない)。
しかし、そのやり方ではTcl/TkのコードとRuby のコードがどのよう に連携をとるのか判らない。GUI を操るのはTcl/Tk部分だが、実際に何 か仕事をするのはRubyのコードだ(そうでなければわざわざRubyから呼 ぶ必要もなく、ぜんぶTcl/Tkで書けばよい)。この両者の間の情報やデー タの受け渡しの仕方が判らないので諦めた。何しろ文献がものすごく少 ないのだ。
いくらか書いてみると、立ち上がりが遅いのも慣れれば平気だし(笑)、 Rubyらしく書けるのはやはり重要だ。
スクリプトレベルでGUIプログラミングを楽しめるようになったのは いいけれど、I18N(InternationalizatioN)とかL10N(LocalizationN)は どーなるんだろう。Xウィンドウシステムのメッセージカタログ方式は 使えなさそうに見える。自分が書いたスクリプトが、あまりに出来がよ くてよその国で使われ始めた、ということはありそうな話だ。その時に 言語の違いをどう処理するのか。
もしかしたら国際化・地域化は「スクリプト言語」「インタープリタ」 の意外な弱点かも知れない。
まースクリプトだから気にしなくてもいいのかも(笑)。Rubyプログラ ム内で環境変数LANGを見て分岐するとかできなくはないし。でも誰か既 にそういうところまで面倒見るモジュールなど書いているのでは。
Tcl/Tkの最大の功績は何といってもGUIを手軽に気楽にプログラムで きるようにしたことに違いない。開発されたのは1990年代の初め頃だっ ただろうか。身の回りで話題に上るようになったのは1994,5年頃だった ような気がする。その頃、Tclとはまったく何の関係もなく、「かんたん グラフィックライブラリ」といったものを考えたりしていた。GUIを記述 できるスクリプト言語レベルのものを考えもした(まさにTcl/Tk)。
おそらく他にも同じようなことを考えた人はたくさんいる筈だ。 GUIを構成する部品はウィンドウシステムが提供してくれるし、動的に 生成して表示することもできる。多くは定型的なものだ。これらの使い 方を記述する規則を言語に仕立てられれば、定型的で面倒な仕事をイン タープリターが請け負って、GUIプログラミングは楽になる。遥か太古 の昔(というほどではないけど)にはマッキントッシュのHyperCardと いうのもあったから大した思いつきではない。で、ぼくなどが作るまで もなく、実際に楽になったというわけだ。
個人で使うツールなどにはこれで充分だと思うし、仕事でも事情が許 せば使っていいのではないか。もはやCでごりごりGUIする時代じゃあり ませんね(いや、そういうこともまだ必要になることもあり得るし、も のの仕組みを知る上では一度は勉強しておいた方がいいには違いないが)。
しばらく前に、Solarisで必要があって超簡単なGUIプログラムを作る 機会があった。アプリケーションビルダというものの助けを借りて、仕 上がるまでに1〜2人日くらいかかったのだけれど、Tcl/Tkとかで書けば 数時間だったのだろう。しかしそれはお堅い仕事で、サーバーに不必要 なプログラム(言語処理系も含む)は載せてはいけないのだった。 Ruby/Tkともなればふたつの処理系をインストールしておく必要がある。 障害発生時にスタンドアロンのプログラムならそれだけを調べれば済む のに対して、調査が面倒になるということでもある。
ということはあるにしても、楽をしたかったり楽しみたかったりする 分にはいうことのない環境ができてくれたことは素晴らしい。
よんどころない事情からMS-Windows Meを搭載するLibretto L1という PCを半年ばかり使っていた。Librettoには不満はなかったのだが、オペ レーティングシステムには大いに文句があった。もしそれをオペレーティ ングシステムと呼ぶならばだが。
「いつかFreeBSDに入れ替えてやる」といつものことを思いながらだ ましだまし使っていたのだけれど、Rubyの勉強を始めるようになって、 我慢ならなくなった。RubyはUnix生まれの言語であり、仕様にUnixを想 定したところが多多あるほか、処理系もCUIを基本としている。それで ぼくもtcshから動かしたりするのだが、
tcshは「ティー・シーシェル」と読む。BSD系Unixの標準シェルである csh(シーシェル)を拡張・改良したもの。MS-Windowsに移植されても いる。
tcshはTWENEX Cshellの略だそうだ。伝説のOS・TWENEXのシェルも相当 使いやすかったと見える。
MS-Windows Me のコンソールアプリケーション回り(何かのドライバな のかエミュレータなのかプロセス管理なのかどこなのかは皆目見当もつ かない)は かなり不安定で、危なっかしくて使っていられな い。
まずまともに使い続けることができない。ちょっとしたことでコンソー ルウィンドウがうんともすんとも言わなくなるし、何ともないようでも 数回Rubyを動かすとだんまりを決め込んでしまう。それだけならばまだ よいが(よくないんだよ)、ハングアップしたそのプロセスを殺すと MS-Windows Me というOS自体(もしそれがOSであるならばだが)が具合 悪くなってしまうようなんだな。MS-Windows MeがWin95以来の流れを継 ぐ、16ビットコード・32ビットコード混在のシステムであるだろうこと は一見して判るが、なぜこんなものを出荷したのか理解に苦しむ。 MS-Windows 2000はなかなかにこなれて、「素人さん」でもそこそこ使 えそうな仕上がりになっているのだが。
こんな調子ではストレスばか り溜まって Rubyの勉強などできない。それどころか けっきょく自分がやりたいこと がすべてできない。何かほんの二年前にも同じことを経 験した気がする。しかし以前ほどゆとりがないのでBSDへの復帰は見送っ て、ひとまずMS-Windows 2000に避難することにした。
おかげで少しは快適である。よーしばりばりRubyで遊ぶぞ。しかし、
誰の「おかげ」なんだ一体? 何を喜ぶことがあるんだ?
(--#)
(2002.03.31)
(C) ©Copyright Noboru HIWAMATA (nulpleno). All rights reserved.