Upload
mitamex4u
View
4.360
Download
0
Embed Size (px)
Citation preview
50 万行オーダーのプロジェクトを俺 Lisp で書く
プロフィール
• mitamex こと日向野保夫と申します。• 10年前にゼンリン電子地図帳 Zi を開発– Zi8 まで
• Web 系 GIS とか地図系のプログラムを多く作る。
• 株式会社匠技研所属– http://www.takugi.com
• ドコモ標準バンドルのゼンリン地図+ナビの開発に携わる。
WWW,Mail
• http://d.hatena.ne.jp/mitamex4u/
• higano あっとまーく takugi.com• mitamex4u あっとまーく gmail.com
ゼンリン電子地図帳 Zi
• 開発期間6ヶ月• C++( 一部アセンブラ )
で 25 万行のコード• 全体で 45 万行ぐらい• 主要な開発は2人
ゼンリン地図+ナビ• スザンヌのやつです• ドコモの携帯に標準バ
ンドルされています• Java で 10 万行ぐらい• スクリプト、サーバー
サイドのプログラムいっぱい
• 全体だと 100 万行超える
• 開発者いっぱい
10 年の月日が流れ
• 個人
• ターゲット上で開発
• C++/ アセンブラ
• 仮想メモリ
• チーム
• エミュレーターで開発
• Java/ スクリプト /CGI
• 厳しいメモリー制限
プロジェクトを楽にしたい!つらい思いはしたくない!
目的はプロジェクトの成功!
• Lisp を使いたいからじゃない。• 楽をしたいから Lisp を使う。• Lisp に不得意な部分は無理せずにホスト
言語( Java 等)で記述する。• とにかく動くものを早く作りたい。–動くものを見るとお客さんは安心します。
実際問題としてLisp しか選択肢がなかった
• 小さくて高速なコア–携帯の限られたリソース
• 携帯上でコンパイルができ、 REPL が動く。
チーム開発では• 一人で作っているならどんな言語を使おう
がかまわない• チームで作るなら、チームのみなさんに Lisp
を使っていただかなくてはならない。• 拒絶反応を示す人は絶対にいる。– Lisp は Algol 系の言語を使ってきたプログラマー
にはあまりにも敷居が高い。• それでもプロジェクトは待ってくれない。– チームの全員を戦力にできるような作戦が必要。– 目的は Lisp を使うことではなく、プロジェクト
を成功させること!
L4u
• Lisp for you!• Lisp 大好きっ子が進めたプロジェクトではな
い– 必要の中から生まれた
• チームによる大規模開発に使える Lisp• 携帯からサーバーまでカバーするスケーラビ
リティ• 携帯上でデバッグ、リモートデバッグ可能• 超並列型• プロトタイプ -> 最適化
L4u 誕生の経緯
• 905 で使っていたスクリプトの問題点を解決したかった。
• 携帯で Erlang を動かしたかった。• Wikipedia に載っている全言語+いっぱい調
査。先祖帰りして Lisp にたどりつく。• その結果、 Lisp でいいじゃん、という結論
に至る。• 学術的興味ではなく、プロジェクトを進め
るための必要性の中から生まれた実用的な言語。
L4u の特徴
• 携帯からサーバーまでカバーしうるスケーラビリティ。
• S 式のパワーそのままに、人間にやさしい構文– Ruby みたいな普通の言語っぽい見た目の Lisp
• Erlang みたいな超並列型– Erlang より並列化しやすいよ
• ホスト言語との連携を前提とした自己主張しない言語。– glue 言語
プロトタイプから最適化
• L4u だけでは速度に限界• 実行するだけで各関数の呼び出し回数・
実行時間がわかる–プロファイラー装備
• 高速化が必要な部分は L4u->ホスト言語
• 2バージョン同時実行によるデバッグ–関数型だからできる
超並列型はチーム開発に向いている
• Erlang のような超並列型 = アクター• クライアント / サーバーと同じような感覚–開発を切り分けしやすい
実行速度、メモリーやコードサイズは?
• L4u は圧縮して 100KiB ぐらい– DoJa では 1MiB までしかコードを入れられない• S 式に統一することで、 XML デコーダー等
が不要になり、全体ではコードサイズが減少
• 継続・超並列型により、苦労して作っていた部分が平易なコードで作れてサイズ減少
• 905 のスクリプトより2ケタ高速。メモリー使用量も大幅に減少。
現在動作しているプラットフォーム
• DoJa MIDP などの携帯電話プラットフォーム– 3 つのプロジェクトに L4u を使っています
• Linux,Windows の Java環境–サーバーサイドを L4u で書いています–ツールを書いて使ってます
• Windows の .NET framework– Managed C++,C# と連携して、 OpenGL や DirectX
も使用可能。– AR とかやってます
みんなに使ってもらうためにエディタも作りました
• JE という PureJava のエディタを改造
• L4u の普通の言語っぽい構文に対応。
• メッセージ通信で他のプロセスと連携
• 誰か Eclipse で作ってくれ! 名前は Eclispでお願いします。
• 2009 年オープンソースで公開予定• 開発者募集中!– Objective-C版がほしいよ
• 使いたい人は連絡ください– mitamex4u あっとまーく gmail.com– higano あっとまーく takugi.com
まずは S式
tuple を導入• タプルは評価してもそれ自体を返す List みたいなもの• {a b} (tuple a b)• '{a b} (tuple 'a 'b) • タプルですか? (tuple? {1 2})
• SXML より直観的にわかりやすくなる。SXML版(html(body (@ (bgcolor 0xff0000)) " はろー "
L4u SXML版(html(body {bgcolor 0xff0000} " はろー "
XHTML<body bgcolor="#ffff00" text="#000000" link="#ff0000">
「タイトル :1」「タイトル : <b>bボールド 2</b>」「タイトル :<strong>strongボールド 3</strong>」<hr size=5 noshade />「タイトル :<i>i イタリック 1</i>」「タイトル :<em>em イタリック 2</em>」 <br />「タイトル :<b><i>b+iボールド + イタリック </i></b>」 <br />
<img src="p32.gif" id="p320" />
<dl><dt>HTML
<dd>HyperText Markup Language の略で・・・ </dd></dt><dt>WWW
<dd>World Wide Web の略で・・・ </dd></dt>
</dl></body>
tuple を使うと・・L4u SXML
(body {bgcolor "#ffff00" } {text "#000000" } {link "#ff0000"}"「タイトル :1」 ""「タイトル :" (b "bボールド 2") "」 ""「タイトル :" (strong "strongボールド 3") "」 "(hr {size "5"} {noshade})"「タイトル :" (i "i イタリック 1") "」 ""「タイトル :" (em "em イタリック 2") "」 " (br)"「タイトル :" (b (i "b+iボールド + イタリック ")) "」 " (br)
(img {src "p32.gif"} {id "p320"})
(dl(dt "HTML"
(dd "HyperText Markup Language の略で・・・ "))(dt "WWW"
(dd "World Wide Web の略で・・・ "))
))
まずは JSON で開発してもらって・・
• JSON は標準でライブラリーも用意されているようなフォーマット。導入に障壁が少ない。
• 配列にデータを用意し、関数で JSON に変換するだけ。お手軽に扱える。
• S 式で出力してください。 ( ゚ Д ゚ ) ハァ ?• JSON で出力してください。 (・∀・)イイ !!
まずは JSON で開発してもらって・・
後で S 式に置き換える。• JSON で完璧に動くものを作り、 JSONへ
の変換部分を S 式に変換する関数に置き換える。
• JSON で出力できるように作ってくれ、とお願いするだけでいつの間にか S 式を出力してくれるサーバーの出来上がり。
JSON で作っていたつもりが・・・
{ "Person" : { "name" : "Yasuo Higano", "nickname" : "mitamex", "interest" : [ { "title" : "SETI@home", "url" :
"http://setiathome.ssl.berkeley.edu/" }, { "title" : "Flickr", "url" : "http://www.flickr.com/" } ] }}
JSON で作っていたつもりが・・・いつの間にか S 式でやり取りされて
る!(
{ Person ( { name "Yasuo Higano" }{ interest "mitamex" }{ interest ( ( { title "SETI@home" }
{ url "http://setiathome.ssl.berkeley.edu/" }
)( { title "Flickr" }
{ url "http://www.flickr.com/" })
)}
)}
)
まとめ
• それとは気がつかないうちに、データをS 式でやりとりさせましょう
どこでも REPL
携帯電話上でデバッグ可能
• エミュレーターで動いているのに実機では動かないことがある!
• 問題が起きている実機で直接デバッグしたら原因がすぐわかるのに!
• デザイナーから1ドット右、1ドット上!とか言われてもその場で修正。
携帯電話にリモートデバッグ
• Doja はソケットが使えないので WebServer にポーリングで HttpGet してコマンドを受け取る。
• PC側は WebServer と S 式メッセージでやりとり。
• 携帯 <-> WebServer <-> PC
• ちょっともっさりしているけど、実用上問題なし。
サーバーサイド L4u
携帯でもサーバーでも同じ言語が動く
• 携帯で重い処理はサーバーに移す• 通信なしでレスポンスを早くしたければ
携帯に移す
smarty もどき• 正規表現を使用せず、約 60 行のソースで、次のような L4u のコードが混在した html を処
理可能
<html><head><title><%= title %></title></head><body>test of foreach<br><% foreach data (x) do %>
データは <%= x %><br><% end %>
test of repeat<br><% repeat 10 (x) do %>
カウント <%= x %>番目でーす <br><% end %>
</body></html>
変換された l4u のコードを eval するだけ
(println "<html><head><title>")(println title )(println "</title></head><body>test of foreach<br>")foreach data (x) do
(println " データは ")(println x )(println "<br>")
end (println "test of repeat<br>")repeat 10 (x) do
(println " カウント ")(println x )(println "番目でーす <br>")
end (println "</body></html>")
ホスト言語との連携
ホスト言語との連携を前提にする
• L4u は自己主張しません。• L4u で全部書けとか言いません。• L4u が苦手な部分はホスト言語に任せてし
まえば良い。• 実行するだけでプロファイリング完了。–これで遅い部分をホスト言語に置き換えればよ
い。• 極限までスピードに最適化した L4u のプロ
グラムはメッセージキューとタスクマネージャ以外すべてホスト言語に置き換わる。
reflection が使えれば
var obj = (create "System.Windows.Forms.MessageBox)
(obj.Show "Hello World!")
CLOS のように (Show obj “Hello World!”) と書いてもよい。
Objective-C のように [obj Show “Hello World”]とも書けます。
Integer クラスの実装 その1public class L4uObjInteger extends L4uObj implements IL4uEmbdFunc {
public int _val;
static L4uEnvironment _exe;
public final L4uEnvironment GetExecUnit() {return _exe;
}
// 実行ユニットが対象としているクラスの実装クラスpublic final Object GetClassObj() {
return _exe._funenv._cls;}
Integer クラスの実装 その2public static final void InitImplementation() {
L4uObjInteger obj = new L4uObjInteger(0);_exe = InitEmbd("L4uObjInteger",obj);
RegFunc(_exe, new L4uVMFuncEmbd("inc!", 1) {public L4uObj exec(L4uVMExecFun exe, int
num_params) {_Stack s = exe._vmthread._stack;L4uObjInteger obj = ((L4uObjInteger) s.at(0));obj._val++;return obj;
}});
• 無名インナークラスを使って楽チンに実装
delegate/cc
• call/cc はオーバーヘッドが大きい• delegate/cc は呼び出した時点で処理が停止し、明示的に“継続”されるまで実行されない。
• スタックをそのままにしておくだけ。スタックのコピーを作る必要がない。 ==オーバーヘッドがない。
• ホスト言語に後のことはまかせた!
delegate/cc の例doja で IME を使って文字を入力
; L4uvar result = (delegate/cc (lambda (cont)
(IMEGetText cont " はろー ")))(println result)
// Javastatic L4uContinuation _ime_cont;
RegFunc( new L4uVMFuncEmbd("IMEGetText",2){public Object Exec(L4uVMExecFun exe,int num_params){
_ime_cont = (L4uContinuation )exe._vmthread._stack.at(0) );imeOn(""+ exe._vmthread._stack.at(1),
TextBox.DISPLAY_ANY, TextBox.ALPHA)}
} )void processIMEEvent(int type, String text) {
_ime_cont.do_continue(text);}
プリプロセッサ、マクロ
L4u のソースコード処理の流れ
• プリプロセッサ処理↓
• シンボル化↓
• L4u によるシンボル変換↓
• Lisp 的なマクロ適用
C 言語みたいなプリプロセッサ
• 同じシリーズの携帯電話でもコードを分ける必要あり。
• 言語仕様としてプリプロセッサをサポートしないと、 Java に外部プログラムでプリプロセッサを適用するような混乱が起きてしまう!
• 美しくない? うるせー! 必要なんだ!
プリプロセッサの例
• プリプロセッサは独自の環境で動く L4u<#define DEV 'N907 #><#define VER 105 #><#if (and (eq? DEV 'N907) (> VER 100))) #>
S 式<#else#>
S 式<#end#>
もはや S 式じゃない?マクロより凶悪な S 式いじり
• マクロよりもプログラマブルな L4u で記述されたマクロ(のようなもの)• シンボル化されたソースを直接いじくって、かっこの外に飛び出せ!; かっこの外にある class というシンボルの処理switch symcase 'class
var a = (read)var b = (read)var c = (readListUntil 'end)
var lb = (new ListBuilder)(lb.add! 'class)(lb.add! a)(lb.add! b)(lb.addList! c)(lb.toList)
もはや S 式じゃない?マクロより凶悪な S 式いじり
class クラス名 (継承するクラス )定義
end↓(class クラス名 継承するクラス定義 )
に変換される。
Dylan みたいだけどS 式を捨てたわけじゃない
• どんな構文でも、最終的に S 式に変換される。
• だから糖衣構文なしで直接 S 式で書いてもよい。
• マクロは、変換後の S 式に対して適用される。
L4u は超並列指向
L4u の超並列指向
• Erlang と同じものを目指している– もともと Erlang を携帯で動かしたかった。
• Erlang と同じメッセージパッシング。– 同一プロセスはもちろん、他プロセス、ネット
ワーク越しの PC とも通信可能。• 共有メモリは一切使えない。メモリーの排他機能は持たない。
• Erlang と同じく、言語が処理するライトウェイトスレッドと、 OS がサポートするスレッドのハイブリッド構成。
メッセージパッシングでの並列処理を前提で考えるといろいろ楽ち
ん• GPS 、 IO 、描画、 UI 、入力・・・ 非同
期だらけ。• メッセージパッシングはクライアント /
サーバーのような形式。チーム開発にもぴったり。
簡単な並列化フィボナッチ数 オリジナル
defun fib(n)if(<= n 2) then
1else
(+ (fib (- n 1)) (fib (- n 2)))end
end
簡単な並列化フィボナッチ数 計算部分を並列
化defun fib(n)
if(<= n 2) then1
elsevar {a b} = (parallel
(fib (- n 1))(fib (- n 2))
)(+ a b)
endend
他のプロセスにメッセージを送る
defun remoteRPL(name url port)var target = (new RemotePID name url port)loop
(print "remoteL4u>")var str = (readln)(target.send {‘print str})(println "")
endend
(remoteRPL “mymsgloop” “localhost” 8080)
他のプロセスからのメッセージを受ける
defun messageLoop(s)(println s)loop
switch (receive)match {'print msg}
(println “ メッセージは” msg)match msg
(println “ なんかわかんないけど” msg)end
endend
home>l4u port:8080l4u>(spawn “mymsgloop”
messageLoop “hello”)
L4u は人にやさしい Lisp を目指した構文
心理学とか脳生理学にに凝っている男が作った Lisp
例えば…
• 脳のローレベル部分は論理で動いていない–あなたのことは嫌いではない–あなたのことが好きだ
• 論理的には同じことを言っているが、• 二重否定では“嫌い”という意味を右脳がダイレクトに感
じ、それを左脳が論理的に考えて好きだと理解する。• “好きだ”は、右脳も左脳も同じ意味を考えなくても直観
的に理解できる。
人にやさしい構文とは
• Lisp は人間にやさしくない–コンピューターにやさしい言語。
• Algol 系言語から入った人は、カッコだらけで生理的な嫌悪感を抱く!
• 特殊形式も関数も同じ書き方だから知識なしには理解できない。
• Lisp の特徴を生かしつつ人にやさしい構文にしたい。
右脳で直感的に理解できる構文例えば if
• (if a b c) って Lisp の知識がない人には何をやるのかさっぱりわからん。
• コンピューターにとっては冗長で無駄な情報。でも、人間には理解を助けるためのアンカーが必要。
if a thenb
elsec
end
then,else はコンピューターにとっては邪魔な記号だが、人間はこれを見たらどの部分が何を意味しているかすぐわかる。
これだったらプログラミングをかじった人間のほとんどが理解できるはず。
やりたいことを明示するcontinue,break,return を持つ
• ありがちな会話初心者「 scheme で break ってどうやるんです
か?」プロ「継続でできるよ」初心者「で、どうやってやるんじゃい!」
• 実際にやりたいことは継続ではないcontinue したい。 break したい。 return したいだけ
なのに、なんで継続使わなくちゃいけないの?
冗長でも、やりたいことを明示するをもつ
• loop,while などのブロック構造–そりゃなんでも再帰で書けるけどさ・・
• 本当にやりたいのは再帰じゃなくてループだろ?
• 関数は defun(def) で定義。変数は var で定義。–互換性のために scheme みたいに define も OK
loop の例let c = 0 in
loopwhen(>= c 10) do
break (* c 2)end
set! c = (+ c 1)end
end
repeat の例
repeat 10 from 5 (x) do(println x)when (== x 10) do
break 'okend
end
見た目は Algol 系でも最終的には S 式で処理される
repeat 10 from 0 (x) do(println x)
end↓( let ( ( x 0 ) )
( while-f ( < x ( + 10 0 ) )( println x ) ( objpath:x.inc! )
))
• マクロは変換後の S 式に対して適用される。
かっこを減らせ! その 1
• ひとつ前の処理の戻り値を指す it を追加。
(print (fizzbuzz (seq 1 100)))↓
(seq 1 100) (fizzbuzz it)(print it)
かっこを減らせ! その 2
• -> 演算子を追加。–戻り値を次の関数の第一引数に放り込める。–処理の流れが左から右になる。–処理のパイプラインを簡単に作れる。
(print (fizzbuzz (seq 1 100)))↓
(seq 1 100) -> (fizzbuzz) -> (print)
第一引数を特別扱い
• L4u ではすべてがオブジェクト• (+ 1 2 3) は整数の加算が呼ばれるべき• (+ “abc” “def”) は文字列の結合が呼ばれるべき
• どのクラスのメソッドを呼び出すか識別するために、第一引数を使う
• クラスごとにメソッドを定義するだけで、第一引数で識別する多重ディスパッチになる
高階関数の引数の順番が逆• map はリストを貰って、リストを返す。だ
からリストが一番重要な引数のはず。
scheme: (map function list)↓
l4u: (map list function)
この引数の順番だから、 (map list function) -> (map list function) という風に処理をパイプライン化しやすい。
関数呼び出しの優先順位
• 変数スコープにある関数–多重ディスパッチを登録しておけば、 CLOS
と同じように処理される。
• 第一引数のクラスに属する関数–多重ディスパッチで処理されなければ、 Java,Ruby のようにクラスのメソッドで処理される。
非 CLOS なオブジェクトシステム
• Java・ Ruby のようなクラスclass Animal()
def 鳴く ()end
end
class 犬 (Animal)def 鳴く ()
(println “ わんわんお!” )enddef お手 ()
(println “ あいよ“ )end
end
var dog = (犬 .new) ; var dog = (new 犬 ) どっちでもいいよ(dog.鳴く ) ; (鳴く dog) どっちでもいいよ[dog お手 ] ; Objective-C のメッセージ式と同じ
非 CLOS なオブジェクトシステム
class 住所録 ()var 名前 = “”var 住所 = “”var 電話番号 = “”
def construct(名前 住所 電話番号 )set! self.名前 = 名前set! self.住所 = 住所set! self. 電話番号 = 電話番号
end
def dump()(println 名前 “ : “ 住所 “ : “ 電話番号 )
endend
var obj = (new 住所録 “ mitamex” “千葉県” “ 090-xxxx-xxxx”)
SLOT じゃなければ、コピペで抜き出して簡単にテストできるよ
var 名前 = “”var 住所 = “”var 電話番号 = “”
def construct(名前 住所 電話番号 )set! self.名前 = 名前set! self.住所 = 住所set! self. 電話番号 = 電話番号
end
def dump()(println 名前 “ : “ 住所 “ : “ 電話番号 )
end
L4u>set! 名前 = “阿部高和”L4u>(dump)
なぜ CLOS があるのにJava っぽいクラスを使うか
• ホスト言語が Java だから。– L4u の遅い部分はホスト言語で書きなおすの
で、同じ様な形が好ましい。–ホスト言語のオブジェクトを簡単に呼び出せ
るので、ホスト言語と同じ形のオブジェクトにしたかった。
var msgbox = (create "System.windows.forms.MessageBox”)
(msgbox.show "Hello world")
まとめ
• 楽するために L4u を使おう!• どこでも REPL• プロトタイプ作成ー>ボトルネックをホス
ト言語で最適化• 超並列型• Algol 系プログラマでもとっつきやすい構文• 携帯電話でも動く小さなコア• 携帯電話からサーバーまでカバーするス
ケーラビリティ
ありがとうございました
開発者募集中!2009 年オープンソースで登場予
定