define形式
define 形式は4つの異なる形式があります. 以下の最初のものが基本的な形式で, 残りのものは手続き定義のための糖衣構文(syntax sugar)と見なすことができます.
(define identifier expression) syntax
この define 形式は,変数(identifier)を式(expression)の評価結果に束縛します.
つまり,変数(identifier)を新たな場所(location)に束縛し,
その場所に式(expression)の評価結果を格納します.
この定義のあと,変数を通して場所に格納されている値が利用できるようになります.
なお,厳密に言うと,トップレベルと内部定義では振る舞いが異なります.
identifier ::= 識別子 expression ::= 式
補足
define 形式によって生成される束縛は, 変数(identifier)と値の関係ではなく, 変数と場所(location)の関係です. なぜなら,変数は場所に付けた名前なので結びつきは変化しないのに対して, 場所に格納される値は変化しするので変数と値の結びつきは変化するからです. しかし,便宜的な言葉使いとして, 「変数を値に束縛する」といった言い方をしてしまいます. この点について,R7RS[3.1節]の第1段落に次のような記述があります.By abuse of terminology, the variable is sometimes said to name the value or to be bound to the value. This is not quite accurate, but confusion rarely results from this practice.
(define (identifier) body+) syntax
この define 形式は無引数の手続きを定義するためのものです.
identifier はその手続きの名前です.
これは,基本的な define 形式と lambda 式を組み合わせた次の定義と等価です.body ::= definition* expression+ identifier ::= 識別子 definition ::= 定義(define形式など) expression ::= 式
(define identifier (lambda () body+))
(define (id$_0$ id$_1$ ... id$_n$) body+) syntax
この define 形式は,$n$引数の手続きを定義するためのものです.id$_0$ は手続きの名前で,id$_1$ ... id$_n$ は仮引数の名前です.これは,基本的な define 形式と lambda 式を組み合わせた次の定義と等価です.idk ::= identifier
(define id$_0$ (lambda (id$_1$ ... id$_n$) body+))
(define (id$_0$ id$_1$ ... id$_n$ . id$_{n+1}$) body+) syntax
この define 形式は,$n$個の必須仮引数と可変的仮引数を持った手続きを定義するためのものです.id$_0$ は手続きの名前で,id$_1$ ... id$_{n+1}$ は仮引数の名前です.ピリオドの前後は空白を入れなければいけません.
これは,基本的な define 形式と lambda 式を組み合わせた次の定義と等価です.
(define id$_0$ (lambda (id$_1$ ... id$_n$ . id$_{n+1}$) body+))
(define (id$_0$ . id$_1$) body+) syntax
この define 形式は可変的仮引数を持った(必須仮引数がない)手続きを定義するためのものです.id$_0$ は手続きの名前で,id$_1$ は仮引数の名前です.ピリオドの前後は空白を入れなければいけません.
これは,基本的な define 形式と lambda 式を組み合わせた次の定義と等価です.
(define id$_0$ (lambda id$_1$ body+))
具体例
以下の1番目の define 形式は,変数 pi を円周率(の近似値)に束縛しています.2番目と3番目は,半径 r の円の面積を求める手続き(area-of-disk1 と area-of-disk2)を定義しています. 形式は異なりますが,両方の定義は等価です.;; defs.scm (define pi 3.1415926) (define (area-of-disk1 r) (* pi r r)) (define area-of-disk2 (lambda (r) (* pi r r)))
guile> (load "defs.scm") ...... コンパイルメッセージ ...... guile> (area-of-disk1 2.0) $1 = 12.5663704 guile> (area-of-disk2 2.0) $2 = 12.5663704以下の define 形式は, 実引数として与えられた数値の総和を求める手続き(sum-args)を定義しています.
;; sum-args.scm (define (sum-args . args) (let loop ((xs args) (sum 0)) (if (null? xs) sum (loop (cdr xs) (+ sum (car xs))))))
guile> (load "sum-args.scm") ...... コンパイルメッセージ ...... guile> (sum-args 1 2 3) $1 = 6 guile> (sum-args 1 2 3 4 5) $2 = 15上記の define 形式は,lambda 式を用いた次の定義と等価です.
(define sum-args (lambda args (let loop ((xs args) (sum 0)) (if (null? xs) sum (loop (cdr xs) (+ sum (car xs))))))
define-once 形式
define-once 形式は次の文法に沿って記述します.
(define-once identifier expression) syntax
define-once 形式は (ice-9 boot-9) モジュールの中で次のように定義されています.
(define-syntax-rule (define-once sym val) (define sym (if (module-locally-bound? (current-module) 'sym) sym val)))仮引数の名前が上の文法と異なっていますが,sym は identifier のことで,val は expression のことです.この定義は,sym がすでに束縛されていたら define-once 形式を
(define sym sym)
に展開し,sym が束縛されていなかったら
(define sym val)
に展開します.
つまり,sym がすでに定義されていたらその値は変えないようにして,
定義されていなかったら新たに定義します.
define-once という名前から,
多重定義や代入を行おうとするとエラーになるとか例外が発生する,
などと期待してしまいますが,
以下の実行例が示すようにそれらの期待は成り立ちません.define-once 形式は,
単に,束縛済みの変数の値を変えないということだけを保証するものです.
guile> (define-once x 10) guile> x $13 = 10 guile> (define x 20) guile> x $2 = 20 guile> (set! x 30) guile> x $3 = 30 guile> (define-once x 40) guile> x $4 = 30
define-values 形式
define-values 形式は次の文法に沿って記述します.
(define-values formals expression) syntax
define-values 形式は expression が多値を返すときに使用します.formals は lambda 式の仮引数部と同様の変数パターンを指定します.
formals ::= (identifier*) | identifier | (identifier+ . identifier)
変数部のパターン
上の文法が示すように,変数部のパターンは以下の3つがあります.
(define-values (id$_1$ ... id$_n$) expression) syntax
この場合,expression は $n$ 個の値を返さなければなりません.その $n$ 個の値が,先頭から順に,id$_1$ ... id$_n$ を束縛します.以下の define-values 形式は,(floor/ 10 3) が 10 を 3 で割ったときの商と余りを多値として返すので,q を 商に束縛し,r を余りに束縛します.
guile> (define-values (q r) (floor/ 10 3)) guile> q $1 = 3 guile> r $2 = 1
(define-values identifier expression) syntax
この場合,expression が返す値からなるリストを新たに生成して,identifier をそのリストに束縛します.以下の define-values 形式は,z を (floor/ 10 3) が返す商と余りからなるリストに束縛します.
guile> (define-values z (floor/ 10 3)) guile> z $3 = (3 1)expression は(多値ではなく)普通の値を返す式でも構わないようです.でも,以下の実行例が示すように identifier は,式の値そのものではなく,式の値からなるリストに束縛されます.
guile> (define-values z (+ 10 20)) guile> z $4 = (30)それから,どうでもよいことのように思えますが,expression は値を何も返さない式でも構いません. その場合,以下の実行例が示すように identifier は空リストに束縛されます.
guile> (define-values z (values)) guile> z $5 = ()ただ,値を返さない式は (values) しか思いつきません. ちなみに,display などの「値を返さない式」のほとんどは #<unspecified> を返します.
(define-values (id$_1$ ... id$_n$ . id$_{n+1}$) expression) syntax
ピリオドの前後には空白を入れなければなりません.
このパターンの場合,expression は $n$ 個以上の値を返さなければなりません.その先頭の $n$ 個の値が順にid$_1$ ... id$_n$ を束縛し,残りの値からなるリストが id$_{n+1}$ を束縛します.以下の実行列では,x と y はそれぞれ 11 と 22 に束縛され,z は残りの数値からなるリストに束縛されます.
guile> (define-values (x y . z) (values 11 22 33 44)) guile> x $6 = 11 guile> y $7 = 22 guile> z $8 = (33 44)
補足
変数を1つも指定しないパターンもあります.ただ,意味があるとは思えません.参考
define-values 形式は (ice-9 boot-9) モジュールの中で次のように定義されています.(define-syntax define-values (lambda (orig-form) (syntax-case orig-form () ((_ () expr) ;; XXX Work around the lack of hygienic top-level identifiers (with-syntax (((dummy) (generate-temporaries '(dummy)))) #`(define dummy (call-with-values (lambda () expr) (lambda () #f))))) ((_ (var) expr) (identifier? #'var) #`(define var (call-with-values (lambda () expr) (lambda (v) v)))) ((_ (var0 ... varn) expr) (and-map identifier? #'(var0 ... varn)) ;; XXX Work around the lack of hygienic toplevel identifiers (with-syntax (((dummy) (generate-temporaries '(dummy)))) #`(begin ;; Avoid mutating the user-visible variables (define dummy (call-with-values (lambda () expr) (lambda (var0 ... varn) (list var0 ... varn)))) (define var0 (let ((v (car dummy))) (set! dummy (cdr dummy)) v)) ... (define varn (let ((v (car dummy))) (set! dummy #f) ; blackhole dummy v))))) ((_ var expr) (identifier? #'var) #'(define var (call-with-values (lambda () expr) list))) ((_ (var0 ... . varn) expr) (and-map identifier? #'(var0 ... varn)) ;; XXX Work around the lack of hygienic toplevel identifiers (with-syntax (((dummy) (generate-temporaries '(dummy)))) #`(begin ;; Avoid mutating the user-visible variables (define dummy (call-with-values (lambda () expr) (lambda (var0 ... . varn) (list var0 ... varn)))) (define var0 (let ((v (car dummy))) (set! dummy (cdr dummy)) v)) ... (define varn (let ((v (car dummy))) (set! dummy #f) ; blackhole dummy v))))))))
define* 形式,define-inlinable 形式
これまで説明したもの以外に, define* 形式 と define-inlinable 形式 があります.トップレベルの定義
トップレベル(あらゆる式の外側)で定義した変数は, プログラム全体においてアクセスできます. トップレベルの define 形式
(define identifier expression)
は次のように振る舞います.
- identifier が束縛されていない場合 (または,構文キーワードだった場合), identifier が新たな場所に束縛され,その場所に expression の評価結果が格納されます.
-
identifier がすでに束縛されている場合,次の set! 形式と等価です.
(set! identifier expression)つまり,expression の評価結果が identifier を束縛している場所に格納されます.
guile> (define x 10) guile> x $1 = 10 guile> (define x 20) guile> x $2 = 20
内部定義
トップレベルだけでなく,lambda 式や let 式などの本体(構文規則において body と表される構文要素)にも define 形式を使って変数を定義することができます.そのような定義は 内部定義(internal definition)と呼ばれています. 内部定義については次の点に注意しなければなりません.- 1つの本体(body)の中では重複定義はできません. つまり,1つの変数に対して複数の define 形式を記述することはできません.
- R5RS は,内部定義は letrec 式に等価であると述べていて, R6RS や R7RS は,内部定義は letrec* 式に等価であると述べています.従って,処理系によって内部定義の振る舞いは異なるかも知れません.Guile(少なくとも3.0以降)は R6RS や R7RS に準拠しようとしているので,内部定義は letrec* 式に等価です.なお,「等価」の意味は後述します.
- Schemeの仕様書は,R5RS,R6RS,R7RSのいずれも, 内部定義は本体(body)の先頭部分に書かなければならない,と定めています.でも,Guile はこの制約を緩和していて,最後が式でありさえすれば, 本体の中で定義と式を交互に書くことができます.
let 式への翻訳(R7RS版)
以下は,lambda 式や let 式などにおける本体(body)とします.
(define $x_1$ $e_1$)
......
(define $x_n$ $e_n$)
expression+
Guile では,これは以下の letrec* 式と等価です.
......
(define $x_n$ $e_n$)
expression+
(letrec* (($x_1$ $e_1$) ... ($x_n$ $e_n$)) expression+)
さらに,TSPL[Section 4.4. Local Binding] や
R7RS[7.3. Derived expression types] を参考にすると,
この letrec* 式は以下の let 式と等価であると考えられます.
(let (($x_1$ $\bot$) ... ($x_n$ $\bot$))
(set! $x_1$ $e_1$)
......
(set! $x_n$ $e_n$)
expression+)
ここで,$\bot$ は,SchemeやGuileの構文要素ではなく,
値が不定であることを表す便宜的な記号です.
これは,R7RS[7.3. Derived expression types] では <undefined> と表されていて,Guileでは #<unspecified> と表示されます.ただし,
(少なくとも Guile において)ソースコード上で利用可能なリテラルはありません.
上記の let 式の本体の中でこれを参照しようとしたときには動作は不確定である(または,エラーが発生する)とします.
注意:
R7RS[7.3. Derived expression types] では,letrec* 式の本体を let 式によって囲んでいます.
でも,それは本体が定義を含んでいるかも知れないためです.
上記の翻訳では,letrec* 式の本体は式だけからなるので let 式で囲む必要はありません.
(set! $x_1$ $e_1$)
......
(set! $x_n$ $e_n$)
expression+)
let 式への翻訳(Guile版)
前にも述べたように,Guileでは,最後が式でありさえすれば, 本体(body)に定義と式を交互に書くことができます. 例えば,次のような let 式を書くことができます.(let () (define a 1) (foo) (define b 2) (+ a b))Guile[6.10.3 Internal definitions] は,これが以下の式に等価であると説明しています.
(let () (letrec* ((a 1) (_ (begin (foo) #f)) (b 2)) (+ a b)))ここで,束縛部の
(_ (begin (foo) #f))
は,a の束縛と b の束縛の間に (foo) を実行するための便法(テクニック)です.
さらに重要なポイントは,letrec* 式全体が a と b のスコープになっていることです.
つまり,
- define 形式を本体(body)のどの位置に書いたとしても, その define 形式によって定義される変数のスコープは本体(body)の全体である
(define $x_1$ $e_1$)
......
expression$_1$+
(define $x_2$ $e_2$)
......
expression$_2$+
(define $x_n$ $e_n$)
......
expression$_n$+
この本体(body)は,以下の let 式に等価であると考えられます.
......
expression$_1$+
(define $x_2$ $e_2$)
......
expression$_2$+
(define $x_n$ $e_n$)
......
expression$_n$+
(let (($x_1$ $\bot$) ... ($x_2$ $\bot$) ... ($x_n$ $\bot$) ...)
(set! $x_1$ $e_1$)
......
expression$_1$+
(set! $x_2$ $e_2$)
......
expression$_2$+
(set! $x_n$ $e_n$)
......
expression$_n$+)
(set! $x_1$ $e_1$)
......
expression$_1$+
(set! $x_2$ $e_2$)
......
expression$_2$+
(set! $x_n$ $e_n$)
......
expression$_n$+)
具体例
まず値が不定($\bot$)の場合の振る舞いを確認しておきましょう. 以下はテスト用の手続きです.;; in-defs.scm (define (test-proc-1) (define x (string-append "x is:" y)) (define y "this is y") (display x) (newline))この手続きの本体では,x を定義する際に y を参照しています. でも,x を定義する時点では y の値は確定していないので, 以下のようなエラーが発生しています.
guile> (load "in-defs.scm") ...... コンパイルメッセージ ...... guile> (test-proc-1) ice-9/boot-9.scm:1669:16: In procedure raise-exception: In procedure string-append: Wrong type (expecting string): #<unspecified>上記のエラーメッセージは, string-append 手続きに #<unspecified> という値が与えられたことを示しています.それは y の値のはずです,従って, 前述した $\bot$ は #<unspecified> と表示される値になっているようです.
補足
#<unspecified> と表示される値は,コアシステムの内部では SCM_UNSPECIFIED という(C言語の)マクロで表されています.ただ,Schemeにおけるリテラルはありません. 参考までに Guile[9.2.5.2 Immediate Objects] に記述されている SCM_UNSPECIFIED の説明を引用します.やや否定的なコメントが含まれています.Macro: SCM SCM_UNSPECIFIEDThe value returned by some (but not all) expressions that the Scheme standard says return an "unspecified" value. This is sort of a weirdly literal way to take things, but the standard read-eval-print loop prints nothing when the expression returns this value, so it’s not a bad idea to return this when you can’t think of anything else helpful.
具体例
次に,内部定義の define 形式によって生成される束縛のスコープが本体(body)の全体であることを確認しておきましょう. 以下のプログラムでは,手続き g をわざと最後のほうに定義しています.;; in-defs.scm (define (test-proc-2) (define f (lambda (x) (g x))) (define a 5) (format #t "a=~A\n" a) (define g (lambda (x) (* x x))) (format #t "f(a)=~A\n" (f a)))g のスコープは手続きの本体全体なので,test-proc-2 は問題なく動作します.
guile> (load "in-defs.scm") guile> (test-proc-2) a=5 f(a)=25 $1 = #t