psyntax-moshでloadをエミュレートする裏技
moshはScheme処理系としては珍しくloadに相当する操作を持っていない。しかし、psyntax的にはloadが存在するので不正なテクニックを駆使することで利用できる。
nmoshのビルド課程では、moshのletの問題を回避するためにこのテクニックを使っていて、実際うまく機能している。
moshのload機能を使うためには、秘密のライブラリ(psyntax system $all)からシンボルinteraction-environmentをimportする。interaction-environmentはR5RS的な対話環境を指し、この環境に対して(rnrs eval)のevalを繰り返し呼ぶことでloadをエミュレートすることができる。
これは、感覚としてはREPLをエミュレーションしていることに近い。というか、moshのREPLは実際にそのように実装されている。
ちなみにnmoshでは同じことはできない。R6RSの規格では、evalは(例えば)defineによる環境の拡張をエラーにしなければならないとされており、nmoshはそのような式はどのような環境であってもエラーにする。
psyntax-moshではevalの動作は環境によって異なり、interaction-environmentをに対するevalはそもそもR6RS的な動作をしない。要するに、
(import (rnrs) (rnrs eval) (only (psyntax system $all) interaction-environment)) ;(define m (environment '(rnrs))) ; <= A (define m (interaction-environment)) ; <= B (define (e1 l) (eval l m)) (e1 '(define a 10)) (e1 '(define a 20)) (e1 '(display a))
このようなスクリプトがあったとして、Bの環境を使うとこのスクリプトは期待通りに動作するが、Aの環境を使うとsyntax errorとなる。
loadの利用
loadやincludeは処理系を実装する方からすると悩みのタネで、今のところnmoshはloadやincludeされたソースコードの更新を検出しないので何か間違ったことが起る可能性がある。loadやevalが別のライブラリに分離されていることからも、これらを使わず、別の方法を用いて実現できないかは常に検討の価値があると考えていただきたい。。
もちろん、プログラムの開発中においてはこれらは有用なので、loadを使っても正常にキャッシュをハンドリングすることは検討している。(しかし、まだ実装されていない)
nmoshでloadを使うときは、”loadで使うプログラムもloadでロードする"ことを心がければ問題を回避できる。
nmoshのプログラムには"loadモード"と"expandモード"の2種類あり、
(import (rnrs)) (define a 10) (define a 20) (display a)
のようなスクリプトは、loadモードでしか動作しない。expandモードでは、aに対する多重定義としてエラーになる。loadモードは定義を多重に行うことができ、後から定義されたものが優先される(後からの定義は単にset!のように働く)。
REPLもloadモードであるため、例えばライブラリを複数回loadすることで、一度importしたライブラリを更新できるなど、開発に便利な特徴を多く備える。
そんな便利なloadモードだけど、loadモード中のシンボルは実行されるまで意味が確定されないので、プログラムをキャッシュできないという大きなデメリットが存在する。
ちなみに、psyntax-moshと、0.2.0までのmoshのR5RSモードにはexpandモードに相当する解釈しか存在しなかった。nmoshの移植の過程で、moshのR5RSモードはloadモードの解釈としたので、moshのR5RSモードを活用したプログラムを書くのはちょっと難しくなった。
そして、R6RSの標準も、expandモードしか持っていない。逆に言えば、この特徴を実現するためにR6RSはREPLやinteraction-environmentのようなものを標準から外したのだろう。