28-chainedpkg-a

「28-chainedpkg-a」の編集履歴(バックアップ)一覧に戻る
28-chainedpkg-a」を以下のとおり復元します。
* チェインされたパッケージ節 (chained_package_clauses)
#center(){Martin_Odersky}
#center(){2010 年 9 月 7 日}

もしあなたが既にいくつかの大規模な Scala 2.8 プログラムを見たことがあるなら、ソースファイルが時々、次のような複数のパッケージ節の列から始まるのを見て当惑したかもしれません。:

    package org.myproject
    package tests
    ...

この意味は何でしょうか? 実はこれは、次のような 2 つのネストしたパッケージ節を書くこととまったく同じです。:

    package org.myproject {
      package tests {
        ...
      }
    }

ネストしたパッケージ節は全く正しいのですが、それほど頻繁には Scala で使われません。構文が単一のあるいはチェインされたパッケージ節よりもずっとヘビーだからです。その利点は、パッケージのネスト構造を明示するということです。しかし Scala 2.8 以前は、たいていのプログラマーは、次のような単一のパッケージ節を選んでいました。:

    package org.myproject.tests
    ...

- &link_anchor(what){変わったこと}
- &link_anchor(why){なぜ変えたか?}
- &link_anchor(change){変更のまとめ}
- &link_anchor(migrating){新スキームへの移行}
- &link_anchor(force){慣習の力}
- &link_anchor(version){バージョン番号}
- &link_anchor(acknow){謝辞}
- &link_anchor(contents){目次}
- &link_anchor(navigation){ナビゲーション}

#center(){==========================================}

&aname(what,option=nolink){ }
** 変わったこと (What Has Changed)

以前の単一のパッケージ節

    package org.myproject.tests
    ...

は、次の 2 段にネストする節に等価でした。

    package org.myproject {
      package tests {
        ...
      }
    }

そして両方とも、次の 3 段にネストする節に等価でした。

    package org {
      package myproject {
        package tests {
          ...
        }
      }
    }

それらはすべて同じ意味でした。 Scala 2.8 で変わりました。

違いは、パッケージ節

    package org.myproject.tests

は、今度は、スコープに org.myproject.tests のメンバーだけを入れ、外側の 2 つのパッケージ org.myproject、org のメンバーを入れないことです。もしスコープに test と org 両方のメンバーを入れたいなら、上記のように 2 つのネストしたパッケージ節か、あるいはチェインされた等価物を使う必要があります。

    package org.myproject // myproject メンバーは可視だが、org members は不可視
    package tests         // test メンバーは可視

#center(){==========================================}

&aname(why,option=nolink){ }
** なぜ変えたか? (Why the Change?)

Scala と Java では、パッケージの意味はいくつかの点で異なります。Java には 4 つの異なる名前解決メカニズムがあります。:メソッド、フィールド、クラスとパッケージのそれぞれにあります。パッケージについては、パッケージ名の解決を常にルートから始めます。ですから、パッケージ名は常に絶対指定であり、決して相対ではありません。Scala は、よりシンプルで規則的です。コンパイラが検索する名前の種類に関係なく、単純名の解決にはただ 1 つの方法しかありません。

When it encounters a simple_name x, the compiler will look from the outwards until it finds a declaration of x and that is the declaration that's chosen.
コンパイラは単純名 x に遭遇すると、x の宣言を外側にあるものから検索し、あればその宣言が選ばれます。

これはブロック構造スコープの普通の考え方です。その結果、ブロック中の宣言は何らかのブロック中の宣言を隠します。

他のすべてのエンティティと同様、Scala ではパッケージをネストできます。これにより、しばしばより簡潔なコードが書けます。例えば、パッケージ org.myproject.tests 内でアクセスしたい、もう 1 つのパッケージ org.myproject.web があるとします。それは、次のように書くだけです。

    import web._ 

パス全体をインポートする必要はありません。

    import org.myprojec.web._ 

パッケージ名が長くなるにつれ、このスタイルで乱雑度をかなり減らせます。ですから、このネスト規約は役に立ちます。また、Java のようなパッケージ種に応じた特定の規則が必要ないので、非常に明解でもあります。

残念なことに実際には、ネスト規約が、歓迎されない副作用を持つことが分かります。人は時々、ルートパッケージと同じ名前のネストしたパッケージに遭遇します。例えば、彼はクラスパス上のどこかでパッケージ org.java を持っているかもしれません。バージョン 2.7 までの Scala オリジナルのネスト規約では、パッケージ org.myproject 内部では、名前 java はルートパッケージ java ではなく、org.java へ解決されると規定していました。これは、org ドメインが、アクセスしたい(ルートの java を隠す) Java パッケージを含むことを知りさえしないユーザーの間に混乱と驚きをもたらしました。

Worse, the availability of the nested java would depend on how the classpath was defined, what jars were on it etc.
さらに悪いことに、ネストした java の利用可能性は、クラスパスの定義の仕方、何の jars か、その他に依存します。

クラスパス上に何があるかなど、いつもわかっている人がいるでしょうか?

もちろん、逃げ道はありました。Scala には、他のすべてのトップレベルのパッケージを含む「ルート」パッケージに対する名前があります。それは _root_ と名付けられています。ですから、標準的な Java パッケージを _root_.java として参照できます。しかし、防御的であるために、_root_ を用いるすべてのトップレベルパッケージへの参照を修飾名を付けて、ネストしたパッケージに似た名前によって偶然隠されないことを確実にすべきです。

手短に言うと、パッケージ節を体系化する最も簡単で最も直接的な方法は脆弱である、という状況に直面しました。クラスパス上で偶然終わるネストしたパッケージがトップレベルのパッケージを隠すことがあるからです。問題を避けるためのより防御的な手法は、いくぶん冗長で見苦しいものでした。

#center(){==========================================}


&aname(change,option=nolink){ }
** 変更のまとめ (Change Summary)

問題の解決は、次のような修飾されたパッケージ節の意味を変えることでした。

    package org.myproject.tests

これは、今は、スコープに tests のメンバーだけを入れ、それを含む外側の 2 つのパッケージのメンバーを入れません。これは Scala パッケージのネスト方法を何も変えません。パッケージ test は依然、パッケージ org.myproject のメンバーです。唯一変わったのは、上のような修飾されたパッケージ節によって見えるスコープ範囲です。

変更の第 2 は、ネストしたパッケージに代わる、よりコンパクトな表現としてチェインされたパッケージ節という新しい構文を使えることです。もしコード中でorg.myproject と org.myproject.tests の両方のメンバーにアクセスしたいなら、2 つのパッケージ節を使うことができます。:

    package org.myproject
    package tests

一般に、もしプロジェクトが複数のサブパッケージから成るなら、最初にプロジェクト全体の名前がついたパッケージ節を使い、続いて 2 番目には、現在のサブパッケージの名前がついたパッケージ節を使うと良いでしょう。このようにすれば、プロジェクトを指すしばしば長くなりがちな前置子を使わずに、プロジェクトの他のサブパッケージを参照できます。

#center(){==========================================}


&aname(migrating,option=nolink){ }
** 新スキームへの移行 (Migrating to the New Scheme)

Because of the change, previous Scala code that is spread out over multiple subpackages of a common base package will most likely no longer compile correctly.
この変更によって、共通の基本パッケージを複数のサブパッケージにわたって広げた以前の Scala コードは、十中八九正しくコンパイルされないでしょう。

同じプロジェクト中の異なるサブパッケージへの参照は正しく拾い上げられません(いつも絶対パスを使わなかったなら、問題はありません)。

幸いなことに修正は簡単です。それぞれ主要なパッケージ節を、2 つあるいは 1 つの、そのプロジェクト名のついたパッケージ節と現在のサブパッケージで置き換えるだけです。これは複数ファイルの正規表現検索と置換で簡単にできます。

#center(){==========================================}


&aname(force,option=nolink){ }
** 慣習の力 (The Force of Convention)

ついでに言えば、古い Scala 2.7 規則は、C# の規則や他の .NET 言語と同じであることが分かります。ではなぜ、.NET 上で明らかに動作することが JVM 上でこのような問題を起こすのか?それは期待(expectations)と慣習(conventions)の問題です。Scala パッケージと似たネストした名前空間をもつ .Net では、名前空間 org.System を定義すると、よく知られているトップレベルの System 名前空間が隠れてしまいますから、正気なら誰もそんなことはしません。JVM 上では人はその類のことをし、そして Java の絶対指定のパッケージ名規約のおかげで、うまく動作します。ですから、この経験は、設計はしばしば技術的な基準のみで正誤を判断できず、既にある慣習とそのユーザーの期待にいかに沿うかが問題となることを示しています。Scala 2.7 のネストしたパッケージは .NET 上でも動作する単純な設計です。そこでの慣習が異なっていたため、JVM 上ではそれほどうまく機能しませんでした。2.8 では、パッケージ節の解釈に新しい展開を与えて問題を解消しました。

#center(){==========================================}

&aname(version,option=nolink){ }
** バージョン番号 (Version Numbers)

実は、厳密に言えば、パッケージ節の変更は言語の主版数を上げることを我々に強います。Scala の非公式の版数管理方式は次の通りです。

1. 言語中の後方非互換な変更は、2005 年 3 月の Scala1 から Scala2 へのように、新しい主版数を要求します。もし Scala を事実上異なる言語に変えるような十分な追加があるなら、主版数も変えなくてはなりません。ですから scala3 は scala2 と大きく異なることを意味すべきです。

2. 新しい言語フィーチャーとライブラリ中の後方非互換な変更は、Scala 2.7 から 2.8 へのように、ポイント番号の増加を要求します。ライブラリの後方非互換な変更は -deprecation 警告でゆっくりと段階的に導入するか、あるいは、それができなければ -Xmigration 警告を伴うべきです。

3. 後方互換なライブラリの追加とバグ・フィックスは、たとえば Scala 2.8.0 から 2.8.1 のように、3 番目の版数番号の変更を要求するだけです。これまでのところ、この版数管理方式に単純に従おうとしてきました。;これはいかなる公式のものでもなく、また、いつもこのようになることは前提とされるべきではありません。パッケージ節の意味変更は後方互換ではなく、したがって主要なバージョンが 3.0 にジャンプすることを要求します。しかし、変更は、2.8 に向けての準備段階遅くにもち上がり、ユーザーからの緊急のリクエストによって引き起こされたものでした。その時我々は、次の 3 つの可能性から選択しなければなりませんでした。:1 つめの選択肢は 2.7 から 3.0 へ直接行くことでした。しかし、すでに Scala 2.8 に関する本が出版されていました。それらの本の読者に不必要な困惑を与えるでしょう。そのうえ、人々がそれについて多くの話をしたにもかかわらず、日の目を見なかった幻の言語バージョンにするのはよくありません。2 つめの選択肢は、 2.8 の後までパッケージ節の変更を延ばすことでした。変更は既存コードの修正を要求とするので、これは全くよくありませんでした。変更する必要があるなら、早ければ早いほどよいのです。長く待てば、それだけ修正すべきコードが多くなります。3 つめの選択肢は、命番方式に例外を設けることで、我々が最終的に選んだものです。

#center(){==========================================}


&aname(acknow,option=nolink){ }
** 謝辞 (Acknowledgements)

修飾されたパッケージ節の意味変更は、Jorge Ortiz によって繰り返し提案されました。私は彼の忍耐力に感謝します。新しいチェインされたパッケージ節構文は、当初 David MacIver によって提案されました。

#center(){==========================================}

&aname(contents,option=nolink){ }
** 目次 (Contents)
- &link_anchor(what){変わったこと}
- &link_anchor(why){なぜ変えたか?}
- &link_anchor(change){変更のまとめ}
- &link_anchor(migrating){新スキームへの移行}
- &link_anchor(force){慣習の力}
- &link_anchor(version){バージョン番号}
- &link_anchor(acknow){謝辞}
- &link_anchor(contents){目次}
- &link_anchor(navigation){ナビゲーション}

#center(){==========================================}

&aname(navigation,option=nolink){ }
** ナビゲーション (Navigation)
- &link_anchor(what){変わったこと}
- &link_anchor(why){なぜ変えたか?}
- &link_anchor(change){変更のまとめ}
- &link_anchor(migrating){新スキームへの移行}
- &link_anchor(force){慣習の力}
- &link_anchor(version){バージョン番号}
- &link_anchor(acknow){謝辞}
- &link_anchor(contents){目次}
- &link_anchor(navigation){ナビゲーション}

復元してよろしいですか?

ツールボックス

下から選んでください:

新しいページを作成する
ヘルプ / FAQ もご覧ください。