set! できない、 set! したい

プログラミング言語 Scheme におけるライブラリと代入の関係について述べます。 ここでは R6RS の仕様に基づいて書きますので R7RS では異なる部分もあることには留意してください。

# 識別子の `import` と `export`

各機能はライブラリからインポートすることで利用可能になります。 インポート可能な識別子はライブラリの側でエクスポートしている識別子です。

```scheme:library-A.sls
#!r6rs
(library (library-A)
  (export foo) ; ← foo をエクスポート
  (import (rnrs))

  (define foo 'abc)
   ; ↓ この定義はエクスポートしていないのでライブラリの外から使えない
  (define bar 'def)
  )
```

```scheme:sample1.scm
#!r6rs
(import (rnrs)
        (library-A))

(display foo)
; (display bar) ← bar を使おうとしたらエラーになる
```

# `set!` できない

ライブラリからインポートした変数には大きな制限があります。 `set!` できないということです。

```scheme:sample2.scm
#!r6rs
(import (rnrs)
        (library-A))

;; エラーになる!
(set! foo "abc")
```

# 手続き経由での `set!`

インポートした変数に `set!` できないのであれば、あらかじめ代入のための手続きをライブラリに用意すればよいというのは自然な発想でしょう。

```scheme:library-B.sls
#!r6rs
(library (library-B)
  (export foo foo-set!)
  (import (rnrs))

  (define foo 'abc)
  (define (foo-set! obj) (set! foo obj))
  )
```

```scheme:sample3.scm
#!r6rs
(import (rnrs)
        (library-B))

(foo-set! "abc")
(display foo)
```

しかし、これもまたエラーです。 ライブラリの中で `set!` されている識別子はエクスポートできません。 もちろん、エクスポートできないのですから、インポートも出来ません。

ただ、この制限については実際の処理系では `set!` できるように緩和しているものもあります。

# マクロ経由での `set!`

では `set!` するのが手続きではなくマクロならどうでかというとやっぱりエラーです。

```scheme:library-C.sls
#!r6rs
(library (library-C)
  (export foo foo-set!)
  (import (rnrs))

  (define foo 'abc)
  (define-syntax foo-set!
    (syntax-rules ()
      ((_ obj)
       (set! foo obj))))
  )
```

```scheme:sample4.scm
#!r6rs
(import (rnrs)
        (library-C))

(foo-set! "abc")
(display foo)
```

結局のところ、 (R6RS の範囲内では) どうやってもエクスポート (インポート) した変数には `set!` できません。

# `set!` できない利点

`set!` される (可能性がある) 変数をエクスポートできないということは、逆説的に言えばインポートした変数の束縛が変更されることは無いことを意味します。 

最終的なプログラムを構成するライブラリ全てを見なくても各ライブラリ単体を見ればある変数が `set!` される (束縛が変更される) かどうかを処理系は知ることが出来るので、 `set!` される可能性がない変数は全てインライン化してもよく、性能に寄与します。

# `set!` したいなら

変数に `set!` したいならば、変数そのもののエクスポートは諦めるしかありません。 代入する操作だけでなく値を参照する操作も手続きを介することになります。 いわゆるゲッタ、セッタのような手続きを作ればよいです。

```scheme:library-D
#!r6rs
(library (library-D)
  (export foo-set! foo-ref)
  (import (rnrs))

  (define foo 'abc)
  (define (foo-set! obj) (set! foo obj))
  (define (foo-ref) foo)
  )
```

```scheme:sample5.scm
#!r6rs
(import (rnrs)
        (library-D))

(foo-set! "abc")
(display (foo-ref))
```

# それでも変数に `set!` したい

それでも、どうしてもインポートした変数に `set!` したいのであれば、上述のゲッタ、セッタをマクロで覆い隠してしまうという方法は考えられます。

```scheme:settable.sls
#!r6rs
(library (settable)
  (export define-settable)
  (import (rnrs))

  (define-record-type (box make-box box?)
    (fields
     (mutable value unbox set-box!)))

  (define-syntax define-settable
    (syntax-rules ()
      ((_ id obj)
       (begin
         (define tmp (make-box obj))
         (define-syntax id
           (identifier-syntax (_ (unbox tmp))
                              ((set! _ f)
                               (set-box! tmp f))))))))
  )
```

```scheme:library-E.sls
#!r6rs
(library (library-E)
  (export foo)
  (import (rnrs) (settable))
  
  (define-settable foo 'abc)
)
```

```scheme:sample6.scm
#!r6rs
(import (rnrs)
        (library-E))

(write foo)
(newline)
(set! foo "abc")
(write foo)
```

これは以前の記事「[最適化技法としてのマクロ](https://qiita.com/SaitoAtsushi/items/981c9aebe5ec633fea3e)」でも取り上げた識別子マクロを利用したものです。 このサンプルコード中の `foo` は見かけ上は変数のように使えますが、実は識別子マクロなのです。 簡単な識別子マクロを作るときは `identifier-syntax` を使うと、以前に使った `make-variable-transformer` よりも楽です。