Gauche:数式の中置記法

Gauche:数式の中置記法

object-applyによる数式の中置記法

Lisp (Scheme)に対する不満の声としてよく聞かれるのが、前置記法の奇妙さだ。前置記法に慣れてみると、手続きが常にcarに来ることに不自然を感じなくなるのだが、それでも数式の記述はやはり普通の中置記法のほうがわかりやすいと思うことがよくある。これはなんとかならないだろうか?

そのように漠然と考えていたところ、Gaucheの「適用可能なオブジェクト」の機能を利用すれば、数式の中置記法をサポートできることに気がついた。Gaucheではobject-applyを適切に定義することにより、carに数がきたときに任意のメソッドを起動することができる。そのメソッドで中置記法を解釈して適切な結果を返してやればよいのだ。もちろん実用的なSchemeプログラムでこのようなものを薦めるつもりはないのだが、お遊びとしては面白いと思う。Rui (2004/12/30 09:53:14 PST)

前提

ここでは

(5 * 2 + 10)

のような式を直接Gaucheで評価可能にすることを考える。2項演算子以外はとりあえず考慮しないが、演算子の優先順位は正しく解釈することにする。

まずは数を適用しようとしたときに起動されるメソッドを定義するのだから、書き出しは次のようになる。

(define-method object-apply ((n <number>) . rest)

これで数がcarにきたときにこのメソッドが呼び出されることになる。このメソッドで演算結果を返してやればよい。

いまから定義しようとしているのはマクロではなく手続きなので、引数はすべて評価されてからメソッドに渡されることになる。従ってメソッドが受け取るのは'*や'+ではなく、#<subr *>や#<subr +>であり、それらは直接適用することができる。これによりいくらか手抜きすることができる。

演算子の優先順位を変えるためのカッコについては、いまから定義しようとするメソッド自身がサポートする必要はない。Gauche自身に任せておけばよい。また、引数は評価されて渡されるわけだから、前置記法と中置記法を混ぜることさえできる。

コード

実際のコードを見てみよう。

(use srfi-1)

(define % modulo)
(define ^ expt)

(define-method object-apply ((n <number>) . rest)
  (define (operator<=? op1 op2)
    (define precedence `((,+ ,-) (,* ,/ ,%) (,^)))
    (define (weight op)
      (list-index (cut memq op <>)
                  precedence))
    (<= (weight op1) (weight op2)))
  (define (calc ops vals rest)
    (if (null? rest)
        (let loop ((ops ops) (vals vals))
          (if (null? ops)
              (car vals)
              (loop (cdr ops)
                    (cons ((car ops) (cadr vals) (car vals))
                          (cddr vals)))))
        (let ((op (car rest))
              (val (cadr rest))
              (rest (cddr rest)))
          (cond ((or (null? rest)
                     (operator<=? (car rest) op))
                 (calc ops
                       (cons (op (car vals) val) (cdr vals))
                       rest))
                (else
                 (calc (cons op ops)
                       (cons val vals)
                       rest))))))
  (calc () (list n) rest))

使用例は以下のとおり。数式全体をカッコでくくらなくてはいけないこと以外は十分に直感的だと思う。

(5 * 2 * 2)        ; => 20
(5 * (1 + 1) * 2)  ; => 20
(5 * (+ 1 1) * 2)  ; => 20

(define n 10)
(n + 100)          ; => 110
((n + 100) * 3)    ; => 330

(n + 100 * 3)      ; => 310

欠点

一方、欠点としては以下のものをあげることができるだろう。

モジュール

h(2014/08/31 13:52:12 UTC):上記内容を元にGaucheのモジュールにしてみました。
https://github.com/Hamayama/numinfix
一部改造、デバッグしました。


Tag: object-apply

More ...