最初の質問ですが、Scheme にはグローバルに関する単一の規則はありません。*foo*
(上のコメントで述べたように、CL の同じ命名規則とは異なる理由で)使用する人もいれば、大文字を使用する人もいますが(大文字と小文字を区別するスキームで)、どちらも慣習と見なされるほど一般的ではありません。
2 番目の質問は、より微妙です。はい、単一のグローバル名前空間を持つことには落とし穴があります。これらの落とし穴は、JavascriptやCommon Lispの落とし穴と同じです。ランダムなコードをロードすると、他のコードの意味が変わる可能性があるという事実です。つまり、2 つのライブラリをロードし、両方で何らかのfoo
グローバル関数を定義したい場合は、これらのライブラリのうち、他のライブラリを壊します。これは新しい問題ではなく、さまざまな対処方法があります。
最も明白なことは、パブリック インターフェイスの一部ではないグローバルを使用しないようにすることです。したがって、たとえば、代わりに
(define helper ...)
(define foo ...)
あなたが書く
(define foo
(let ()
(define helper ...)
(define foo ...)
foo))
インターフェイスに複数の関数がある場合は、同様のことを行います (たとえば、Racketdefine-values
を使用したり、関数のリストを使用したりします)。これには二重の保護があることに注意してください。helper
グローバル名前空間を汚染しないため、他のコードを壊さないなどの内部的なものと、 を定義する他のコードがhelper
このコードを壊すことはありません。これにはfoo
、再帰的に自分自身を呼び出す場合も含まれます。
関連するハックは、高速にしたい場合にそのようなコードを作成することです。最初のバージョンではfoo
、バインドが後で変更される可能性があるため、Scheme コンパイラは高速ループにコンパイルできませんが、2 番目のバージョンでは、そのような最適化が可能です。
モジュール システムがない場合の防御的プログラミングのもう 1 つの形式は、コードにとって重要な値を取得することです。たとえば、次のようにできます。
(define foo
(let ([+ +] [- -] [* *] [/ /] ... more ...)
(define helper ...)
(define foo ...)
foo))
そして今、コードは算術演算への独自の不変のバインディングを持っています。これにより、将来これらの演算が変更されないように保護され、このコードでの算術演算の安全な最適化が可能になります。
(function() { ... })()
これらはすべて、Javascriptでの の一般的な使用法に似た、貧乏人のモジュール システムの慣例のようなものです。しかしもちろん、適切なモジュール システムを使用すると、これがより便利になり、適切に動作します。新しいJavascriptバージョンと新しいSchemeバージョンの両方が、ある種のモジュールシステムでこの問題に直面しており、Schemeでは、モジュールのみを使用load
し、信頼できないハックとして回避する傾向があります.
(また、Common Lisp について言及したので、他のファイルからグローバル変数や関数を上書きしている可能性があるため、同じ問題があることがわかります。これに対処する方法は、CL のパッケージ システムを使用することです。モジュールシステム. モジュールシステムが弱い理由は, 別の名前空間の利便性を提供することです.つまり、コンパイラは、バインディングが変更されないことをまだ想定できません)。