EoPL reading (75) 2.2 An Abstraction for Inductive Data Type

軽い環境でナニなのは良いのですが、emacsskk 使えんのはイタ杉。とりあえず、'() を受け付けるようにして、parse を修正する方向で。
とりあえず expression が '() を取り扱う方向で修正盛り込み。

(define-datatype expression expression?
  (nul-exp
   (datum null?))
  (lit-exp
   (datum number?))
  (var-exp
   (id symbol?))
  (if-exp
   (test-exp expression?)
   (true-exp expression?)
   (false-exp expression?))
  (lambda-exp
   (ids (list-of symbol?))
   (body expression?))
  (app-exp
   (rands (list-of expression?))))

追加分含め、試験が以下。

(use gauche.test)

(add-load-path ".")
(load "define-datatype")
(load "expression")

(test-start "expression")
(test-section "expression")
(test* "expression is ..."
       '((nul-exp lit-exp var-exp if-exp lambda-exp app-exp)
	 (nul-exp datum)
	 (lit-exp datum)
	 (var-exp id)
	 (if-exp test-exp true-exp false-exp)
	 (lambda-exp ids body)
	 (app-exp rands))
       expression)

(test-section "expression?")
(test* "(nul-exp) is expression"
       #t
       (expression? '(nul-exp)))

(test* "(lit-exp) is expression"
       #t
       (expression? '(lit-exp)))
(test* "(var-exp) is expression"
       #t
       (expression? '(var-exp)))
(test* "(if-exp) is expression"
       #t
       (expression? '(if-exp)))
(test* "(lambda-exp) is expression"
       #t
       (expression? '(lambda-exp)))
(test* "(app-exp) is expression"
       #t
       (expression? '(app-exp)))

(test-section "nul-exp")
(test* "(nul-exp ()) is (nul-exp ())"
       '(nul-exp ())
       (nul-exp '()))

(test-section "lit-exp")
(test* "(lit-exp 1) is (lit-exp 1)"
       '(lit-exp 1)
       (lit-exp 1))

(test-section "var-exp")
(test* "(var-exp 'x) is (var-exp x)"
       '(var-exp x)
       (var-exp 'x))

(test-section "if-exp")
(test* "(if-exp (var-exp x) (lit-exp 1) (lit-exp 2)) is ..."
       '(if-exp (var-exp x) (lit-exp 1) (lit-exp 2))
       (if-exp (var-exp 'x) (lit-exp 1) (lit-exp 2)))

(test-section "lambda-exp")
(test* "(lambda-exp (x) (lit-exp 1)) is ..."
       '(lambda-exp (x) (lit-exp 1))
       (lambda-exp '(x) (lit-exp 1)))

(test-section "app-exp")
(test* "(app-exp '((var-exp x) (lit-exp 1))) is ..."
       '(app-exp ((var-exp x) (lit-exp 1)))
       (app-exp '((var-exp x) (lit-exp 1))))

(test-end)

試験は無問題。次は parse 周辺をナニ。以下が実装。

(add-load-path ".")
(load "define-datatype")
(load "variable-reference")

(define unparse-expression
  (lambda (exp)
    (if ((isa expression) exp)
	(unparse-exp exp)
	(unparse-var exp))))

(define unparse-exp
  (lambda (exp)
    (cases expression exp
	   (nul-exp (datum) '())
	   (lit-exp (num) num)
	   (var-exp (id) id)
	   (if-exp (test-exp true-exp false-exp)
		   (list 'if 
			 (unparse-expression test-exp) 
			 (unparse-expression true-exp) 
			 (unparse-expression false-exp)))
	   (lambda-exp (ids body)
		       (list 'lambda ids
			     (unparse-expression body)))
	   (app-exp (rands)
		    (let f ((rslt '()) (rands rands))
		      (if (null? rands)
			  rslt
			  (f (append rslt (list (unparse-expression (car rands)))) (cdr rands)))))
	   )))

(define unparse-var
  (lambda (exp)
    (cases variable-reference exp
	   (lex-info (id depth position) id)
	   (free-info (id) id))))

(define parse-expression
  (lambda (datum)
    (cond
     ((null? datum) (nul-exp datum))
     ((number? datum) (lit-exp datum))
     ((symbol? datum) (var-exp datum))
     ((pair? datum)
      (cond ((eqv? (car datum) 'lambda)
	     (lambda-exp (cadr datum)
			 (parse-expression (caddr datum))))
	    ((eqv? (car datum) 'if)
	     (if-exp (parse-expression (cadr datum))
		     (parse-expression (caddr datum))
		     (parse-expression (cadddr datum))))
	    (else
	     (let f ((rslt '()) (datum datum))
		(if (null? datum)
		    (cons 'app-exp (list rslt))
		    (f (append rslt (list (parse-expression (car datum))))
		       (cdr datum)))))))
     (else
      (eopl:error 'parse-expression
		  "Invalid concrete symtax ~s" datum)))))

以下が試験。一応パスしてますが、試験に不足がないかどうかは微妙。

(use gauche.test)

(add-load-path ".")
(load "define-datatype")
(load "expression")
(load "occurs-free")
(load "parse")

(test-start "parse")
(test-section "unparse-expression")
(test* "unparse 1"
       1
       (unparse-expression '(lit-exp 1)))

(test* "unparse 'a"
       'a
       (unparse-expression '(var-exp a)))

(test* "unparse (if x 1 2)"
       '(if x 1 2)
       (unparse-expression '(if-exp (var-exp x) (lit-exp 1) (lit-exp 2))))

(test* "unparse (lambda (x) x)"
       '(lambda (x) x)
       (unparse-expression '(lambda-exp (x) (var-exp x))))

(test* "unparse ((lambda (x) x) a)"
       '((lambda (x) x) a)
       (unparse-expression '(app-exp ((lambda-exp (x) (var-exp x)) (var-exp a)))))

(test* "unparse (lambda (x) (f x y))"
       '(lambda (x) (f x y))
       (unparse-expression '(lambda-exp (x) (app-exp ((var-exp f) (var-exp x) (var-exp y))))))

(test* "unparse (lambda (x) (x))"
       '(lambda (x) (x))
       (unparse-expression '(lambda-exp (x) (app-exp ((var-exp x))))))

(test* "unparse (lambda (x y z) x)"
       '(lambda (x y z) x)
       (unparse-expression '(lambda-exp (x y z) (var-exp x))))

(test* "unparse (lambda () x)"
       '(lambda () x)
       (unparse-expression '(lambda-exp () (var-exp x))))

(test* "unparse (if (null? l) x y)"
       '(if (null? l) x y)
       (unparse-expression '(if-exp (app-exp ((var-exp null?) (var-exp l)))
				    (var-exp x)
				    (var-exp y))))

(test* "unparse .."
       '(lambda (x y z)
	  ((lambda (x y) (+ x y z)) y x))
       (unparse-expression '(lambda-exp (x y z)
					(app-exp 
					 ((lambda-exp 
					   (x y) (app-exp ((free-info +)
							   (lex-info x 0 0)
							   (lex-info y 0 1)
							   (lex-info z 1 2))))
					  (lex-info y 0 1) 
					  (lex-info x 0 0))))))


(test-section "parse-expression")
(test* "parse '()"
       '(nul-exp ())
       (parse-expression '()))

(test* "parse 1"
       '(lit-exp 1)
       (parse-expression 1))

(test* "parse 'a"
       '(var-exp a)
       (parse-expression 'a))

(test* "parse (if x 1 2)"
       '(if-exp (var-exp x) (lit-exp 1) (lit-exp 2))
       (parse-expression '(if x 1 2)))

(test* "parse (lambda (x) x)"
       '(lambda-exp (x) (var-exp x))
       (parse-expression '(lambda (x) x)))

(test* "parse ((lambda (x) x) a)"
       '(app-exp ((lambda-exp (x) (var-exp x)) (var-exp a)))
       (parse-expression '((lambda (x) x) a)))

(test* "string is undefined"
       *test-error*
       (parse-expression "a"))

(test* "parse (a b c)"
       '(app-exp ((var-exp a) (var-exp b) (var-exp c)))
       (parse-expression '(a b c)))

(test* "parse ((lambda (x) (+ x x)) a)"
       '(app-exp ((lambda-exp (x) (app-exp ((var-exp +) (var-exp x) (var-exp x)))) (var-exp a)))
       (parse-expression '((lambda (x) (+ x x)) a)))

(test* "parse (lambda (x) (x))"
       '(lambda-exp (x) (app-exp ((var-exp x))))
       (parse-expression '(lambda (x) (x))))

(test* "parse (if (null? l) x y)"
       '(if-exp (app-exp ((var-exp null?) (var-exp l))) (var-exp x) (var-exp y))
       (parse-expression '(if (null? l) x y)))

(test-end)

パスしてます。これで

test (free-vars '(lambda (x) ()) should return (), expects ()

な試験にパス。残りは

test (free-vars '(lambda (x y) (x 1 2) ((y) 3 z)) should return (z): expects (z) => got ()

なんですが、これはダサいんですが、以下でナニ。

(define parse-expression
  (lambda (datum)
    (cond
     ((null? datum) (nul-exp datum))
     ((number? datum) (lit-exp datum))
     ((symbol? datum) (var-exp datum))
     ((pair? datum)
      (cond ((eqv? (car datum) 'lambda)
	     (lambda-exp (cadr datum)
			 (if (= 1 (length (cddr datum)))
			     (parse-expression (caddr datum))
			     (parse-expression (cddr datum)))))
	    ((eqv? (car datum) 'if)
	     (if-exp (parse-expression (cadr datum))
		     (parse-expression (caddr datum))
		     (parse-expression (cadddr datum))))
	    (else
	     (let f ((rslt '()) (datum datum))
		(if (null? datum)
		    (cons 'app-exp (list rslt))
		    (f (append rslt (list (parse-expression (car datum))))
		       (cdr datum)))))))
     (else
      (eopl:error 'parse-expression
		  "Invalid concrete symtax ~s" datum)))))

追加した試験のみ以下に。

(test* "parse (lambda (x) (x y) (z a))"
       '(lambda-exp (x) (app-exp ((app-exp ((var-exp x) (var-exp y)))
				  (app-exp ((var-exp z) (var-exp a))))))
       (parse-expression '(lambda (x) (x y) (z a))))

これで free-vars な試験は全部パス。次は bound-vars か、と言いつつ実装着手。作ってみてびっくり。実装は以下なんですが

(add-load-path ".")
(load "define-datatype")
(load "expression")
(load "parse")

(define (bound-vars l)
  (bound-vars-test (parse-expression l)))

(define (bound-vars-test l)
  (let f ((rslt '()) (arg '()) (l l))
    (cases expression l
	   (nul-exp (datum) rslt)
	   (lit-exp (num) rslt)
	   (var-exp (id)
		    (let free-symbol ((l arg) (s id))
		      (cond ((null? l) rslt)
			    ((eq? (car l) s)
			     (append rslt (list s)))
			    (else
			     (free-symbol (cdr l) s)))))
	   (lambda-exp (ids body)
		       (f rslt (append arg ids) body))
	   (app-exp (rands)
		    (let app-exp-inner ((rslt rslt) (rands rands))
		      (if (null? rands)
			  rslt
			  (app-exp-inner (f rslt arg (car rands))
					 (cdr rands)))))
	   (else
	    rslt)
	   ))
	    
  )

free-vars との差分が以下。って diff とったら出力微妙だな。。ちがうのは var-exp の分岐の中身だけです (勿論、手続きの名前とかは異なりますが)

	   (var-exp (id)
		    (let free-symbol ((l arg) (s id))
		      (cond ((null? l) rslt)
			    ((eq? (car l) s)
			     (append rslt (list s)))
			    (else
			     (free-symbol (cdr l) s)))))

ええと、ここだけ違うんだったら lambda なソレを渡せばなんとかなりますな。余裕があったら追記しますし、微妙だったら明日の現実トウヒなネタになります (を