プログラミングに名前付けは不可欠です。いや、「プログラミングとは名前付けである」と言って良いでしょう。Schemeプログラムでもデータや手続きに名前をつけることができます。Scheme言語でデータや手続きに名前を付けるにはいくつかの方法がありますが、ここでは次の3つを取り上げます。
- defineによる大域的な名前付け
- 関数引数による名前付け
- letによる局所的な値への名前付け
大域的、つまり処理系のトップレベルで名前を付けるにはdefineを使います。関数内での局所的に名前を付けるには、関数引数による名前付けやletによる局所名前付けがあります。ここではまず関数引数による名前付けを説明し、次にletによる局所的な名前付けを説明します。
letによる名前付けは実は関数引数による名前付けと等価なのですが、これについては「lambdaは空気のような存在である」で詳しく説明します。
defineによる大域的な名前付け
大域的な名前をつけるにはdefineを使います。
(define 名前 式)
この構文は式の値に名前をつけます。
プログラムは複雑な処理を手続きというものにまとめて名前を付けたり、変更する場所を
一か所にするために値に名前を付けます。この名前を付けるという行為はモノを考えるレベルを上げるための行為です。例えば、
10
という数値はただの数値です。それ以外の意味は持ちません。しかし、
(define my-money 10)
のように10に名前を付けることで、その数値に意味を持たせることができます。
ここでは「私のお金は10」と読むことができますね(お金の単位がありませんが)。
これによって
(set! my-money (+ my-money 1))
と書けば、「私のお金に1を足して、それを新しく私のお金にした」と読むことがで
きます。set!手続きは新たな値にmy-moneyという(すでに存在していた)名前を付け直します。
また、この「私のお金に1を足して、それを新しく私のお金にした」という
処理を手続きにまとめて名前を付ければ
(define (add1-my-money)
(set! my-money (+ my-money 1)))
私のお金に1を足す手続きと読めます。そして以下の式を評価すれば
(add1-my-money)
「私のお金に1を足した」と読むことができます。このように、名前を付けるという
行為は値に意味を持たせると共に、人が読みやすいプログラムを作るという意味でも
重要です。
Schemeでは値に名前を付けることを束縛(bind)と呼んでいます。
例えば上記の10にmy-nameという名前を付ける例は
名前my-moneyを数値10に束縛する
と言えます。bindという英語に「束縛」という訳語を使っているのですが、束縛という言葉は数学に由来しています。束縛を「バインド」というカタカナ語に言い換えて、
名前my-ageを数値10にバインドする
としても同じ意味です。ですが本書では「束縛」という訳語を使用します。
手続きに名前をつける
Schemeでは手続きも値を持ちます。たとえば+という演算子の値はどんな型でしょうか。
gosh> (class-of +)
#<class <procedure>>
+演算子の値は<procedure>型(手続き型)でした。では+自身の値は何でしょうか。
gosh> +
#<subr +>
subrとはサブルーチンの略です。subrはGaucheにあらかじめ組み込まれた手続きの値であることを表しています。
+も値を持つことが分かったので、+の値に別の名前をつけてみましょう。
(define op +)
+の値にopという名前をつけたので、opを使って足し算することができます。
gosh> (op 0 1 2 3 4)
10
手続きの組み合わせに名前をつける
手続きの組み合わせに名前を付ける方法もあります。たとえば2乗を計算する手続きを書いてsquareという名前をつけてみましょう。
gosh> (define (square x) (* x x))
square
square手続きは一つ引数をとり、それにxという名前を付けます。
一般的にdefineを使って手続きに名前をつける構文は以下です。
(define (手続きの名前 引数の仕様) 式)
2乗の計算にsquareという名前を付けたので、squareを使って2乗を計算することができます。
gosh> (square 100)
10000
gosh> (square 1.82)
3.3124000000000002
関数引数による束縛
square手続きは引数にも名前を付けています。square手続きが100や1.82などの実際の値に適用されたとき、名前xは値100や値1.82に束縛されます。
つまり関数引数も名前を(実際の)値に束縛するのです。これは置き換えモデルによって考えることができます。
(define (square x) (* x x))
(square 100) → (* 100 100) → 10000
(square 1.82) → (* 1.82 1.82) → 3.3124
letで局所的な値に名前を付ける
letは局所的な値に名前を付けます。
たとえば、0〜6までの数値を引数にとり、それぞれ"日"、"月"、..."土"までの曜日の名前を返す手続きweekday-nameを書いてみましょう。
(define (weekday-name index)
(let ((day-names (list "日" "月" "火" "水" "木" "金" "土")))
(if (or (< index 0)
(> index 6))
#f ;; 0〜6の範囲外なら偽
(list-ref day-names index)))) ;; 曜日名を返す
weekday-name手続きの中のlet式はday-namesという名前を、"日"〜"土"の曜日名のリストに束縛します。つまり、曜日名のリストにday-namesという名前をつけます。
一般にlet式は次の書式をとります。
(let ((名前1 初期値1)
(名前2 初期値2)
...
(名前n 初期値n))
式)
let式でつけた名前が有効なのはlet式の括弧の内部だけです。
(define (weekday-name index)
(let ((day-names (list "日" "月" "火" "水" "木" "金" "土")))
(if (or (< index 0)
(> index 6))
#f
(list-ref day-names index))))
ここでday-namesはlet式を括る括弧の内部だけで有効です。if式はlet式の内部にあるので名前day-namesが使用できます。
list-ref手続きはリストと数を引数にとり、指定した数だけリストの先頭から離れた位置の要素の値を得ます。
(list-ref リスト 数)
ここでは0ならリスト先頭の"日"を返し、6ならリスト先頭から6離れた要素"土"を返します。
let式ではいくつでも値に名前をつけられますが、weekday-name手続きでは名前を一つしかつけませんでした。Gaucheではletでなくlet1を使った以下の記述も可能です。
(define (weekday-name index)
(let1 day-names (list "日" "月" "火" "水" "木" "金" "土")
(if (or (< index 0)
(> index 6))
#f
(list-ref day-names index))))
注意すべきはlet1がGaucheの独自拡張だということです。他のScheme言語処理系ではそのまま使えません。
letと同じく局所的な名前をつける式にはlet*やletlec、Gaucheの独自拡張であるand-let*など様々なものがあります。これらについては「第2部 文法」の「特殊形式」で説明しています。