レコード型の定義
モジュールのロード
下記の define-record-type を利用するためには,
以下に示すように (srfi srfi-9) モジュールをロードする必要があります.
(use-modules (srfi srfi-9))
define-record-type
レコード型は以下の define-record-type 形式を使って定義します.
syntax:
(define-record-type $type$-$name$
($constructor$ $field$-$name^*$)
$predicate$
$field$-$spec^*$)
|
$type$-$name$ |
::= | $identifier$ |
$constructor$ |
::= | $identifier$ |
$field$-$name$ |
::= | $identifier$ |
$predicate$ |
::= | $identifier$ |
$field$-$spec$ |
::= | ($field$-$name$ $accessor$) |
| $|$ | ($field$-$name$ $accessor$ $modifier$) |
$accessor$ |
::= | $identifier$ |
$modifier$ |
::= | $identifier$ |
$identifier$ |
::= | 識別子 |
上記の各構成要素は次のような意味を持ちます.
$type$-$name$ |
新たに定義するレコード型の型名
|
$constructor$ |
$type$-$name$型のデータを生成する手続きの名前
|
$field$-$name$ |
$type$-$name$型のデータを構成するフィールドの名前
|
$predicate$ |
$type$-$name$型の型検査を行う手続きの名前
|
$field$-$spec$ |
各フィールド($field$-$name$)に対して,
そのフィールドの値を取得するための手続きの名前($accessor$)と,
値を設定するための手続きの名前($modifier$)からなる.
|
$aceessor$ |
フィールドの値を取得するための手続きの名前
|
$modifier$ |
フィールドの値を設定するための手続きの名前
|
define-record-type は,レコードの型,レコードの情報構造(フィールド),
上で述べた各種手続きを一気に定義します.
例えば,以下は人の情報を扱うためのレコード型を定義しています.
;; record-sample.scm
(define-record-type <person>
(make-person name character webpage)
person?
(name get-person-name)
(character get-person-character set-person-character!)
(webpage get-person-webpage set-person-webpage!))
ここで:
-
<person> は型名を表しています.型名の前後を三角括弧で囲んでいるのは慣習的なものでしかありません.型名は,例えば,person にしたり someone にしてもかまいません.
-
make-person は <person> 型のデータを生成するための手続き($constructor$)を表しています.その後ろに指定した name,character,webpage はフィールド名($field$-$name$)を表しています.この例では,人の情報として,氏名,特性や興味,Webページのアドレスを記録することを意図しています.
-
person? は,<person>型の型検査を行うための手続きを表しています.
-
それぞれの get-person-xxxx は各フィールドの値を取得するための手続き($accessor$)を表し,set-porson-xxxx! は各フィールの値を設定する手続き($modifier$)を表しています.
これらの名前も自由に決めることができます.get や set を付けたり,型名(person)を付けているのは,慣習らしきことに従っているだけのことです.
-
上記の構文が示しているように,$modifier$は不要ならば省略できます.
上の例では,nameフィールドを変更することはないと考えて,その $modifier$ を省略しています.
注意
$field$-$name$ に指定した各フィールドに対して $field$-$spec$ を指定しないと構文エラーが発生します.以下では,x と y という2つのフィールドを持つレコード型に対して,各フィールドの $field$-$spec$ を指定しなかったために構文エラーが発生しています.1番目のものは x と y の両方の $field$-$spec$ を指定しなかった場合を示していて,2番目のものは y の $field$-$spec$ を指定しなかった場合を示しています.3番目のものは両方のフィールドの $field$-$spec$ を指定した場合を示しています.この場合にはエラーは発生しません.
guile> (define-record-type point (make-point x y) point?)
While compiling expression:
Syntax error:
unknown file:6:0: define-record-type: unknown field in constructor spec in subform x of (define-record-type point (make-point x y) point?)
guile> (define-record-type point (make-point x y) point? (x get-x))
While compiling expression:
Syntax error:
unknown file:7:0: define-record-type: unknown field in constructor spec in subform y of (define-record-type point (make-point x y) point? (x get-x))
guile> (define-record-type point (make-point x y) point? (x get-x) (y get-y))
各種手続きの呼び出し
$constructor$ はフィールドの値を実引数として呼び出します.
($constructor$ フィールドの値 ... )
実引数は define-record-type の中で宣言したフィールドの順番に指定します.
この呼び出しは,実引数として指定されたフィールドの値を持つレコード型のオブジェクトを返します.例えば,上記の make-person は次のように呼び出します.
(make-person "AlgoKajya" "A hobbyist programmer" "algokajya.github.io")
ここで,
- "AlgoKajya" は name フィールドの値,
- "A hobbyist" は character フィールドの値,
- "algokajya.github.io" は webpage フィールドの値
を示しています.この呼び出しは,それぞれの値からなる<person>型オブジェクトを新たに生成して返します.以下に実行例を示します.
guile> (use-modules (srfi srfi-9))
guile> (load "record-sample.scm")
...... コンパイルメッセージ ......
guile> (define algo (make-person "AlgoKajya" "A hobbyist programmer" "algokajya.github.io"))
guile> algo
$1 = #<<person> name: "AlgoKajya" character: "A hobbyist programmer" webpage: "algokajya.github.io">
$predicate$ は任意のオブジェクトを実引数として呼び出します.
($predicate$ オブジェクト)
この呼び出しは,実引数が $predicate$ が示すレコード型オブジェクトならば #t を返し,そうでなければ #f を返します.以下では,変数 algo を束縛しているレコード型オブジェクトと整数値の10に対して preson? を実行してみています.
guile> (person? algo)
$2 = #t
guile> (person? 10)
$3 = #f
$accessor$ はレコード型オブジェクトを実引数として呼び出します.
($accessor$ レコード型オブジェクト)
この呼び出しは $accessor$ が示すフィールドの値を返します.
以下の実行例では,変数 algo を束縛しているレコード型のオブジェクトから氏名,
特性,Webページのアドレスを取り出しています.
guile> (get-person-name algo)
$4 = "AlgoKajya"
guile> (get-person-character algo)
$5 = "A hobbyist programmer"
guile> (get-person-webpage algo)
$6 = "algokajya.github.io"
$modifier$ は,
レコード型のオブジェクトとフィールドの値を実引数として呼び出します.
($modifier$ レコード型オブジェクト フィールドの値)
この呼び出しは,レコード型オブジェクト内のフィールドの値を変更するとともに,
第2引数のフィールドの値を返します.以下の実行例では,
変数 algo を束縛しているレコード型のオブジェクト内の character フィールドの値を変更しています.
guile> (set-person-character! algo "An unskilled Schemer")
$7 = "An unskilled Schemer"
guile> algo
$8 = #<<person> name: "AlgoKajya" character: "An unskilled Schemer" webpage: "algokajya.github.io">
レコード型の内部定義
SRFI-9 はレコード型の内部定義(lambda 式や let 式などの本体における定義)を禁止しています.以下は SRFI-9 からの引用です.
Record-type definitions may only occur at top-level (there are two possible semantics for `internal' record-type definitions, generative and nongenerative, and no consensus as to which is better).
筆者が誤解していなければ,「generative」なsemanticsとは,
レコード型の内部定義を実行時に行うことを意味していて,
同じ型名の内部定義が2つの異なる本体内にあるとき,
それらは別個のレコード型として定義されることになります.
一方,「nongenerative」なsemanticsとは,
レコード型の内部定義をコンパイル時に行うことを意味していて,
同じ型名の内部定義が2つの異なる本体内にあっても同じレコード型として定義されることになります(この場合,たぶん,両者の形式が異なると構文エラーになるのだろうと想像します).
Guileはレコード型の内部定義が可能です.
「generative」なsematicsを採用しているようです.
以下のプログラムはこれら2点を確認するためのものです.
;; record.scm
(use-modules (srfi srfi-9))
(define (f x y)
(define-record-type <point>
(make-point x y)
point?
(x get-x)
(y get-y))
(let ((point (make-point x y)))
(format #t "(In f) point:~A\n" point)
point))
(define (g point)
(define-record-type <point>
(make-point x y)
point?
(x get-x)
(y get-y))
(if (point? point)
(format #t "(In g) point:~A IS a g's point.\n" point)
(format #t "(In g) point:~A IS NOT a g's point.\n" point)))
このプログラムでは,2つの手続き f と g の内部で形式的に等しいレコード型を定義していて,手続き f で生成したレコードオブジェクトが g のレコードオブジェクトとして同定されるか否かを試そうとしています.下記の実行結果を見ると,両方のレコード型は異なるものとして処理されているようです.
$ guile -l record-sample.scm
...... コンパイルメッセージや起動メッセージ ......
guile> (g (f 1.0 2.0))
(In f) point:#<<point> x: 1.0 y: 2.0>
(In g) point:#<<point> x: 1.0 y: 2.0> IS NOT a g's point.
$1 = #t
Guileによる拡張機能
モジュールのロード
下記の手続きを利用するためには,
以下に示すように (srfi srfi-9 gnu) モジュールをロードする必要があります.
(use-modules (srfi srfi-9 gnu))
レコードオブジェクトの整形出力
syntax:
(set-record-type-printer! $type$-$name$ $proc$)
|
$type$-$name$ |
レコード型の型名 |
$proc$ |
2引数の手続き.第1引数はレコードオブジェクトが渡され,第2引数は出力ポートが渡されます. |
返り値 |
$proc$ をそのまま返します. |
上記の $proc$ は,第1引数に渡されたレコードオブジェクトの内容を第2引数の出力ポートに出力する手続きを指定します.
このとき,set-record-type-printer! は,$type$-$name$型のオブジェクトを出力するときの出力手続きを $proc$ に設定します.この設定のあと,$type$-$name$型のオブジェクトを出力すると $proc$ を使って出力するようになります.
具体例
;; record-sample.scm
(use-modules (srfi srfi-9))
(use-modules (srfi srfi-9 gnu))
(define-record-type <person>
(make-person name character webpage)
person?
(name get-person-name set-person-name!)
(character get-person-character set-person-character!)
(webpage get-person-webpage set-person-webpage!))
(define (set-person-printer)
(set-record-type-printer!
<person>
(lambda (person port)
(format port "<person>\n Name: ~S\n Character: ~S\n WebPage: ~S\n"
(get-person-name person)
(get-person-character person)
(get-person-webpage person)))))
以下の
$1 は上記の set-person-printer を実行する前の表示結果を示しています.
これはシステム標準の形式で表示されています.
一方,
$3 は set-person-printer を実行したあとの表示結果を示しています.
これは上で指定した lambda 式を使って表示しています.
ちなみに,display や wirte を使って <person> 型オブジェクトを表示しても上記の lambda 式が使われます.
guile> (load "record-sample.scm")
guile> (make-person "AlgoKajya" "A hobbyist programmer" "algokajya.github.io")
$1 = #<<person> name: "AlgoKajya" character: "A hobbyist programmer" webpage: "algokajya.github.io">
guile> (set-person-printer)
$2 = #<procedure 7fe2f8bc34b8 at /org/algo/guile-test/datatype/record-sample.scm:15:3 (person port)>
guile> (make-person "AlgoKajya" "A hobbyist programmer" "algokajya.github.io")
$3 = <person>
Name: "AlgoKajya"
Character: "A hobbyist programmer"
WebPage: "algokajya.github.io"
guile> (display $1)
<person>
Name: "AlgoKajya"
Character: "A hobbyist programmer"
WebPage: "algokajya.github.io"
guile> (write $1)
<person>
Name: "AlgoKajya"
Character: "A hobbyist programmer"
WebPage: "algokajya.github.io"
その他
Guile は immutable なレコード型を定義する仕組みも提供しています.
その詳細は
Guile[6.6.16 SRFI-9 Records]
を参照して下さい.