Guile基礎/変数定義(define 形式)

変更履歴

概 要

目 次

参考資料

define形式

define 形式は4つの異なる形式があります. 以下の最初のものが基本的な形式で, 残りのものは手続き定義のための糖衣構文(syntax sugar)と見なすことができます.

(define identifier expression) syntax
   identifier  ::=  識別子
   expression  ::=  式

この define 形式は,変数(identifier)を式(expression)の評価結果に束縛します. つまり,変数(identifier)を新たな場所(location)に束縛し, その場所に式(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
   body        ::=  definition* expression+
   identifier  ::=  識別子
   definition  ::=  定義(define形式など)
   expression  ::=  式

この define 形式は無引数の手続きを定義するためのものです. identifier はその手続きの名前です. これは,基本的な define 形式と lambda 式を組み合わせた次の定義と等価です.

(define identifier (lambda () body+))

(define (id$_0$ id$_1$ ... id$_n$) body+) syntax
   idk  ::=  identifier

この define 形式は,$n$引数の手続きを定義するためのものです.id$_0$ は手続きの名前で,id$_1$ ... id$_n$ は仮引数の名前です.これは,基本的な define 形式と lambda 式を組み合わせた次の定義と等価です.

(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
   formals  ::=  (identifier*)
                |  identifier
                |  (identifier+ . identifier)

define-values 形式は expression が多値を返すときに使用します.formals は lambda 式の仮引数部と同様の変数パターンを指定します.

変数部のパターン

上の文法が示すように,変数部のパターンは以下の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)
は次のように振る舞います. 以上から,トップレベルでは1つの identifier に対して複数の define 形式を記述できます.ただし,ある式を評価するとき, その式の直近で行われた定義が有効であり,それより前の定義は無効です.

上記の2番目の場合はREPLにおいて頻繁に(そして暗黙的に)利用しています.REPLにおいて,定義済みの変数に対して define 形式を実行したり,1度ロードしたことのあるプログラムファイルを再ロードした場合,定義済みの変数の値を定義し直すことになります.
guile> (define x 10)
guile> x
$1 = 10
guile> (define x 20)
guile> x
$2 = 20

内部定義

トップレベルだけでなく,lambda 式や let 式などの本体(構文規則において body と表される構文要素)にも define 形式を使って変数を定義することができます.そのような定義は 内部定義(internal definition)と呼ばれています.

内部定義については次の点に注意しなければなりません.

let 式への翻訳(R7RS版)

以下は,lambda 式や let 式などにおける本体(body)とします.

(define $x_1$ $e_1$)
 ......
(define $x_n$ $e_n$)
expression+

Guile では,これは以下の letrec* 式と等価です.

(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 式で囲む必要はありません.

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 のスコープになっていることです. つまり, ということになります. 上記の便法(テクニック)は,このスコープルールを守るためのものです. つまり,ネストした letrec* 式に翻訳するわけにはいかないので, 上のような便法を使っているのです.

以上の説明とR7RS版の説明を合わせて考えると,Guile版の内部定義の let 式への翻訳は次のように説明できます.以下のものは,lambda 式や let 式などのGuile版の本体(body)とします.以下の ... は定義が続いているものとします.

(define $x_1$ $e_1$)
 ......
expression$_1$+
(define $x_2$ $e_2$)
 ......
expression$_2$+
(define $x_n$ $e_n$)
 ......
expression$_n$+
この本体(body)は,以下の let 式に等価であると考えられます.

(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$+)

具体例

まず値が不定($\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_UNSPECIFIED

The 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
(おしまい)