3

この質問と同様の質問が、前方参照などで以前に尋ねられたことに注意してください - なぜこのコードはコンパイルされるのですか? 、しかし、まだいくつかの質問を開いたままにする答えが見つかったので、この問題にもう一度取り組んでいます。

メソッドと関数内では、valキーワードの効果は語彙的であるように見えます。

def foo {
  println(bar)
  val bar = 42
}

降伏

error: forward reference extends over definition of value bar

ただし、クラス内では、スコープ ルールがval変更されているようです。

object Foo {
  def foo = bar
  println(bar)
  val bar = 42
}

これはコンパイルされるだけでなくprintln、コンストラクター内のも0出力として生成されますが、インスタンスが完全に構築された後に呼び出すとfoo、期待される値が返されます42

したがって、メソッドがインスタンス値を前方参照することは可能であるように思われます。これは、最終的に、メソッドが呼び出される前に初期化されます (もちろん、コンストラクターから呼び出している場合を除きます)。コンストラクター内のステートメントについても同様です。同じ方法で値を前方参照し、初期化される前に値にアクセスすると、ばかげた任意の値になります。

このことから、いくつかの疑問が生じます。

  • valコンストラクタ内で字句コンパイル時の効果を使用するのはなぜですか?

コンストラクターが実際には単なるメソッドであることを考えるとval、通常の実行時の効果のみを与えて、 のコンパイル時の効果を完全に削除するのはかなり矛盾しているようです。

  • が、事実上、不変valを宣言する効果を失うのはなぜですか?

異なるタイミングで値にアクセスすると、異なる結果になる可能性があります。私には、コンパイラの実装の詳細が漏れているように思えます。

  • これの正当なユースケースはどのようなものでしょうか?

コンストラクター内の現在のセマンティクスを絶対に必要とし、おそらく と組み合わせてval、適切なレキシカル で簡単に実装できない例を思いつくのに苦労しています。vallazy

  • のこの動作をどのように回避しval、他のメソッド内での使用に慣れているすべての保証を取り戻すでしょうか?

おそらく、すべてvalのインスタンスを宣言lazyして、val不変であり、アクセス方法に関係なく同じ結果が得られるようにし、通常のメソッド内で観察されるコンパイル時の影響をあまり関連性のないものにすることができますが、それはそうですこの種のことについては、私にとって非常にひどいハックのようです。

この動作が実際の言語内で変更される可能性が低いことを考えると、コンパイラ プラグインはこの問題を修正する適切な場所でしょうか、またはval-alike キーワードを実装することは可能でしょうか?この奇妙で、言語内のより賢明なセマンティクス?

4

2 に答える 2

3

部分的な答えのみ:

コンストラクターが実際には単なるメソッドであることを考えると...

そうではありません。

  • 結果を返さず、戻り値の型を宣言しません (または名前がありません)。
  • 次のようなクラスのオブジェクトに対して再度呼び出すことはできません"foo".new ("bar")
  • 派生クラスから隠すことはできません
  • 「new」でそれらを呼び出す必要があります
  • それらの名前は、クラスの名前によって固定されています

Ctor は、構文から見るとメソッドに少し似ています。パラメーターを取り、本体を持ちますが、それだけです。

  • なぜ val は、事実上、不変値を宣言する効果を失うのでしょうか?

そうではありません。この錯覚を得るには、null にできない基本型を使用する必要があります。オブジェクトを使用すると、見た目が異なります。

object Foo {
  def foo = bar
  println (bar.mkString)
  val bar = List(42)
}
// Exiting paste mode, now interpreting.

defined module Foo

scala> val foo=Foo 
java.lang.NullPointerException

val を 2 回変更することはできません。null または 0 以外の値を指定することはできません。元に戻すことはできません。異なる値は基本型でのみ可能です。したがって、これは変数とはかけ離れています。これは、おそらく初期化されていない最終値です。

  • これの正当なユースケースはどのようなものでしょうか?

インタラクティブなフィードバックでREPLで作業していると思います。明示的なラッピング オブジェクトまたはクラスなしでコードを実行します。このインスタント フィードバックを取得するには、(暗黙の) オブジェクトがその終了を取得するまで待つことはできません}。したがって、クラス/オブジェクトは、最初にすべての宣言と初期化が実行される 2 パス方式では読み取られません。

  • val のこの動作をどのように回避し、他のメソッド内でそれを使用することに慣れているすべての保証を取り戻すのでしょうか?

サブクラスで上書きされる可能性がある Java で属性を読み取らないように、Ctor で属性を読み取らないでください。

アップデート

Java でも同様の問題が発生する可能性があります。初期化されていない final 属性への直接アクセスはコンパイラによって防止されますが、別のメソッドを介して呼び出すと、次のようになります。

public class FinalCheck
{
    final int foo;
    
    public FinalCheck ()
    {
        // does not compile: 
        // variable foo might not have been initialized
        // System.out.println (foo);
        
        // Does compile - 
        bar ();

        foo = 42;       
        System.out.println (foo);
    }

    public void bar () {
        System.out.println (foo);   
    }
    public static void main (String args[])
    {
        new FinalCheck ();
    }
}

... の 2 つの値が表示されfooます。

0
42

私はこの振る舞いを言い訳したくはありませんし、Java と Scala でコンパイラが結果として警告を発することができれば良いと思います。

于 2012-04-21T08:46:28.180 に答える
2

したがって、メソッドがインスタンス値を前方参照することは可能であるように思われます。これは、最終的に、メソッドが呼び出される前に初期化されます (もちろん、コンストラクターから呼び出している場合を除きます)。コンストラクター内のステートメントについても同様です。同じ方法で値を前方参照し、初期化される前に値にアクセスすると、ばかげた任意の値になります。

コンストラクタコンストラクタです。オブジェクトを構築しています。そのフィールドはすべて JVM によって初期化され (基本的にはゼロに設定されます)、次にコンストラクターは、入力が必要なフィールドに入力します。

  • val がコンストラクター内で字句コンパイル時の効果を使用するのはなぜですか?

    コンストラクターが実際には単なるメソッドであることを考えると、これは val のコンパイル時の効果を完全に削除して、通常の実行時の効果のみを与えることはかなり矛盾しているようです。

ここで何を言っているのか、何を尋ねているのかわかりませんが、コンストラクターはメソッドではありません。

  • なぜ val は、事実上、不変値を宣言する効果を失うのでしょうか?

    異なるタイミングで値にアクセスすると、異なる結果になる可能性があります。私には、コンパイラの実装の詳細が漏れているように思えます。

そうではありません。コンストラクターから変更しようとするとbar、それが不可能であることがわかります。もちろん、コンストラクターで異なるタイミングで値にアクセスすると、異なる結果になる可能性があります。

あなたはオブジェクトを構築しています: それは構築されずに始まり、構築されて終わります。変更されないようにするには、最終的な値から開始する必要がありますが、誰かがその値を割り当てずに、どうすればそれを行うことができるでしょうか?

誰がそれをすると思いますか?コンストラクター。

  • これの正当なユースケースはどのようなものでしょうか?

コンストラクター内で val の現在のセマンティクスを絶対に必要とする例を思いつくのに苦労しています。適切な字句 val を使用して、おそらく lazy と組み合わせて簡単に実装することはできません。

値が入力される前に val にアクセスするユースケースはありません。初期化されているかどうかを確認することは不可能です。例えば:

class Foo {
  println(bar)
  val bar = 10
}

コンパイラは初期化されていないことを保証できると思いますか? それでは、REPL を開き、上記のクラスを挿入してから、次のようにします。

class Bar extends { override val bar = 42 } with Foo
new Bar

そしてbar、印刷時に初期化されたことを確認してください。

  • val のこの動作をどのように回避し、他のメソッド内でそれを使用することに慣れているすべての保証を取り戻すのでしょうか?

使用する前に vals を宣言してください。ただし、コンストラクターはメソッドではないことに注意してください。あなたがするとき:

println(bar)

コンストラクター内で、次のように書いています。

println(this.bar)

そしてthis、コンストラクタを書いているクラスのオブジェクトにはbarゲッターがあるので呼び出されます。

bar定義であるメソッドで同じことを行う場合thisbarゲッターにはありません。

于 2012-04-21T18:34:15.103 に答える