レコードとライブラリでアクセス制御

# 導入

「困難を分割せよ」というのは元々はデカルトの言葉だそうですが、プログラミングをする上でも基礎になる考え方です。 困難を困難たらしめる困難とは何か、どんどん分割していけばいずれ解は自明なものになります。 ある問題を片付けるプログラムを作るとき、そのプログラムに必要な部品をどんどん作っていけばよいのです。

一方で、プログラムの部品 (関数だったりクラスだったり、それらをまとめたライブラリだったり) が必要な機能を満たしていても、それの使い方を使い手がいつもきちんと詳細まで把握しているとは限りません。 うっかりとつまらない間違いをしてしまうこともあるでしょう。

つまり、ある部品によって出来ることを知っておくのと同様にやってはいけないことを把握することもまた重要で、しかしそれは難しいという現実もあるということです。 プログラムの部品を作るにあたっては、

- 使ってはいけない箇所で使えないように
- もし使ってしまったら素早く発覚するように
- 間違った使い方をしたときの影響範囲が小さくなるように

ということに配慮した設計が必要であると考えられます。

自分自身で使う部品については、作っている時点ではそれをどう使うかはあまりにも自明に思えるが故に細かいことを考えなくなりがちですが、翌日の、翌月の、翌年の自分は他人のようなものです。

さて、 Scheme を取り上げた文章は<ruby>今日<rp>(</rp><rt>こんにち</rt><rp>)</rp></ruby>でもしばしば R5RS を前提としたものがあり、複数の値をまとめるオブジェクトとしてはリストとベクタ (たまにクロージャに閉じ込めようとするものもありますが) だけでなんとかしようとしている場面を少なからず見ます。 この記事では R7RS においてはレコードやライブラリがそのような__使ってはいけない場合__を制御するにあたって役にたつということを紹介するものです。

R6RS にもレコードやベクタはありますが R7RS とは異なる構文であることに注意してください。 今回は R7RS の仕様を基準に説明します。

# アクセス制御のない場合

たとえば、ごく簡単な事例として人物を表現するデータ構造を考えてみます。

<ruby>更<rp>(</rp><rt>さら</rt><rp>)</rp></ruby>に以下のような条件を付けることにします。

- 人物データは名前と年齢だけから成る
- 名前が変更されることはない
- 年齢は 1 ずつ増える (減ることはない)

扱うデータはふたつですからペアで表現すれば充分でしょう。

```scheme
(define (make-person name age)
  (cons name age))

(define (person-name person)
  (car person))

(define (person-age person)
  (cdr person))

(define (person-grow! person)
  (set-cdr! person (+ (cdr person) 1)))
```

これで必要とされる機能は足りていますが、これらの手続きを通さずにオブジェクトを書き換えることは出来てしまいますので、条件を安易に壊してしまえます。

```scheme
(set-cdr! person (- (cdr person) 1))
```

データを直接書き換えればどうとでも出来てしまうのです。 「このオブジェクトは人物を表している」ということにしたところで、そこに有るのはただのペアです。

# レコードを用いる

ではレコードを導入して同様の機能を作ってみます。

```scheme
(import (scheme base)
        (scheme write))

(define-record-type person (make-person name age) person?
  (name person-name)
  (age person-age person-age-set!))

(define (person-grow! obj)
  (person-age-set! obj (+ 1 (person-age obj))))

(define (person-display obj)
  (display "#<person name='")
  (display (person-name obj))
  (display "' age=")
  (display (person-age obj))
  (display "'>"))

;; 実行例
(let ((man (make-person "John" 16)))
  (person-grow! man)
  (person-display man))
```

ここでは `define-record-type` は `person` という型を定義しています。 この型は `name` と `age` というふたつの要素から成るということ、また、それらの要素を取り出したり書き換えたりする手続きも同時に定義されます。

定義をよく見ればわかりますが、 `age` の横には `person-age` というアクセサと `person-age-set!` というモディファイア (書換え用の手続き) が書かれていて、 `name` の横には `person-name` というアクセサしか書かれていません。 `person` 型の `name` を書き換える機能がないのですから「名前を書き換えることが出来ない」という制約はこれで表現できたことになります。

しかし、まだ年齢に関する制約を表現できていません。 アクセサとモディファイアを隠したいですね。 C++ で言うところの `private` のようなものがあればよいのですが。

# ライブラリ

R7RS にはライブラリという機能があります。 機能群をまとまりごとに入れることが出来て、どの名前を公開するのかを制御することが出来ます。 見せたくないものは公開しなければ外からは見えません。

さっそく上述の例をライブラリにまとめてみます。

```scheme:person.sld
(define-library (person)
  (export make-person
          person?
          person-name
          person-age
          person-grow!
          person-display)
  (import (scheme base)
          (scheme write))

  (begin
    (define-record-type person (make-person name age) person?
      (name person-name)
      (age person-age person-age-set!))

    (define (person-grow! obj)
      (person-age-set! obj (+ 1 (person-age obj))))

    (define (person-display obj)
      (display "#<person name='")
      (display (person-name obj))
      (display "' age=")
      (display (person-age obj))
      (display "'>"))
    ))
```

`export` 節で指定している名前のみが `(person)` ライブラリから公開されている名前です。 `(person)` ライブラリを `import` したプログラム (またはライブラリ) は `(person)` ライブラリから公開されている名前を使えるようになります。

```scheme:sample.scm
(import (scheme base)
        (person))

;; 実行例
(let ((man (make-person "John" 16)))
  (person-age-set! man 1) ;; ← エラー：そんな手続きはない!
  (person-grow! man)
  (person-display man))
```

ここでは、 `person` レコードの要素を書き換える手続きを使えるのは `(person)` ライブラリの中だけに限定できました。 `export` しない名前は C++ でいうところの `private` みたいなものです。

# まとめ

これだけだとまだ制約を潜り抜けることは不可能ではないのですが、レコードとライブラリによるアクセス制御の概要は示せたと思います。 馬鹿げた失敗を防ぎましょう。