flet & labels in Scheme

ループ処理を書く時などに、全く同じ関数呼び出しを複数回書くことがあると思うんですが、それがどうも面倒*1なので、ローカル関数を手軽に作る構文を書いてみました。

マクロ:

(require "mlfun.ss")

(define-syntax define-flet
  (syntax-rules ()
    ((define-flet flet let)
     (define-syntax flet
       (syntax-rules ()
         ((flet ((name params . expr) (... ...)) . body)
          (let ((name (fn params . expr)) (... ...)) . body)))))))

(define-flet flet let)
(define-flet flet* let*)
(define-flet fletrec letrec)            ;`labels' in CL

(関数は lambda でも良いんですが、自動カリー化とかパターンマッチを組み込んである自作の fn を使っています)

例:

(fun (read-header in)
  (fletrec ((line () (read-line in 'any))
            (loop (l r)
              (if (string=? l "")
                  (reverse r)
                  (loop (line) (cons l r)))))
    (loop (line) '())))


追記:

let での変数のバインディングと flet のそれとではアリティーが違うことに着目して、変数と関数を両方バインドできるようにしてみました。

(define-syntax define-flet
  (syntax-rules ()
    ((define-flet flet let)
     (define-syntax (flet stx)
       (syntax-case stx ()
         ((flet loop . rest)
          (and (identifier? #'loop)
               (eq? (syntax->datum #'let) 'let))
          (syntax/loc stx
            (let loop . rest)))
         ((flet (bind (... ...)) . body)
          (quasisyntax/loc stx
            (let #,(map (lambda (x)
                          (syntax-case x ()
                            ((v e) x)
                            ((v p . e)
                             (syntax/loc x
                               (v (fn p . e))))))
                        (syntax->list #'(bind (... ...))))
              . body))))))))

モジュールのエクスポート時に flet を let にリネームすれば、let と flet の構文を統合することが出来ますね。


追記2:

let で関数束縛の構文を使った際に emacs でラムダっぽくインデントするようにしてみました。

(defadvice scheme-indent-function (after flet-hack activate)
  (unless ad-return-value
    (setq ad-return-value
          (scheme-indent-flet (ad-get-arg 0) (ad-get-arg 1)))))

(defun scheme-indent-flet (indent-point state)
  (goto-char (elt state 2))
  (when (and (eq (char-after) ?\()                 ;start of params
             (< (progn (forward-sexp 1) (point))   ;end of params
                indent-point))
    (condition-case ()
        (progn
          (backward-up-list 3)
          (forward-char 1)
          (when (and (looking-at "\\=let")
                     (< indent-point
                        ;; end of binding
			(progn (forward-sexp 2) (point))))
            (goto-char (elt state 1))
            (+ (current-column) lisp-body-indent)))
      (error nil))))

実は cl-indent.el で flet をどうやってインデントしているか調べようとしたんですが、難し過ぎて諦めかけました。


ライブラリ:
mlfun.ss

*1:インターナルdefineを使う方も多いと思うんですが、それだとdefineを何度も書くのが面倒、letだとlambdaを書くのが面倒、ということです