皆様、初めまして鈴木です。 別の ML から話題を移動させたこともあるので、先に経緯説明をしてしまい ます。そのため、本題に入る前にまつもとさんの説明書きに一通りコメントし てしまうことにします。ちょっと長くなってしまいますがご容赦下さい。 <1044439667.250527.24749.nullmailer / picachu.netlab.jp>の記事において matz / ruby-lang.orgさんは書きました。 # 返答が遅くなってしまい、申し訳ありません。 > 元々はnilが偽なのは良くないのでは、という話です(末尾参照)。 元々の元々の話は ruby-talk [63065] に始まるスコープ規則変更 (同 [63668]参照) に関するスレッドで、未初期化値を nil で代用するのはまずく ないか、というものです。 まず、Klemme 氏の書いた例を御覧下さい。 > def blocktest(*args) > args.each do |arg| > foo = "yes" > end > > p foo > end > > blocktest(1, 2, 3) > blocktest() 新しいスコープ規則によれば、foo は each に与えらたブロックの局所変数 ではなく、blocktest に対して局所的な変数です。そのため、これら二つの呼 出し例はエラーになりません。 問題は、args が空だった場合に foo はどんな値で初期化されるべきか、で す。まつもとさんは nil だと言っています。で、私がそれはまずくないかと 主張したわけです。 > Schemeでは「未初期化の変数値」である#<undef>(Gaucheの場合)は > 偽ではなく真です。だから、Rubyでもそうあるべきでは、という主 > 張なのだと理解しました。 「定義されているが初期化されていない」状態を表す first class な値を ブール代数から一旦切り離すべきだ、と考えます。その値は条件判断の文脈で 真として振舞うべきですが、それは単に「false (と nil) ではないオブジェ クト」なので、結果的にそうなるというだけのことです。 詳細は後述しますが、少なくとも「Scheme がそうだから」ではありません。 Scheme は、単なる説明する上で都合の良い道具に過ぎません。 # いや、Scheme のそういう原理主義的なところは好きなんですけどね。 > * nilを真にするというのは(Schemeでなく)Lispの伝統に反する。 > * 互換性で大問題が起きる 全くその通りだと思います。Scheme ですら nil は偽値として振舞います。 talk では (英作文能力の無さゆえに) 書けませんでしたが、私も nil を真に する変更は非現実的だと思っています。 > * nilは3値論理(真、偽、未定義)を表現するのに都合が良い 三値論理を表現するのに都合が良いことには同意します。 しかし、nil は「未定義」でなく「無効」と考えるべきだと思います。 > nilを唯一の偽の値にすることもRubyについてはありえないかなあと考えて > います。 nil を唯一の偽値にしろ、と主張するつもりはありません。 > とはいうものの、「#<undef>は偽であってはならない理由」という > のに興味はありますから(R5RSにも理由の説明はないし)、ぜひとも > 聞きたいなあと思っているのです。 R5RS の著者がどう考えているかは知りません。が、少なくとも R5RS に理 由の説明が無い理由は明白ですね。R5RS にとっては「処理系依存の未規定値」 に過ぎないわけですから。 ---- さて、ここから本題 (1) です。 前述の Klemmer 氏の例の foo という変数は、blocktest の引数が空であっ た場合でも (新スコープルール案下では) 定義されます。しかし、その場合明 示的な代入による初期化は行われません。そして、この場合でも foo には何 らかの値で初期化されます。前述のように、まつもとさんは nil を想定して いるようです。nil は元来「slot が未初期化であることを表す特別な値」だ からだそうです。 しかし、Ruby の nil は本当に「未初期化」を表しているんでしょうか? そ れがそもそもの疑問点です。私は nil を「無効な値」の代表値と考えるべき だと主張します。実際、Ruby での nil はそのような目的で使われています。 たとえば、「nil を代入する」とか「nil で初期化する」という表現は文章 としても実際のコード (e.g. a_var = nil) でも妥当なものです。 しかし、たとえば talk [63595] に見られる > ruby avoids this by using the special nil object as a place you > point to - with semantics 'this variable has not been pointed at a > real-object', even though nil itself is an object. このような主張には問題があります。このような考え方は reasonable なのか もしれませんが、論理的根拠がありません。 nil を変数に代入することは、その変数が「(ある文脈において) 無効とみ なされるべきオブジェクトを指している」という状態にすることです。しかし、 変数を未初期化状態に戻すことではありません。なぜなら、「無効である」と いう概念と「初期化されていない」という概念は全く異なる次元のものだから です。 たとえば、アンケートの結果を三値論理で得るようなプログラムを書くとし ます。この場合、アンケート結果を nil: 未回答 true: yes false: no とみなすようにプログラムを作るのは、まあ妥当と言えるでしょう。ここで、 an_answer = answers[0] というコードを考えてみて下さい。配列 answers にはアンケートの回答列が (0 origin で) 納められています。もしも設問1の回答 (つまり、answers[0]) が未回答だった場合、上記コードは an_answer という変数を「未初期化状態 にしている」のでしょうか。違いますね。あくまで「アンケートにとって無効 な回答」という意味のある値を代入しているだけです。 このように、任意の文脈で無制限に変数に代入できる値をして「未初期化状 態を表す値」と定義するのは無理があります。 ただし、現在のスコープルールでは、未初期化な変数への参照は常にエラー を引き起こします。意味論的に「定義されているが未初期化」という状態を認 めないものだからです。このような意味論下では、上記のような「無効とみな されるべきオブジェクトを指している」ことを「未初期化状態」と定義するこ とは可能です。…美しいとは思いませんが、とにかく可能です。 しかし、新提案のスコープルールは、「定義されているが未初期化」という 状態を容認する意味論に即したものです。そこに nil を旧来の定義に即した 未初期化値として持ち込むのは、double meaning です。 # 懺悔させてもらえば、talk では nil => false の double meaning しか主 # 張していないような形になっちゃっています。そのつもりは無かったのです # が、後から読むと、そのようにしか見えませんね。これはひとえに私の英語 # 力と文章構成力の無さゆえのものです。ごめんなさい。 実害もあります。nil で明示的に初期化したのか本当に未初期化なのかの区 別をしなければならないときのために、特別なメソッドを NilClass に定義し なければならなくなります。場合によっては、下記のような冗長なコードが必 要になるかもしれません。 case a_var when nil if var.non_initialized? then ... # 未初期化時の処理 else ... # 有効な nil という値であった場合の処理 end when ... ... end nil と未初期化値が別物であれば、以下のように素直に書けるにも関わらず、 です。 case a_var when #<non-initialized> ... when nil ... ... end そのため、#<non-initialized> (仮称) を新設し、変数の定義時に代入が無 かった場合に使うようにすべきだ、と私は考えました。 この #<non-initialized> に求められる主な性質は以下の通りです。 (*) first class object (*) a = #<non-initialized> のようなコードは禁止 (*1) (*) ==, !=, defined?, non_initialized? (仮称) などに対応 そして、nil から "a special value for uninitialized slot" という意味は 剥奪するべきだと考えます。その場合の nil の意味は (*) 偽値として振舞うオブジェクトの一つ (*) ただし false とは not identical という、ただそれだけのものになります。nil はどこからでも見えるオブジェ クトなので、結果的に (*) 無効値の代表値 として振舞うことは可能でしょう。 本題 (1) をまとめます。 (*) 「無効」と「未初期化」は別概念 (*) nil は無効値の代表値であるべき (*) 未初期化値を表すオブジェクトは新設すべき (**) first class object であるべき (**) 未初期化値の明示的代入には制限を与えるべき (**) ==, !=, defined?, non_initialized? 等に対応するべき (*1) 何らかの tricky な方法で未初期化値を生成して無理矢理変数に代入す ることを禁止するべきではありません。それはプログラマの責任領域です。 ----- さて、本題 (2) です。 これは無効値や未初期化値が偽値なのはアレじゃないか? という議論です。 ただし、私は nil in Ruby を真値化せよという主張は既に取り下げている ことを明言しておきます。にも関わらずここで議論するのは、nil に代わる無 効値、たとえば invalid を将来新設する可能性はあるかもしれないからです。 さらに、未初期化値が真値であるべきか否かという議論も要請されているから でもあります。 私は、無効値や未初期化値をブール代数という概念から切り離すべきだと考 えます。もちろん無効値や未初期化値を表すオブジェクトを二値論理の文脈で 評価すれば真値として振舞うでしょうが、それは Ruby が false (と nil) 以 外のオブジェクトを真値として扱った結果に過ぎません。無効値や未初期化値 を偽値として振舞わせることは、明示的にこれらの値 (を表すオブジェクト) に偽値という意味を持たせることです。つまり double meaning. なぜ切り離すべきかと言うと、「無効」や「未初期化」という値 (あるいは 状態) の意味は文脈に属するもの、つまりプログラマに属するものだからです。 これらの値を偽値として振舞わせることは、それらの値に「既定の意味」を与 えることになります。 これは一種の制限だと考えられます。 たとえば talk [63746] に書いた以下の例を御覧下さい。 | def questionnaire | print_somewhat_Q | str = gets | parse_answer(str) if str | end このメソッドはアンケートを採るためのものです。このメソッドは無効値とし て nil を返すか、そうでなければ何らかの「有効な値」を返すことが期待さ れます。nil が返って来るのは、空入力を得た場合か、あるいは入力を parse_answer が無効なものとみなした場合です。 このコードを書いたプログラマが、既定回答を「偽値」と定めていた場合、 結果が nil かどうかの判断用のコードは省略できます。しかし、既定回答を 用意しなかったり真値と設定していた場合には省略できません。これは非対称 的だし、妙なコーディングスタイル (「既定回答を偽値にするようにコーディ ングするべき」等) に誘導してしまう可能性もあります。 無効値や未初期化値から切り離すべきなのは、ブール代数の概念に限りませ ん。同様の理由により、その他全ての意味を持った概念から切り離すべきです。 どうしても無効値に特定の意味を持たせたいのならば、たとえば NilClass に to_str メソッドを定義して "" を返させるような実装を敷衍するべきだと考 えます。未初期化値については、そのような実装すら止めた方が良いと考えま す。 # nil.to_str => "" ってのは、ruby 系 ML のどれかで既出だったような… とはいえ、これらの主張は「〜であるのは望ましくない」という程度のもの で、「〜であってはならない」という程強いものではないかもしれません。 本題 (2) をまとめます。 (*) 無効値や未初期化値を他の概念から切り離した方が良い (**) 切り離したくないのであれば、to_str の類で明に変換 ---- 1002.(ichimal) 鈴木信吾 電通大 EC 情報工学専攻, D1 英語でもこれくらい書けなきゃマズいんだよな、俺 (^^;