Scala实例值范围 [英] Scala instance value scoping

查看:94
本文介绍了Scala实例值范围的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

请注意,此问题和类似的问题已在之前提出过,例如在解决方案

仅部分答案:

鉴于构造函数实际上只是一个方法...

不是.

  • 它不返回结果,也不声明返回类型(或没有名称)
  • 不能再次调用诸如"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中使用交互式反馈进行工作.您无需显式包装对象或类即可执行代码.要获得此即时反馈,不能等到(隐式)对象获得其关闭}为止.因此,不能以两次通过的方式读取类/对象,因为首先要执行所有的声明和初始化.

  • 一个人如何解决val的这种行为,找回习惯于其他方法的所有保证?

不要读取Ctor中的属性,就像您不读取Java中的属性一样,这可能会在子类中被覆盖.

更新

在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 ();
    }
}

...您看到foo的两个值.

0
42

我不想为此行为辩解,并且我同意,如果编译器可以因此发出警告(使用Java和Scala),那就太好了.

Note that this question and similar ones have been asked before, such as in Forward References - why does this code compile?, but I found the answers to still leave some questions open, so I'm having another go at this issue.

Within methods and functions, the effect of the val keyword appears to be lexical, i.e.

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

yielding

error: forward reference extends over definition of value bar

However, within classes, the scoping rules of val seem to change:

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

Not only does this compile, but also the println in the constructor will yield 0 as its output, while calling foo after the instance is fully constructed will result in the expected value 42.

So it appears to be possible for methods to forward-reference instance values, which will, eventually, be initialised before the method can be called (unless, of course, you're calling it from the constructor), and for statements within the constructor to forward-reference values in the same way, accessing them before they've been initialised, resulting in a silly arbitrary value.

From this, a couple of questions arise:

  • Why does val use its lexical compile-time effect within constructors?

Given that a constructor is really just a method, this seems rather inconsistent to entirely drop val's compile-time effect, giving it its usual run-time effect only.

  • Why does val, effectively, lose its effect of declaring an immutable value?

Accessing the value at different times may result in different results. To me, it very much seems like a compiler implementation detail leaking out.

  • What might legitimate usecases for this look like?

I'm having a hard time coming up with an example that absolutely requires the current semantics of val within constructors and wouldn't easily be implementable with a proper, lexical val, possibly in combination with lazy.

  • How would one work around this behaviour of val, getting back all the guarantees one is used to from using it within other methods?

One could, presumably, declare all instance vals to be lazy in order to get back to a val being immutable and yielding the same result no matter how they are accessed and to make the compile-time effect as observed within regular methods less relevant, but that seems like quite an awful hack to me for this sort of thing.

Given that this behaviour unlikely to ever change within the actual language, would a compiler plugin be the right place to fix this issue, or is it possible to implement a val-alike keyword with, for someone who just spent an hour debugging an issue caused by this oddity, more sensible semantics within the language?

解决方案

Only a partial answer:

Given that a constructor is really just a method ...

It isn't.

  • It doesn't return a result and doesn't declare a return type (or doesn't have a name)
  • It can't be called again for an object of said class like "foo".new ("bar")
  • You can't hide it from an derived class
  • You have to call them with 'new'
  • Their name is fixed by the name of the class

Ctors look a little like methods from the syntax, they take parameters and have a body, but that's about all.

  • Why does val, effectively, lose its effect of declaring an immutable value?

It doesn't. You have to take an elementary type which can't be null to get this illusion - with Objects, it looks different:

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

You can't change a val 2 times, you can't give it a different value than null or 0, you can't change it back, and a different value is only possible for the elementary types. So that's far away from being a variable - it's a - maybe uninitialized - final value.

  • What might legitimate usecases for this look like?

I guess working in the REPL with interactive feedback. You execute code without an explicit wrapping object or class. To get this instant feedback, it can't be waited until the (implicit) object gets its closing }. Therefore the class/object isn't read in a two-pass fashion where firstly all declarations and initialisations are performed.

  • How would one work around this behaviour of val, getting back all the guarantees one is used to from using it within other methods?

Don't read attributes in the Ctor, like you don't read attributes in Java, which might get overwritten in subclasses.

update

Similar problems can occur in Java. A direct access to an uninitialized, final attribute is prevented by the compiler, but if you call it via another method:

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 ();
    }
}

... you see two values for foo.

0
42

I don't want to excuse this behaviour, and I agree, that it would be nice, if the compiler could warn consequently - in Java and Scala.

这篇关于Scala实例值范围的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆