読者です 読者をやめる 読者になる 読者になる

省略可能引数

プログラミング言語 Scheme は仕様は小さいが、 SRFI という形で便利な機能が提案されている。 特に SRFI-1 は典型的なリスト操作を多く含むのでよく利用され、また、典型的であるが故にこれを真似るのは練習の題材としても恰好のものである。

SRFI-1 にある等比数列を作る手続きである iota を真似て独自に実装してみようとする記事を見た。

整数のリストを作る | blog.PanicBlanket.com

ここでは数列はともかくとして、省略可能な引数の扱いに手間取っているようだ。 引数もまたリストであるので定型的に書けるが、よくある形ならばそれはひとつの語彙に押込めてしまうのが作法というものだろう。 このような場合のために R6RS/R7RS では case-lambda という構文が用意されている。 case-lambda を用いて iota を書くならばこのような要領になる。

(define iota
  (case-lambda
   ((count)       (iota count 0 1))
   ((count start) (iota count start 1))
   ((count start step)
    ;; 省略
    )))

しかし、私は Gauche や Chicken などにある let-optionals* の方が好きだ。 上記のコードを let-optionals* を使って書き替えた場合はこうなる。

(define (iota count . restarg)
  (let-optionals* restarg
      ((start 0)
       (step 1))
    ;; 省略
    ))

let-optionals*Scheme の仕様に含まれるものではなく、一部の処理系が独自に提供しているものだが、マクロを使えば以下のように簡単に定義できる。

(define-syntax let-optionals*
  (syntax-rules ()
    ((_ args ((var default) . rest) body* ... body)
     (let* ((temp args)
            (var (if (null? temp) default (car temp))))
       (let-optionals* (if (null? temp) '() (cdr temp)) rest body* ... body)))
    ((_ args () body* ... body)
     (if (null? args)
         (begin body* ... body)
         (error "Too many arguments:" args)))
    ((_ args (var . rest) body* ... body)
     (let-optionals* args ((var #f) . rest) body* ... body))
    ((_ args rest-var body* ... body)
     (let ((rest-var args)) body* ... body))))

Script-fu で省略可能な引数を扱っている記事も読んだ。

Script-fu は画像処理ソフト GIMP に組込まれている Scheme 処理系で、 TinyScheme が基礎になっている。 残念ながら syntax-rules はサポートされていないが、伝統的マクロならば使える。 Script-fu の伝統的マクロを用いて let-optionals* を書くとこうなる。

(define-macro (let-optionals* args opts . body)
  (cond ((and (pair? opts)
              (let ((opt (car opts)))
                (and (list? opt) (= (length opt) 2) opt)))
         => (lambda (opt)
              (let ((var (car opt))
                    (def (cadr opt))
                    (rest (gensym)))
                (if (symbol? var)
                    `(let* ((,rest ,args)
                            (,var (if (null? ,rest) ,def (car ,rest))))
                       (let-optionals* (if (null? ,rest) '() (cdr ,rest))
                           ,(cdr opts)
                         . ,body))
                    (error "Malformed syntax")))))
        ((and (pair? opts)
              (let ((opt (car opts)))
                (and (symbol? opt) opt)))
         => (lambda (opt)
              `(let-optionals* ,args ((,opt #f) ,@(cdr opts)) . ,body)))
        ((symbol? opts)
         `(let ((,opts ,args)) . ,body))
        ((null? opts)
         `(if (null? ,args)
              (begin . ,body)
              (error "Too many argments")))
        (else (error "Malformed syntax"))))

この定義はもちろん TinyScheme でも使える。

私は画像を扱うときは PIXIA を好んで使っていたのだけれど、 OS を Windows7 から Windows10 にアップグレードした際にどういうわけかクラッシュしやすくなってしまったので GIMP 2 を導入した。 折角なので Script-fu を使った自動化、またその下地になるユーティリティ的な手続きやマクロの定義を蓄積していきたいと思う。

Document ID: c80009ca4afc568f85c46f6e89428b6b