Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

vals are accessible before they are initialized, and their value is sometimes null or zero if accessed at that time, and no warning or error is issued upon their use #4856

Closed
scabug opened this issue Jul 31, 2011 · 3 comments
Assignees

Comments

@scabug
Copy link

scabug commented Jul 31, 2011

The behavior or forward references to vals and recursive values is a bit odd and inconsistent.


In a class initializer a val can refer to a val not yet declared and no error or warning is issued, instead the value is treated as zero or null.

For example:

scala> :paste
// Entering paste mode (ctrl-D to finish)

class Foo {
val bar = x+1
val x = 5
}

// Exiting paste mode, now interpreting.

defined class Foo

scala> val foo = new Foo
foo: Foo = Foo@7875455c

scala> foo.bar
res0: Int = 1

Int the end the value of x is treated as zero unexpectedly. I would expect either foo.bar to 6 or give an error at compile time or run time. This could easily create bugs where someone reordered vals in the initializer such a way that some of them would now be null or zero.


You can use lazy vals and by-name parameters to create an object that refers to itself. However, if that self-reference is evaluated too soon you get null instead of the expected value.

For example:

scala> :paste
// Entering paste mode (ctrl-D to finish)

class Root(child0: => Child) {
 lazy val child = child0
}

class Child(parent0: => Root) {
 lazy val parent = parent0
}

// Exiting paste mode, now interpreting.

defined class Root
defined class Child

scala> val what:Root = new Child(what).parent
what: Root = null

scala> val w2:Root = new Root(new Child(w2)).child.parent
w2: Root = null

I would have preferred to get a run time exception or (better) a compile error in this case.


For comparison, when used inside {}'s local vals referring to later vals actually are reordered to evaluate properly.
For example:

scala> :paste
// Entering paste mode (ctrl-D to finish)

def ??? = {
val result = prefix + suffix;
val prefix = "should this "
val suffix = "really work?"
result
}

// Exiting paste mode, now interpreting.

$qmark$qmark$qmark: java.lang.String

scala> ???
res4: java.lang.String = should this really work?

This is clever but it does mean that there's an inconsistency between this and a class initialization (where they are not reordered and no error is reported).


A val that refers directly to itself either gets zero for itself, or if you put lazy in front you can create infinite recursion:

scala> val one:Int = 10 - one
one: Int = 10

scala> lazy val one:Int = 10 - one
one: Int = <lazy>

scala> one
java.lang.StackOverflowError

I can only imagine that there is some rare use case for forward and recursive references within vals but I can't help but wonder if this is really a helpful capability or just making things more confusing.

At my level I can't even figure out how to define a recursive val that isn't infinitely recursive without involving some method that has a side-effect.


If it were me, I would forbid any reference or use of a val that would ever return any value other than its final, initialized value. This would mean pretty much all the use cases above would return either a compile-time or run-time error where they currently yield a value or zero or null.

@scabug
Copy link
Author

scabug commented Jul 31, 2011

Imported From: https://issues.scala-lang.org/browse/SI-4856?orig=1
Reporter: Dobes Vandermeer (dobes_vandermeer)
Affected Versions: 2.9.0

@scabug
Copy link
Author

scabug commented Jul 31, 2011

@paulp said:
3 is a bug, not cleverness. In trunk it's a compile-time error.

:9: error: forward reference extends over definition of value result
val result = prefix + suffix;
^

This ticket is a duplicate of about a thousand others, but I'll leave it open since it gave me an idea. Your advice at the end is impossible unless you want to prohibit overriding vals.

Here's a val referring to itself and making the fibonacci sequence.

scala> val fib: Stream[Int] = 1 #:: ((0 #:: fib zip fib) map { case (x, y) => x + y })
fib: Stream[Int] = Stream(1, ?)

scala> fib take 10 toList
res0: List[Int] = List(1, 1, 2, 3, 5, 8, 13, 21, 34, 55)

@scabug
Copy link
Author

scabug commented Jun 11, 2012

@adriaanm said:
Paul, assigning to you (in case you want to reopen) and closing (since it seems to me there's nothing more to see here).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants