2013-03-08 花粉の影響が出始めた
DCI Hansei Meetup
DCI | |
本家のDCI Meetup出てないんだけどなぜか反省して来ました。とりあえず発表資料。
みなさんわりとガチなOO老害トークしてる中で一人だけこんなんですいません。でも私にはこういうのが求められている気がしたんです。だからこれは使命感のなせる業なんです。
2013-02-16 寒風
LLVMで作る日本語プログラミング言語
https://github.com/technohippy/Kaleidoscope.ja
以前から日本語プログラミング言語って作ってみたくて、でもただのトランスレーターならともかく、まっとうにコンパイル出来てネイティブでサクサク動くようなのはどうやって作ったらいいか分からないどころか、どの辺から勉強に手をつければいいのかすら分からなくて放置してたわけです。
ところがまぁ世の中良くしたもので、LLVMというものを使えばフロントエンドを作るだけで、ややこしいところは良しなに処理してくれるそうじゃないですか。しかも最近本が出たばかり。これが日本語の予約語とか関数名・変数名とかを扱えるなら、いろいろ捗りそう。ということで試してみました。
結論から言えば、タイトルのとおり、LLVMは日本語も問題なく使えるみたいです。
サンプル: カレイドスコープ
ということで、さっそく実際に動くものを作ります。
LLVMの本家ページのチュートリアルにKaleidoscopeという言語の実装例があります。KaleidoscopeはC言語に似た簡易言語で以下のようなことができます。
- if文による分岐
- for文による繰り返し
- 関数定義
- 単項演算子・二項演算子の定義
# Compute the x'th fibonacci number. def fib(x) if x < 3 then 1 else fib(x-1)+fib(x-2) # This expression will compute the 40th number. fib(40)
http://llvm.org/docs/tutorial/LangImpl1.html#the-basic-language
これを日本語プログラミング言語にしてみることにしましょう。
まず、フロントエンドがC++だと日本語を扱うのが大変そうなので(というかそもそもC++知らないので)Rubyに移植します。
https://github.com/technohippy/Kaleidoscope.rb
面倒くさいので細かい説明はなし。C++のチュートリアルをほぼ行単位でRubyにしてるだけなので、中身の詳細については本家チュートリアルを見てください。
この中のlangimpl6.rbに以下の改造を加えます。
- 日本語を識別子とみなすように
- 予約語を日本語に変更
ソースコードの関連する部分はこう。日本語識別子はすごく適当なのできっといろいろ漏れてます。
def isalpha c; isalnum(c) && !isdigit(c) end def isalnum c; c =~ /^[^\s();:,=#!<>+\/\*\-\|\&\.]$/ end
return TOK_DEF if $IdentifierStr == '次の関数を定義' return TOK_EXTERN if $IdentifierStr == '次の関数を利用' return TOK_IF if $IdentifierStr == 'もし' return TOK_THEN if $IdentifierStr == 'が真なら' return TOK_ELSE if $IdentifierStr == 'そうではなければ' return TOK_FOR if $IdentifierStr == '次の条件で' return TOK_IN if $IdentifierStr == '以下を繰り返す' return TOK_BINARY if $IdentifierStr == '二項演算子' return TOK_UNARY if $IdentifierStr == '単項演算子'
これだけでだいたい日本語プログラミング言語として動きました。簡単ですね。
サンプルのサンプル: マンデルブロ集合
先の日本語プログラミング言語が動作することを示すために、サンプルとしてマンデルブロ集合を表示してみようと思います。
ただ、いまのところ画面に表示するための命令がなにもありません。これだけは如何ともしがたいので、まずはprintdとputchardという組み込み関数的なものを使えるようにします。
まず、C++で件の関数を定義して
#include <cstdio> //===----------------------------------------------------------------------===// // "Library" functions that can be "extern'd" from user code. //===----------------------------------------------------------------------===// /// putchard - putchar that takes a double and returns 0. extern "C" double putchard(double X) { putchar((char)X); return 0; } /// printd - printf that takes a double prints it as "%f\n", returning 0. extern "C" double printd(double X) { printf("%f\n", X); return 0; }
ビットコードに変換した後で
$ clang++ -emit-llvm -S print.cpp -o print.ll $ llvm-as print.ll
langimpl6.rb(改)内でデフォルトでそれらが使えるようにLLVM::Module.newではなく、LLVM::Module.parse_bitcodeを使うように変更します。
#$TheModule = LLVM::Module.new "my cool jit" $TheModule = LLVM::Module.parse_bitcode 'print.bc'
Kaleidoscopeの改造はこれで終わりです。この上で動作するマンデルブロ集合を表示する日本語プログラム(抜粋)は次のような感じになります。(一番最後に全文を載せています)
# ..前略.. 次の関数を定義 密度を表示する(密度) もし 密度 > 8 が真なら 次の文字コードを持つ文字を表示する(32) # ' ' そうではなければ もし 密度 > 4 が真なら 次の文字コードを持つ文字を表示する(46) # '.' そうではなければ もし 密度 > 2 が真なら 次の文字コードを持つ文字を表示する(43) # '+' そうではなければ 次の文字コードを持つ文字を表示する(42); # '*' # ある位置が発散するかどうかを決める 次の関数を定義 発散するか?(実数 虚数 繰り返し数 実数初期値 虚数初期値) もし 繰り返し数 > 255 | (実数*実数 + 虚数*虚数 > 4) が真なら 繰り返し数 そうではなければ 発散するか?(実数*実数 - 虚数*虚数 + 実数初期値, 2*実数*虚数 + 虚数初期値, 繰り返し数+1, 実数初期値, 虚数初期値); # ..後略..
早速実行してみましょう。
ちょっとわかりにくいですが、マンデルブロ集合です。
ということで、LLVMを使った日本語プログラミング言語はたぶん実現可能ですよ、というお話でした。
# 論理否定 単項演算子 次の関数を定義 単項演算子!(値) もし 値 が真なら 0 そうではなければ 1; # 負 単項演算子 次の関数を定義 単項演算子-(値) 0-値; # > を < と同じ優先順位で定義 次の関数を定義 二項演算子> 10 (左辺値 右辺値) 右辺値 < 左辺値; # 論理和 二項演算子 次の関数を定義 二項演算子| 5 (左辺値 右辺値) もし 左辺値 が真なら 1 そうではなければ もし 右辺値 が真なら 1 そうではなければ 0; # 論理積 二項演算子 次の関数を定義 二項演算子& 6 (左辺値 右辺値) もし !左辺値 が真なら 0 そうではなければ !!右辺値; # 同値比較 次の関数を定義 二項演算子 = 9 (左辺値 右辺値) !(左辺値 < 右辺値 | 左辺値 > 右辺値); # 順次実行。右の値を返す 次の関数を定義 二項演算子 : 1 (式1 式2) 式2; 次の関数を利用 putchard(char); 次の関数を定義 次の文字コードを持つ文字を表示する(文字コード) putchard(文字コード); 次の関数を定義 密度を表示する(密度) もし 密度 > 8 が真なら 次の文字コードを持つ文字を表示する(32) # ' ' そうではなければ もし 密度 > 4 が真なら 次の文字コードを持つ文字を表示する(46) # '.' そうではなければ もし 密度 > 2 が真なら 次の文字コードを持つ文字を表示する(43) # '+' そうではなければ 次の文字コードを持つ文字を表示する(42); # '*' # ある位置が発散するかどうかを決める 次の関数を定義 発散するか?(実数 虚数 繰り返し数 実数初期値 虚数初期値) もし 繰り返し数 > 255 | (実数*実数 + 虚数*虚数 > 4) が真なら 繰り返し数 そうではなければ 発散するか?(実数*実数 - 虚数*虚数 + 実数初期値, 2*実数*虚数 + 虚数初期値, 繰り返し数+1, 実数初期値, 虚数初期値); # イテレーションを抜けるのに必要だった繰り返しの回数を返す 次の関数を定義 発散するかの確認を開始する(実数 虚数) 発散するか?(実数, 虚数, 0, 実数, 虚数); # 指定された2次元の範囲内でマンデルブロ集合を計算してプロットする 次の関数を定義 マンデルブロ(xの最小値 xの最大値 xのステップ yの最小値 yの最大値 yのステップ) 次の条件で y = yの最小値, y < yの最大値, yのステップ 以下を繰り返す ( (次の条件で x = xの最小値, x < xの最大値, xのステップ 以下を繰り返す 密度を表示する(発散するかの確認を開始する(x,y))) : 次の文字コードを持つ文字を表示する(10) ) # 指定した倍率で指定された位置のマンデルブロ集合をプロットする 次の関数を定義 マンデルブロ集合を表示する(開始実数値 開始虚数値 実数値の倍率 虚数値の倍率) マンデルブロ(開始実数値, 開始実数値+実数値の倍率*78, 実数値の倍率, 開始虚数値, 開始虚数値+虚数値の倍率*40, 虚数値の倍率); マンデルブロ集合を表示する(-2.3, -1.3, 0.05, 0.07);
2013-02-08 天気はいいけど肌寒い
きつねさんはおしえてくれないruby-llvm
今年のテーマ(の一つ*1)はLLVMにしようと思い立ってちびちびお勉強してたんだけど、タイムリーなことに本日達人出版会さんから素晴らしい本が出版されまして。さすが達人出版会。いいとこついてくるわー。ていうか一週間前に欲しいってtweetしたばっかだわー。
http://tatsu-zine.com/books/llvm
ただまぁ残念なことに?こちらの本もそうなんですけどLLVMのチュートリアルはたいていの場合CとかC++の知識必須だったりするわけです。いや、読めと言われたら読むけど、正直CもC++もどっちもよく知らないからあんまり頭に入って来なくてつらい。せっかくLLVM(llc?)でコンパイルできるわけだし、フロントエンドの実装はもっと軽い言語でいいじゃない。例えばRubyとか、もしくはRubyとか。
と嘆いてたらruby-llvmというものを見つけました。名前の通りRubyでLLVMをなんかぐりぐりできるライブラリです。同僚の石川先生もコントリビュートしてるので、きっとすばらしいものです。触り始めたばっかりでよく知らないけど。
まぁそういうことで、llvmとruby-llvmとセットで覚えるかと、ここしばらく本家チュートリアルのKaleidoscopeをruby-llvmに移し替えてました。元のチュートリアル(C++)の流れをなるべく変えないようにRubyに移植してたから、ruby-llvmの使い方としてはあんまりよくない感じかもしれないけど。
LLVMの日本語の情報が少なすぎるので、ホントはできたサンプルを元に本家のチュートリアルの簡略版でも書こうかと思ってたんだけど、きつねさんが出たのでなんかもういいや。
ソースは公開しておくので、よかったら何かの参考にしてください。
https://github.com/technohippy/Kaleidoscope.rb
あ、そうだ。一点だけ。
今のところhomebrewでインストールされるruby-llvmはLLVM3.0じゃないとだめっぽいので、brew install llvm --shared する前に、ここの手順で3.0を使うように設定しましょう。怠るといろいろエラーが出ます。
*1:あとWebGLと、次点でDartlangとRails再入門
2013-01-28 快晴
顧客が本当に必要だったDCI
dci | |
顧客が説明した要件
アーキテクトのDCI
http://rubysource.com/dci-the-evolution-of-the-object-oriented-paradigm/
class TransferringMoney include Context def self.transfer source_account_id, destination_account_id, amount source = Account.find(source_account_id) destination = Account.find(destination_account_id) TransferringMoney.new(source, destination).transfer amount end attr_reader :source_account, :destination_account def initialize source_account, destination_account @source_account = source_account.extend SourceAccount @destination_account = destination_account.extend DestinationAccount end def transfer amount in_context do source_account.transfer_out amount end end module SourceAccount include ContextAccssor def transfer_out amount raise "Insufficient funds" if balance < amount decrease_balance amount context.destination_account.transfer_in amount update_log "Transferred out", amount end end module DestinationAccount include ContextAccssor def transfer_in amount increase_balance amount update_log "Transferred in", amount end end end
プログラマのMVC
class TransferringMoney def self.transfer source_account_id, destination_account_id, amount source = Account.find(source_account_id) destination = Account.find(destination_account_id) TransferringMoney.new(source, destination).transfer amount end def initialize source_account, destination_account @source_account = source_account @destination_account = destination_account end def transfer amount transfer_out amount end private def transfer_out amount raise "Insufficient funds" if balance < amount @source_account.decrease_balance amount transfer_in amount @source_account.update_log "Transferred out", amount end def transfer_in amount @destination_account.increase_balance amount @destination_account.update_log "Transferred in", amount end end
顧客が本当に必要だった関数
def transfer(source_account_id, destination_account_id, amount) source = Account.find(source_account_id) destination = Account.find(destination_account_id) source.decrease_balance amount destination.increase_balance amount destination.update_log "Transferred in", amount source.update_log "Transferred out", amount end
DCIのサンプルって「関数」でよくない?って思うようなの多いよね、と・・・。
big brother albania 6
2013/04/11 19:53
どうもありがとうございました
2013-01-23 雪が降るってホント?
DCI Meetupに参加
dci | |
・・・してません。
してないけど、想像で思いの丈を書くよ。
まず、DCIについてはちょっと前にモノ思ったんだけど、togetter見る限り、疑問の一つに関してDCI Meetupで一応の回答が得られた模様。
その疑問というのは「ロールはextend Moduleではなくて、Adapterで実装してはいかんのか?」ということなんだけど、回答としては要するに「ロールが異なっても同じオブジェクトなんだからアイデンティティを同一に保ちたい」ということらしい。
ただ、言わんとすることは分かったけど、実はあんまり納得できてなくて、とあるコンテキストで何かしらのロールを果たしているオブジェクトのアイデンティティってそんなに重要なんだろうか?
ミッキーマウスがステージで踊ってるのを見るときに中の人が誰かなんてどうでもいいというか、むしろ中の人なんていないことにして見るのが正解であるように、ロールを果たしているオブジェクトが実際には何か、なんてむしろ考えずに済むのが理想だし、そのためのコンテキストじゃないかと思うんだけど。
あと例えば、とあるコンテキストにロールが二つ登場するとして、実際にはそれらの実体が同じオブジェクトになるということもあり得ると思う。そうした場合に、アイデンティティが同じということは「オブジェクト自身」はコンテキスト内で自分がどのロールを果たしているかを知らないということになる。
もちろんプログラマはそのオブジェクトを参照している変数名でロールを知ることができるんだろうけど、オブジェクト自身は普通自分がどういう変数名で参照されているかなんてわからないわけだから、自分のロールを知る術がない*1。このことは「リフレクション重要」っていうCoplien自身の言葉とずれがあるように思う。
DCIは人間讃歌
dci | |
ただここまで書いといてアレだけど、DCIについては実装方法とかは瑣末な話で、もともと アレグザンダーとかその辺が大本にあって、目指すところはもうちょっと電波な思想的な話みたい。DCI Meetupでは「DCIは世界を変える方法」とか言ったみたいだけど、まぁ要するに世界の見方を変えようって話なのかな。
これ(DCI)は単なるソフトウェアフレームワークではないし、メンタルモデルをとらえるためのパターンランゲージに留まるものでもない。コンピュータソフトウェアにおける人間的要素を賛美するような世界観をとらえるパラダイムなのである。
(http://dl.dropbox.com/u/15791171/sa10-JimCoplien_Patterns_ja.pdf)
これまでのOO設計は静的な構造を捉えることばかりに囚われていて、ユーザーがそのシステムをどのように捉えているのかと視点に欠けている部分があった。アーキテクチャからメンタルモデルへ、つまり「世界がどういう形であるか」から「世界がどういう形であるとユーザーは考えているのか」へ設計の目指すところを変えたのがDCIだと思う。
ユーザーのメンタルモデルという視点にたてば、それは固定されたものではなく、ユースケースごとに変化するものであるはず。と言っても完全に不定では何が何だか分からない。システムにはこれまで同様静的な部分も当然ある。この一時的な構造と、永続的な構造を分かりやすく分離するために導入されたのが、ロールでありコンテキストだろう。
つまり、実際のシステムのコアになる部分は静的・永続的なものであって、オブジェクト(データ)はそこに属する。ここまではこれまでと変わらない。一方ユースケースごとにユーザーの中に構築されるメンタルモデルは動的・一時的で、そこではオブジェクトがコンテキストに応じて様々なロールに射影されている。この部分がこれまでのMVCには欠けていたもので、DCIによって新しく導入されるもの。
敢えて視線をイデアから人間の住む世界である洞窟の影へ。これがDCIは「コンピュータソフトウェアにおける人間的要素を賛美するような世界観をとらえるパラダイム」という言葉の意味じゃないだろうか。
要するにDCIは人間讃歌で勇気の讃歌。
*1:extendによる実装ではobj.my_role みたいなメソッドを実現する手段がないということです






