memo

ちょっとしたメモ書き(個別表示)

S式OCaml(Lisp風OCaml)トランスレータ3 : (2011/12/07)
とりあえずこのネタはこれで一旦終わり
まだ書ききれてない話もあるのでブツ切りの日記じゃなくて
いつか別の形でまとめるかも
(open Test_mod)
モジュールのopenは上記のように行う
これで現在の名前空間から該当モジュールのシンボルが直接参照可能になる
モジュール名の修飾が不要になるわけだが、便利な反面名前空間の汚染のデメリットもある
解決策として現時点での最新版であるOCaml 3.12からはlet openという範囲を限定したものが追加された
以下が他モジュールのシンボルの参照方法となる
(クラスからインスタンスを生成し、メンバ関数を呼び出す例)

;; openしない
(let
((obj (new Test_mod.test_class "ins_args")))
(call obj m_func)
)

;; 従来のopen
(open Test_mod)
(let ((obj (new test_class "ins_args")))
(call obj m_func)
)

;; OCaml 3.12以降用のlet openに展開される
;; letopen
(letopen Test_mod
(let ((obj (new test_class "ins_args")))
(call obj m_func)
)
)

メンバ関数の呼び出しは2種類の記法がある
上記の例ではcallで呼び出しているが以下の2つは等価となる
(call obj m_func argv)
(obj#m_func argv)

以下がサンプルとビルド方法
s_exp_ocaml.scm
common.scm
mod_sample.scm
test_mod.ml
クラス定義の方法は特に用意してないのでその部分は普通にOCamlで
別モジュールとして書いてそれをリンクして参照するようにする
(サンプルではtest_mod.mlというファイル)

gosh s_exp_ocaml.scm mod_sample.scm > result.ml

あとは適当にバイトコンパイルなら
ocamlc test_mod.ml result.ml
とか、ネイティブコンパイルなら
ocamlopt test_mod.ml result.ml
みたいな感じで実行ファイルを生成

例外処理は以下のような感じとなる

(define-exception FooError)
(define-exception BarError)

(try
(lambda
(e)
(match e
(FooError (print_string "FooError!!\n"))
(BarError (print_string "BarError!!\n"))
(_ (print_string "Error!!\n"))
)
)

(begin
(print_string "Begin\n")
(if #t (raise BarError))
(print_string "End\n")
)
)

define-exceptionで例外を定義して、(raise 例外名)で例外を発生させる
もしtry式の中であれば、例外処理用の関数が例外を第一引数として呼ばれる
try式の形式は
(try error-handler exp ...)
となり、error-handlerが前述の例外処理用の関数となる

マクロはSchemeのdefine-syntax〜syntax-rulesやらCommon Lispのdefmacro(define-macro)やらが利用可能
Lispマクロの強力さ簡潔さはS式に強く依存しており、文法を捨てることでしか手に入れられないのではないかと思う
そしてS式化することの利点はつまるところ「Lispマクロが使える」というただ一点に集約される
(したがって今までのS式OCamlトランスレータ関連の一連の日記はただの序文である、これからが本題)

マクロはS式からS式を生成する仕組みであり、OCamlコードへの変換の前処理として実行される
それぞれ以下のようなS式に展開され最終的にはOCamlのコードへと変換される。

Schemeマクロの例
(define-syntax
cond
(syntax-rules ()
((_ (a1 a2 ...))
(if a1 (begin a2 ...)))
((_ (a1 a2 ...) (b1 b2 ...))
(if a1 (begin a2 ...) (if b1 (begin b2 ...))))
((_ (a1 a2 ...) b ...)
(if a1 (begin a2 ...) (cond b ...)))
))

(cond
((> x 10)
(print_string "over 10"))
((> x 5)
(print_string "over 5"))
((> x 0)
(print_string "over 0"))
(#t
(print_string "zero or negative number"))
)

上記のような多分岐を展開してネストしたif文を生成するマクロ
形としてはmatchに似ているが分岐の条件が複合的な場合などmatchでは対応できない状況で使う

Common Lispマクロの例
(define-macro compose
(lambda args
(letrec
(
(make-fargs
(lambda (ls)
(if
(null? ls)
'()
(cons (gensym) (make-fargs (cdr ls))))
))
(fargs (make-fargs args))
(make-exp
(lambda (ls)
(if
(null? ls)
'comp_args
(list (car ls) (make-exp (cdr ls))))
))
(comp_exp (make-exp fargs))
)
`((lambda ,fargs
(lambda (comp_args) ,comp_exp)
) . ,args)
)))


(define new_function (compose f1 f2 f3 f4))
上記のようなS式を以下のように展開するマクロ

(define new_function ((lambda
(
macro_gensym_symbol__1
macro_gensym_symbol__2
macro_gensym_symbol__3
macro_gensym_symbol__4)
(lambda (comp_args)
(macro_gensym_symbol__1
(macro_gensym_symbol__2
(macro_gensym_symbol__3
(macro_gensym_symbol__4 comp_args))))
)
) f1 f2 f3 f4))

関数を合成した新たな関数を返すマクロとなる
このマクロ中に登場する関数gensymは重複しない新たなシンボルを生成する関数で、上記の例のように変数束縛を引数の数に応じて自動生成しなければならない場合にも都合が良いが、本来はHygienic macroでないdefine-macroで変数束縛を作る際に重複しない変数名を
生成する際に使う関数


common.scmにはもっと複雑な例もサンプルとして書いてある
このS式OCamlのlambdaは可変長引数には対応していないが
マクロを利用すればそれも可能となる

(define-varargs (name args ...) exp ...)
一見、可変長引数の関数を定義するマクロに見えるが実際は
可変長引数のマクロを定義するマクロとなる

;; 式1
(define-varargs
f
(arg1 arg2 . opt)
(foreach (lambda (x) (print_int x) (newline)) opt)
)

;; 式2
(f 1 2 3 4 5)

上記の式1ようにこのマクロを使用することで、マクロfと暗黙の関数(名前はmacro_gensym_symbol__1のような自動生成されたもの)が定義される。
そして式2はその暗黙の関数を呼び出すようなコードに展開される
以下がマクロ展開ののち変換されるOCamlのコードとなる
(読みやすいよう調整してあるが、実際にはカッコやインデントが異なる)

let macro_gensym_symbol__1 = (fun arg1 arg2 opt ->
(foreach
(fun x ->
print_int x;
newline();
)
opt)
);;

(macro_gensym_symbol__1 1 2 ([3; 4; 5]));;

この例では第1引数、第2引数は通常の関数のように渡るが、第3引数以降はリストにまとめられて渡る
例えば以下のように最低限の引数しか与えなかった場合は第3引数には空のリストが渡ることになる
(f 10 20)
(macro_gensym_symbol__1 1 2 ([]))

Schemeの可変長引数と同様、すべての引数をリストにまとめる場合は以下のようになる
(define-varargs
f
args
(foreach (lambda (x) (print_int x) (newline)) args)
)
Schemeのlambda同様に引数argsをカッコで括らない場合は、全ての引数がlistであるargsに格納される


Schemeのマクロもそれなりに強力だけど、Common Lispのマクロに関してはそれだけで
本が一冊書けてしまうくらいのすさまじさになる。
(On Lispという本はその大部分をマクロの解説に割いているので興味があれば参照してみてください)

オマケ
OCamlには強力な型推論があるので変数宣言で必ずしも型を明示する必要はないが、場合によってはそれが意図しないエラーメッセージとなって混乱することもある
型を明示することで本来の意図を示したい場合は以下のように書くことで解決する
(変数名の次に来るのが型情報となる)

;; 1. int
(define-typed foo ((int)) 100)

;; 2. int -> int -> int
(define-typed
bar
((int) (int) (int))
(lambda (x y) (+ x y)))

;; 3. int list -> int
(define-typed
sum
((int list) (int))
(lambda
(ls)
(if
(= ls '())
0
(+ (car ls) (sum (cdr ls))))))


: back