もっとマクロのこと

以下にもっと複雑なマクロについて紹介します。
すべてcommon.scmで定義済みのマクロのサンプルとなります。
まあ「こういうこともできる」というマクロ拡張の参考にしてください。

可変長引数のリスト走査

(require "common.scm")
;; 複数のlistの各要素を組にしたlistを返す、いわゆるzip関数

;; 2つのリストのそれぞれの要素をtuple化したlistを返す
(list-map-n
	(fun (v1 v2) (tuple v1 v2))
	'(1 2 3) '(4 5 6)
	)

;; 3つのリストのそれぞれの要素をtuple化したlistを返す
(list-map-n
	(fun (v1 v2 v3) (tuple v1 v2 v3))
	'(1 2 3) '(4 5 6) '(7 8 9)
	)
標準ライブラリで定義されているList.mapやList.map2の可変長引数版です。
common.scmでは逆順にする「list-rev-map-n」やlistを返さない「list-iter-n」も定義済みです。


anaphoricマクロ

define-macroで定義されたマクロの生成する式は割り込ませた変数の衝突について
プログラマ自身が注意を払う必要があります。
これはdefine-macroの持つ危険性ではありますが、同時に強力な性質でもあります。
以下のマクロは意図的にマクロの用意した変数名にパラメータを束縛して割り込ませています。
こういった任意の変数名での割り込みは実質的に予約語の拡張のような用途での利用が可能です。
マクロサンプルanaphoric-argsは「第一引数に関数を受け取る関数(List.mapやList.iter等)」を受け取ります。
(require "common.scm")

(anaphoric-args anaph-iter List.iter2)

(anaph-iter
	(
		(print_string "sum: ")
		(print_int (+ _1 _2))
		(print_newline)
		)
	'(1 2 3)
	'(4 5 6)
	)
これは「List.iter2」に渡す関数をマクロで生成しています。
そしてその関数は「_1」や「_2」といった連番の引数を自動生成します。
(C++経験者ならboost::lambdaを連想するかもしれません)

前述の「anaphoric-args」「list-map-n」は組み合わせて使うことも可能です。
(require "common.scm")
(anaphoric-args anaph-map list-map-n)
(define ls
	(anaph-map
		(
			(+ _1 _2 _3)
			)
		'(1 2 3)
		'(4 5 6)
		'(7 8 9)
		))
この例では3つのlistのそれぞれn番目の要素を足し合わせたlistを返します。
本来変数名には用途に応じた名前をつけるのが望ましいですが、
用途が自明でかつ横着に書きたい場合には便利かもしれません。


可変長引数の関数定義

厳密には可変長引数の関数ではなく、可変長引数のマクロを定義します。
つまりdefun-varargsは、マクロを定義するマクロということになります。
仮引数の記法はSchemeの可変長引数を参考にしています。
;; 2つ以上の引数を受け取ります(2つは必須)
;; 3つ目以降の引数はlistとして受け取り、
;; 以下の例では変数optで参照可能です。
(defun-varargs vfunc1 (v1 v2 . opt)
	(print_int v1)
	(print_newline)
	(print_int v2)
	(print_newline)
	(List.iter
		(fun (x)
			(print_stringa "opt: ")
			(print_int x)
			(print_newline)
			)
		opt)
	)

(vfunc1 1 2 3 4 5)

;; 任意の数の引数をlistとして受け取ります。
;; 以下の例では変数argsで参照可能です。
(defun-varargs vfunc2 args
	(List.iter
		(fun (x)
			(print_stringa "args: ")
			(print_int x)
			(print_newline)
			)
		args)
	)

(vfunc2 1 2 3 4 5)


リストの内包表記(組み込みリスト用)

Schemeのsrfi-42を参考にしています。
list-ecマクロの中に式を並べることでlistの生成を行います。 これらの式を「(list-ec (for x 1 10) (for y 1 10) (if (< x y)) (* x y))」のように列挙すると
最後の式を評価した結果がlist-ecの返すlistの要素となります。
(require "common.scm")
;; 1から20までの整数うち、2の倍数を2乗した数値によるリストを生成する
(define ls (list-ec (for x 1 20) (if (= (mod x 2) 0)) (* x x)))

(List.iter
	(fun (x)
		(print_int x)
		(print_newline)
		)
	ls
	)

;; 既存のリストから各要素を2倍した新たなリストを生成する
(define ls2 (list-ec (map x ls) (* x 2)))

(List.iter
	(fun (x)
		(print_int x)
		(print_newline)
		)
	ls2
	)


関数の合成

composeマクロは複数の関数を合成して新たな関数を返します。
以下の「((compose decl pow incl pow) 3)」は
「(decl (pow (incl (pow 3))))」と等価になります。
後ろのほうの関数から順に適用されていることに注意してください。
(require "common.scm")

(define incl (fun (x) (+ x 1)))
(define decl (fun (x) (- x 1)))
(define pow (fun (x) (* x x)))
(print_int ((compose decl pow incl pow) 3))


契約プログラミング

関数の引数、戻り値のチェックを体系的に行うための機能です。
EiffelやD言語の持つ機能ですが、マクロサンプルのcontract-funは
D言語を参考に実装してあります。
(require "common.scm")

(define cont_f
	(contract-fun (x y)
			(
				(in (< x 4))
				(out result (< result 10))
				)
			(+ x y)
		))

(try
	(match
			(Contract_assert_failure (Contract_in_assert expr)
				(print_string (^ "arguments error\n")))
			(Contract_assert_failure (Contract_out_assert expr)
				(print_string (^ "return value error\n")))
			(_ ()
				(print_string "unknown error\n"))
			)

	(print_int (cont_f 3 8))
	(print_newline)
	)
引数の条件、戻り値の条件のうち、どちらかを満たさない場合、
例外Contract_assert_failureが投げられます。
上記の例では「第一引数が4未満」「戻り値が10未満」という条件が課されています。
条件はどちらかが不要であれば「in」か「out」のどちらか片方だけでも問題ありません。


Haskell風の関数定義

(require "common.scm")

(defun-matched-clauses
	(fact (1) 1)
	(fact (x) (* x (fact (- x 1))))
	
	(sum ('()) 0)
	(sum ((:: hd tl)) (+ hd (sum tl)))
	
	(foldr (f v '()) v)
	(foldr (f v (:: x xs)) (f x (foldr f v xs)))

	)


(print_int (fact 10))
(print_newline)

(print_int (sum '(1 2 3 4 5)))
(print_newline)

(print_int (foldr (fun (x y) (+ x y)) 0 '(1 2 3 4 5)))
(print_newline)
Haskellには仮引数にリテラルを記述した同名の関数定義を列挙することで
実引数が仮引数の値にマッチした物を呼び出す機能があります。
これはその機能をマクロで再現してみたものになります。


遅延リスト

遅延リストは組み込みのデータ型では無いため「common.scm」には含まれません。
lazy_list_sample.scm (サンプルソースコード)
lazy-list.scm (マクロ定義)
lazyList.ml (遅延リストライブラリ)
gosh s_exp_ocaml.scm lazy_list_sample.scm > result.ml
ocamlc lazyList.ml result.ml
遅延リストライブラリ「lazyList.ml」に関してはこちらのサイト(ブログ)のコードを使わせていただきました。


より実験的なマクロ機構

以下のマクロは、より実験的なマクロ機構です。
どこが実験的かというと、私が元々の仕様を勘違いして実装している可能性があります。


syntax-case
現時点でのSchemeの最新仕様であるR6RSで規定されたマクロ機構です。
define-macroの柔軟性と、syntax-rulesの簡潔さと安全性を両立したマクロ機構です。
準備中


Explicit Renaming
Schemeの標準仕様では規定されていないマクロ機構です。
使い勝手はdefine-macroに近く、define-macroのgensymとは別のやり方で変数名の衝突を回避します。
準備中


以下に各種マクロ機構の変数衝突回避の違いのわかる例を列挙しておきます。
どれも2つの参照型変数の値を入れ替えるものですが、一時変数「tmp」の扱いの違いを参考にしてください。
;; syntax-rulesの例
(define-syntax swap-sr
   (syntax-rules ()
      ((_ a b)
         (let ((tmp (! a)))
            (:= a (! b))
            (:= b tmp)
            )
         ))
   )

;; syntax-caseの例
(define-syntax swap-sc
   (lambda (form)
      (syntax-case form ()
         ((_ a b)
            (syntax
               (let ((tmp (! a)))
                  (:= a (! b))
                  (:= b tmp)
                  ))
            ))
         )
   )

;; define-macroの例
(define-macro swap-dm (lambda (a b)
      (let (
         (tmp (gensym))
         )
         `(let (
            (,tmp (! ,a))
            )
            (:= ,a (! ,b))
            (:= ,b ,tmp)
            )
         )))

;; Explicit Renamingの例
(define-syntax swap-er (er-macro-transformer
   (lambda (form rename compare)
      (let (
         (a (cadr form))
         (b (caddr form)))
         `(let (
            (,(rename 'tmp) (! ,a))
            )
            (:= ,a (! ,b))
            (:= ,b ,(rename 'tmp))
            )
         ))))
以下のように呼び出すと、与えられ2つの変数の値を入れ替えるような式に展開されます
(let
	((x (ref 5)) (y (ref 3)))
	(swap-sr x y)
	;; (swap-sc x y)
	;; (swap-dm x y)
	;; (swap-er x y)
	)