match と cut

長いこと「match なんて酸っぱい葡萄だ(=match なんかなくてもプログラムは書ける)」と思っていたが、やっと分かったかもしれない。

;; S式を変換する例。(ret) は "ret void", (ret '(i32 8)) は "ret i32 8", 
;; (ret '(i32 8) '(i1 1)) は "ret i32 8, i 1" にしたい。
;; (ret) => ret void
;; (ret (type value)) => ret <type> <value>
;; (ret (type value) (type value)) => ret <type> <value>, <type> <value>

(define (ret-inst . args)
  (match args
    (() "ret void")
    (((type value) ...)
     (format #f "ret ~a"
	     (string-join
	      (map (lambda (ty va)
		     (format #f "~a ~a" ty va)) type value) ", ")))
    (_
     (errorf "mulformed: ~a" args))))

ほとんど仕様通りにプログラムにできる。どうりで compile.scm で多用されているわけだ。式をひたすら変形していくのに便利すぎ。ただし、(当然ながら)簡単なパターンマッチでは、簡単な判定しかできない(例えば上の例では (ret '(i32 ()) のようなものを許してしまう)。複雑なパターンマッチ用には ? predicate というのが使えて、これまた多用されている。少し改良したのが以下。

(define (ret-inst . args)
  (match args
    (() "ret void") ;; (ret) にマッチ
    ((((? symbol? type) (? (lambda (x) (and (not (pair? x))
					    (or (symbol? x)
						(number? x)))) value)) ...) ;; (ret '(i32 1)) とか
     (format #f "ret ~a"
	     (string-join
	      (map (cut format #f "~a ~a" <> <>) type value) ", ")))
    (_ ;; それ以外はエラー
     (errorf "mulformed: ~a" args))))

ついでにこれも自分にとっては「酸っぱい葡萄」だった cut を使った。

  • cut は「手続きを簡潔に書ける便利なマクロ」なので lambda に近いレベルの存在。
  • 良い点は簡潔に書けること。
  • 悪い点は良い点の裏返しで(簡潔すぎて)引数に名前が付かないこと、だと思う。ただし実例を見ると1個の<>がほとんどで、長所のほうが大きい。上は下手な例か。また lambda よりカッコが少なすぎて、かえって見難い気がする。CL の#' も無いから、カッコの中にべたーと要素が並んでいるのはなんとなく気持ち悪い。慣れか。