surprised by class initialization

161 views
Skip to first unread message

Michael Schmitz

unread,
Nov 30, 2011, 12:22:10 PM11/30/11
to scala-user
Maybe Java has the same issue, but the way Scala constructors work
encourages me to put initialization code at the top of the body of
classes. Maybe this is a bad approach and I should put more
information into secondary constructors (def this = ).

scala> class Foo { val x = y + 2; val y = 3 }
defined class Foo

scala> (new Foo).x
res9: Int = 2

Whereas, when the assignment to y is first.

scala> class Bar { val y = 3; val x = y + 2 }
defined class Bar

scala> (new Bar).x
res2: Int = 5

The first example silently uses the default value for an int (0).
This is confusing to me because y is a val that takes on two different
values (0 and 3) during the initialization of the class. I remember a
similar problem when using initialization code in traits.

At the very least, should we be giving a compiler warning when
referring to a variable that has not yet been initialized?

Peace. Michael

Dave

unread,
Nov 30, 2011, 1:18:23 PM11/30/11
to scala-user
In java you get "illegal forward reference"

It is a bug in 2.9
see: https://issues.scala-lang.org/browse/SI-4419

Daniel Sobral

unread,
Nov 30, 2011, 1:21:11 PM11/30/11
to Michael Schmitz, scala-user

This is a hard problem to solve in practice, simple as it is for your
particular example. What can be done is to throw an exception at
run-time if the initialization does not happen correctly, which can be
done with the -Xcheckinit flag.

--
Daniel C. Sobral

I travel to the future all the time.

Daniel Sobral

unread,
Nov 30, 2011, 1:27:24 PM11/30/11
to Dave, scala-user
On Wed, Nov 30, 2011 at 16:18, Dave <dave.mah...@hotmail.com> wrote:
> In java you get "illegal forward reference"
>
> It is a bug in 2.9
> see: https://issues.scala-lang.org/browse/SI-4419

No, that is a different issue. In this case, x and y are members of a
class, so this code is valid. Java has the same problem, but the way
this code would appear in Java is less obvious. Here's an equivalent
Java class:

class Foo {
private int x;
private int y;

Foo() {


x = y + 2;

y = 3;
}
}

Java won't complain, and it has the very same bug. I think all IDEs do
complain about it, but Java initialization is much simpler than
Scala's. But the point is that people find the bug in that obvious,
while they don't find the bug in Scala version obvious. The reason is
that Scala mixes declaration and initialization *in the syntax*, but
separates them at run-time, while Java keeps them separated.

--

Michael Schmitz

unread,
Nov 30, 2011, 1:41:43 PM11/30/11
to Daniel Sobral, Dave, scala-user
I don't think your example is quite right.

class Foo {
private final int x;
private final int y;

Foo() {
x = y + 2;
y = 3;
}
}

> javac Foo.java
Foo.java:6: variable y might not have been initialized


x = y + 2;

Peace. Michael

Daniel Sobral

unread,
Nov 30, 2011, 1:51:24 PM11/30/11
to Michael Schmitz, Dave, scala-user
On Wed, Nov 30, 2011 at 16:41, Michael Schmitz <mic...@schmitztech.com> wrote:
> I don't think your example is quite right.
>
> class Foo {
>  private final int x;
>  private final int y;
>
>  Foo() {
>   x = y + 2;
>   y = 3;
>  }
> }
>
>> javac Foo.java
> Foo.java:6: variable y might not have been initialized
>   x = y + 2;

dcs@ayanami:~/tmp$ cat Foo.java


class Foo {
private int x;
private int y;

Foo() {
x = y + 2;
y = 3;
}
}


dcs@ayanami:~/tmp$ javac Foo.java

Are you using Java 7, by any chance?

martin odersky

unread,
Nov 30, 2011, 1:59:53 PM11/30/11
to Daniel Sobral, Michael Schmitz, Dave, scala-user
On Wed, Nov 30, 2011 at 7:51 PM, Daniel Sobral <dcso...@gmail.com> wrote:
On Wed, Nov 30, 2011 at 16:41, Michael Schmitz <mic...@schmitztech.com> wrote:
> I don't think your example is quite right.
>
> class Foo {
>  private final int x;
>  private final int y;
>
>  Foo() {
>   x = y + 2;
>   y = 3;
>  }
> }
>
>> javac Foo.java
> Foo.java:6: variable y might not have been initialized
>   x = y + 2;

I think you need to write

  Foo() {
    x = this.y + 2
    y = 3
  }

That's pretty much what Scala's version translates to.

Cheers

 -- Martin
 

Michael Schmitz

unread,
Nov 30, 2011, 2:03:48 PM11/30/11
to Daniel Sobral, Dave, scala-user
> javac -version
javac 1.6.0_22

I just stuck in the finals. Sorry I mean to make my change clearer
before sending the email.

Peace. Michael

Daniel Sobral

unread,
Nov 30, 2011, 3:01:48 PM11/30/11
to Michael Schmitz, Dave, scala-user
Yeah, just noticed that now.

Dave

unread,
Nov 30, 2011, 3:19:44 PM11/30/11
to scala-user
If I translate it to:

Init.java
=========
package init;

class Init {
class Foo { private final int x = y + 2;
private final int y = 3;
public int x() { return x; }
}
class Bar { private final int y = 3;
private final int x = y + 2;
public int x() { return x; }
}
public static void main(String[] args) {
new Init();
}

Init() {
System.out.println(new Foo().x());
System.out.println(new Bar().x());
}
}

I get:


C:\scala-2.9.1.final\examples>javac -d . Init.java
Init.java:4: illegal forward reference
class Foo { private final int x = y + 2;
^
1 error

If you out comment Foo and the call site I get:

C:\scala-2.9.1.final\examples>javac -d . Init.java

C:\scala-2.9.1.final\examples>java init.Init
5

On 30 nov, 19:27, Daniel Sobral <dcsob...@gmail.com> wrote:

> I travel to the future all the time.- Tekst uit oorspronkelijk bericht niet weergeven -
>
> - Tekst uit oorspronkelijk bericht weergeven -

Daniel Sobral

unread,
Nov 30, 2011, 3:38:16 PM11/30/11
to Dave, scala-user
What's the point of what happens when you do not translate it correctly?

So, Java does not support abstract overrides, it does not support
traits, it does not support early initialization, it does not support
overrides of fields. In other words: it has a simpler model to deal
with.

Dave

unread,
Dec 1, 2011, 7:41:08 AM12/1/11
to scala-user
I know that Scala has different semantics than Java and Java different
semantics than c# and Nemerle is also different and so on.
But that is between programming languages and this addresses a
semantic difference within Scala between Scala surface code and its
underlying semantic model.

val guarantees immutability so y gets a value only once and upon
initializing.
Its semantic translation however is a mutable that is initialized with
0 upon first usage and later assigned 3.
So during semantic translation a semantic side-effect is introduced
which weakens the ability for the compiler to check for typos.

For instance:
t and y keys are next to each other on the keyboard
if you want to type
class Foo { val t = 1; val x = t + 2; val y = 3 }
and you type
class Foo { val t = 1; val x = y + 2; val y = 3 }
the compiler will not raise an error with the current translation.

This is simple but when the algorithm is more complex and the
application is bigger you won't see it.
Incorrectness (due to typos etcetera) is even more worse than runtime
errors and can be very expensive.

Compiler time errors are a big reason to program in a strong
statically typed language (next to performance).

One note to the title btw: it is instance initialization and x and y
are instance members. In java class initialization and class members
are static.

Daniel Sobral

unread,
Dec 2, 2011, 9:59:46 AM12/2/11
to Dave, scala-user
On Thu, Dec 1, 2011 at 10:41, Dave <dave.mah...@hotmail.com> wrote:
>
> val guarantees immutability so y gets a value only once and upon
> initializing.

Yes, but val can be overridden, so you cannot predict the point of
initialization.

> Its semantic translation however is a mutable that is initialized with
> 0 upon first usage and later assigned 3.
> So during semantic translation a semantic side-effect is introduced
> which weakens the ability for the compiler to check for typos.
>
> For instance:
> t and y keys are next to each other on the keyboard
> if you want to type
> class Foo { val t = 1; val x = t + 2; val y = 3 }
> and you type
> class Foo { val t = 1; val x = y + 2; val y = 3 }
> the compiler will not raise an error with the current translation.

scala> class Foo { val t = 1; val x = y + 2; val y = 3 }
defined class Foo

scala> class Bar extends { override val y = 1 } with Foo
defined class Bar

scala> new Bar
res0: Bar = Bar@7810a519

scala> res0.t
res1: Int = 1

scala> res0.x
res2: Int = 3

scala> res0.y
res3: Int = 1

Works as expected.

Dave

unread,
Dec 2, 2011, 10:21:16 AM12/2/11
to scala-user
Yes but it works because there is not really a forward reference for
y.
override val y = 1 is executed first (and then val t = 1; val x = y +
2;) and val y = 3 is not executed.
So basically the sequence is:
val y = 1; val t = 1; val x = y + 2;

On 2 dec, 15:59, Daniel Sobral <dcsob...@gmail.com> wrote:

Daniel Sobral

unread,
Dec 2, 2011, 3:13:49 PM12/2/11
to Dave, scala-user
On Fri, Dec 2, 2011 at 13:21, Dave <dave.mah...@hotmail.com> wrote:
> Yes but it works because there is not really a forward reference for
> y.
> override val y = 1 is executed first (and then val t = 1; val x = y +
> 2;) and val y = 3 is not executed.
> So basically the sequence is:
> val y = 1; val t = 1; val x = y + 2;

The point is that y is accessed through open recursion, so one cannot
tell for sure whether it was initialized or not. Whether there's an
error or not depends on how it is used. While that case seemed silly
because "val y = 3" was lost, here are two variations that are not so
silly:

class Foo { val t = 1; val x = y + 2; val y = _ }
abstract class Foo { val t = 1; val x = y + 2; val y: Int }

martin odersky

unread,
Dec 2, 2011, 3:15:23 PM12/2/11
to Daniel Sobral, Dave, scala-user
On Fri, Dec 2, 2011 at 9:13 PM, Daniel Sobral <dcso...@gmail.com> wrote:
On Fri, Dec 2, 2011 at 13:21, Dave <dave.mah...@hotmail.com> wrote:
> Yes but it works because there is not really a forward reference for
> y.
> override val y = 1 is executed first (and then val t = 1; val x = y +
> 2;) and val y = 3 is not executed.
> So basically the sequence is:
> val y = 1; val t = 1; val x = y + 2;

The point is that y is accessed through open recursion, so one cannot
tell for sure whether it was initialized or not. Whether there's an
error or not depends on how it is used. While that case seemed silly
because "val y = 3" was lost, here are two variations that are not so
silly:

class Foo { val t = 1; val x = y + 2; val y = _ }
abstract class Foo { val t = 1; val x = y + 2; val y: Int }

Right. That's why the best we can do is a "dubious forward reference" warning. 
Errors would be out of place here.

 -- Martin
 

Dave

unread,
Dec 2, 2011, 3:42:10 PM12/2/11
to scala-user
But that is an academic answer, but what should an it-manager in an
enterprise do with this information?

He thinks Java works this way: either a correct answer or an error
maybe not the succinct implicitly higher kinded way, but it works and
Scala we are not sure which way it works and he doesn't want that his
programmers who have different levels in programming are looking for
the needle in the haystack and spend time on how it works so he will
not adopt Scala.


On 2 dec, 21:13, Daniel Sobral <dcsob...@gmail.com> wrote:

Michael Schmitz

unread,
Dec 2, 2011, 4:02:27 PM12/2/11
to Dave, scala-user
I'll still use scala, but a warning message would be great!

Daniel Sobral

unread,
Dec 2, 2011, 4:03:43 PM12/2/11
to Dave, scala-user
On Fri, Dec 2, 2011 at 18:42, Dave <dave.mah...@hotmail.com> wrote:
> But that is an academic answer, but what should an it-manager in an
> enterprise do with this information?
>
> He thinks Java works this way: either a correct answer or an error
> maybe not the succinct implicitly higher kinded way, but it works and
> Scala we are not sure which way it works and he doesn't want that his
> programmers who have different levels in programming are looking for
> the needle in the haystack and spend time on how it works so he will
> not adopt Scala.

Are you seriously claiming that an it-manager would even entertain
such low level discussion, much less base Scala adoption on its
answer? I find the very idea completely absurd.

But, anyway, here's the non-academic answer: it is NOT POSSIBLE to
correctly assert if a forward reference is used incorrect at compile
time in Scala.

If you want to avoid problems, just compile with -Xcheckinit. The
error will show up at run-time, but it should show up readily enough.

Eugen Labun

unread,
Dec 2, 2011, 6:32:50 PM12/2/11
to martin odersky, Daniel Sobral, Dave, scala-user
On 2011-12-02 21:15, martin odersky wrote:

Daniel, Martin,
I'm realy grateful to you for your patience in explaining Scala concepts.

But it's still unclear to me, why not to forbid forward references generally by generating a compile
*error*? Like it's done for sequences of expressions in methods.

Why errors "would be out of place here"?

(Perhaps, the last example should show an important usecase, but I can't comprehend, sorry.)

--
Thank you,
Eugen

martin odersky

unread,
Dec 3, 2011, 4:02:39 AM12/3/11
to Eugen Labun, Daniel Sobral, Dave, scala-user
On Sat, Dec 3, 2011 at 12:32 AM, Eugen Labun <la...@gmx.net> wrote:
On 2011-12-02 21:15, martin odersky wrote:
On Fri, Dec 2, 2011 at 9:13 PM, Daniel Sobral <dcso...@gmail.com <mailto:dcso...@gmail.com>> wrote:

   On Fri, Dec 2, 2011 at 13:21, Dave <dave.mah...@hotmail.com
   <mailto:dave.mahabiersing@hotmail.com>> wrote:
    > Yes but it works because there is not really a forward reference for
    > y.
    > override val y = 1 is executed first (and then val t = 1; val x = y +
    > 2;) and val y = 3 is not executed.
    > So basically the sequence is:
    > val y = 1; val t = 1; val x = y + 2;

   The point is that y is accessed through open recursion, so one cannot
   tell for sure whether it was initialized or not. Whether there's an
   error or not depends on how it is used. While that case seemed silly
   because "val y = 3" was lost, here are two variations that are not so
   silly:

   class Foo { val t = 1; val x = y + 2; val y = _ }
   abstract class Foo { val t = 1; val x = y + 2; val y: Int }

Right. That's why the best we can do is a "dubious forward reference" warning.
Errors would be out of place here.

Daniel, Martin,
I'm realy grateful to you for your patience in explaining Scala concepts.

But it's still unclear to me, why not to forbid forward references generally by generating a compile *error*? Like it's done for sequences of expressions in methods.

Why errors "would be out of place here"?

At the end of 


there's an example that shows that forward referencing vals can be useful.
I quote:

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)

 
(Perhaps, the last example should show an important usecase, but I can't comprehend, sorry.)

Cheers

 -- Martin
 

Peter Walkley

unread,
Dec 3, 2011, 4:56:23 AM12/3/11
to scala...@googlegroups.com
In java, this is the sort of thing I would expect to be flagged by checkstyle and/or findbugs. The "IT manager" seriously is not going to care unless they are a micromanaging frustrated ex-developer !
 
With my team-leader hat on, I would want to see a comment in the code explaining why the unusual pratise was there for the benefit of the developers who come along after - but that would be the end of the matter.

Dave

unread,
Dec 3, 2011, 7:50:26 AM12/3/11
to scala-user

>
> there's an example that shows that forward referencing vals can be useful.
> I quote:
>
> 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)
>
> > (Perhaps, the last example should show an important usecase, but I can't
> > comprehend, sorry.)
>
> Cheers
>
>  -- Martin- Tekst uit oorspronkelijk bericht niet weergeven -

>
> - Tekst uit oorspronkelijk bericht weergeven -

But this is a recursive function and fib is lazy initialized (but
still initialized). The initialization is within the statement and
after that it is used.

Here:


class Foo { val t = 1; val x = y + 2; val y = _ }

there is a eager forward reference of y across the statement boundary
(the semicolon). Or am I misreading something? Are those semicolons
not really statement separators?

I see it as an important difference.
But nevertheless I understand that it is a difficult problem to solve
otherwise Paul would have fixed it long time ago.

Nils Kilden-Pedersen

unread,
Dec 3, 2011, 8:12:13 AM12/3/11
to martin odersky, Daniel Sobral, Michael Schmitz, Dave, scala-user
Is there a reason why the Java compiler allows the forward reference, when prefixing with "this"? It seems the compile error, which occurs without "this", is valid, and I'm struggling to think of a use case, where the above behavior is desirable.

Eugen Labun

unread,
Dec 4, 2011, 10:29:53 AM12/4/11
to martin odersky, Daniel Sobral, Dave, scala-user
On 2011-12-03 10:02, martin odersky wrote:
> At the end of https://issues.scala-lang.org/browse/SI-4856 .
> there's an example that shows that forward referencing vals can be useful.
> I quote:
>
> scala> val fib: Stream[Int] = 1 #:: ((0 #:: fib zip fib) map { case (x, y) => x + y })
> fib: Stream[Int] = Stream(1, ?)

Thank you, Martin, but I should agree with Dave that the usage of 'fib' here (on the
right-hand-side) is not really a forward reference. The parameter of the '#::'-method will be taken
by name, and therefore is in fact a function object that is fully initialized *without accessing
'fib'* (the desugared version of this expression is attached, the class of the function is
'Fib$$anonfun$1').

It should also be noted that this code doesn't compile if 'fib' is a local val (instead of a member
val), since the compiler sees it as a (formal) forward reference: "error: forward reference extends
over definition of value fib".

This behavior (member val: OK, local val: compile error) could be seen as inconsistency.

A *lazy* val does work in both cases (member / local).


But that were rather side notes. What is much more important in my opinion:


Java's automatic control over initialization, which ensures that fields and variables are
initialized before their usage, was an important improvement as compared to C/C++.

Scala may not be a step back.

At the beginning I was delighted with Scala features such as the uniform access principle or the
ability to override member fields. But if it comes at the costs of losing automatic control over the
initialization state, I would rather give up some of these new features than vice versa.

Assuming, you would agree, which options do we have?

I see the following:

1) We could disallow overriding vals�, if it will make possible (does it?) to get automatic
initialization control.

2) Another option would be to handle all member-'val's as 'lazy vals'�.
As an addition, perhaps, it would make sense to introduce another keywords for local variables
(which are simple old variables) as opposed to member fields (which are field/accessor[/mutator]
pairs/triples)?

3) Something better?


What do you think about?

Thanks,
Eugen


� Which imo isn't properly working anyway, e.g. there is no access to the overriden value via
'super', there is a lot of nuances how to avoid 'null's, etc., etc.

� Theoretically, such change (eager evaluation -> lazy evaluation) should not break existing
programs (i.e. their correctness), but the point of initialization might be important for the
run-time behavior, e.g. for the responsiveness of the user interface. I think also that the slightly
reduced performance due to initialization check on each access will not be an issue in practice,
since the resulting target reference is often cached by the caller. (Remember, I'm still speaking
about member vals, not about local vals.)

Fib.scala
Fib_xprint_jvm.txt

martin odersky

unread,
Dec 5, 2011, 2:57:41 PM12/5/11
to Eugen Labun, Daniel Sobral, Dave, scala-user
On Sun, Dec 4, 2011 at 7:29 AM, Eugen Labun <la...@gmx.net> wrote:
> On 2011-12-03 10:02, martin odersky wrote:
>>
>> At the end of https://issues.scala-lang.org/browse/SI-4856 .
>> there's an example that shows that forward referencing vals can be useful.
>> I quote:
>>
>> scala> val fib: Stream[Int] = 1 #:: ((0 #:: fib zip fib) map { case (x, y)
>> => x + y })
>> fib: Stream[Int] = Stream(1, ?)
>
>
> Thank you, Martin, but I should agree with Dave that the usage of 'fib' here
> (on the right-hand-side) is not really a forward reference. The parameter of
> the '#::'-method will be taken by name, and therefore is in fact a function
> object that is fully initialized *without accessing 'fib'* (the desugared
> version of this expression is attached, the class of the function is
> 'Fib$$anonfun$1').

Of course. That's one half of my point! In general you will not be
able to detect forward references statically. Subcases, yes, but in
general no. That's why the best we can do is a warning.

>
> It should also be noted that this code doesn't compile if 'fib' is a local
> val (instead of a member val), since the compiler sees it as a (formal)
> forward reference: "error: forward reference extends over definition of
> value fib".
>

Excact. Same as in Java. Java prescribes that local variables are
guaranteed to be explicitly initialized but object fields need not be.
The problem is considerably simpler for local variables because there
are no aliases. But some things (e.g. fib) fall through the cracks,
and are rejected by the stricter warning even though they are
technically OK.

>
> But that were rather side notes. What is much more important in my opinion:
>
>
> Java's automatic control over initialization, which ensures that fields and
> variables are initialized before their usage, was an important improvement
> as compared to C/C++.
>
> Scala may not be a step back.
>

I have argued two times now on this thread Java and Scala behave
_exactly the same_. Before we go on, please show an example where they
do not.

> At the beginning I was delighted with Scala features such as the uniform
> access principle or the ability to override member fields. But if it comes
> at the costs of losing automatic control over the initialization state, I
> would rather give up some of these new features than vice versa.
>

Neither Java nor Scala give you automatic control over initialization state.

>
> Assuming, you would agree, which options do we have?
>
> I see the following:
>

> 1) We could disallow overriding valsน, if it will make possible (does it?)


> to get automatic initialization control.
>

It won't. To find out more, here's a reference:

Manuel Fähndrich, K. Rustan M. Leino: Declaring and checking non-null
types in an object-oriented language. OOPSLA 2003: 302-312.

The paper shows among others what you need to do to get static
initialization checking. The type system is very complicated and still
very restrictive. It's also strictly research. It was implemented in
Spec# (I believe), but never in a mainstream programming language.

> 2) Another option would be to handle all member-'val's as 'lazy vals'ฒ.


>   As an addition, perhaps, it would make sense to introduce another keywords
> for local variables (which are simple old variables) as opposed to member
> fields (which are field/accessor[/mutator] pairs/triples)?
>

We considered that. But lazy vals interact badly with effects. If you
need effects to happen during object initialization, lazy vals are
problematic.

Cheers

-- Martin

Nils Kilden-Pedersen

unread,
Dec 5, 2011, 3:46:19 PM12/5/11
to martin odersky, Eugen Labun, Daniel Sobral, Dave, scala-user
On Mon, Dec 5, 2011 at 1:57 PM, martin odersky <martin....@epfl.ch> wrote:
I have argued two times now on this thread Java and Scala behave
_exactly the same_. Before we go on, please show an example where they
do not.

Martin, I asked a couple of days ago, if anyone knows why Java allows forward referencing when prefixing with "this". I asked because I can't think of a use case where that is useful, and therefore it seems questionable that Scala should allow it. (I can understand why there are technical reasons why this can be difficult to detect, but that's another argument).

rkuhn

unread,
Dec 5, 2011, 5:21:59 PM12/5/11
to scala...@googlegroups.com, martin odersky, Eugen Labun, Daniel Sobral, Dave
Doesn’t the fib example from three posts up fit the bill:


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

That’s a forward reference, AFAICT (“left” is the new “forward” ;-) ).

Regards,

Roland

Dave

unread,
Dec 6, 2011, 7:22:03 AM12/6/11
to scala-user

Maybe it is also called a "forward reference", but that is a recursive
function within a statement which is something different than this
issue. Simply said: all fibs are behind val fib.

Here it is about imperative forward references across statement bounds
(semicolons). There is no recursive function here.
In the example below the correct answer is 5 but for the eager forward
reference is 2.
Only lazy and final forward references return 5 as correct answer (and
"eager forward reference with early override")
So an eager forward reference is a potentially threat for incorrect
answer.
Therefore a warning would be helpful

package initscala
class Foo { val other = this; val x = other.y + 2; val y = 3 } //
eager forward reference
class FooLazy { val other = this; val x = other.y + 2; lazy val y =
3 } // lazy forward reference
class FooFinal { val other = this; val x = other.y + 2; final val y =
3 } // final forward reference
class Foo2 extends Foo { override val y = 3 } // eager forward
reference with late override
class Foo3 extends FooLazy { override lazy val y = 3 } // lazy forward
reference with late override
class Foo4 extends { override val y = 3 } with Foo // eager forward
reference with early override
class Bar { val other = this; val y = 3; val x = other.y + 2 } // no
forward reference

object Main extends App {
println("Foo (eager forward reference): " + (new Foo).x)
println("FooLazy (lazy forward reference): " + (new FooLazy).x)
println("FooFinal (final forward reference): " + (new FooFinal).x)
println("Foo2 (eager forward reference with late override): " +
(new Foo2).x)
println("Foo3 (lazy forward reference with late override): " + (new
Foo3).x)
println("Foo4 (eager forward reference with early override): " +
(new Foo4).x)
println("Bar (no forward reference): " + (new Bar).x)

}
/*
C:\scala-2.9.1.final\examples>scala initscala.Main
Foo (eager forward reference): 2
FooLazy (lazy forward reference): 5
FooFinal (final forward reference): 5
Foo2 (eager forward reference with late override): 2
Foo3 (lazy forward reference with late override): 5
Foo4 (eager forward reference with early override): 5
Bar (no forward reference): 5
*/


rkuhn

unread,
Dec 6, 2011, 8:02:24 AM12/6/11
to scala...@googlegroups.com
Actually, the “fib” example is not a recursive function, it is really a forward reference. What the compiler must do is conceptually this:

val temp = 1 #:: ((0 #:: fib zip fib) map { case (x, y) => x + y })
val fib = temp

and since it is not possible (halting problem) to determine whether the first line actually evaluates “fib” (in the general case), it is not possible to provide an error message for uninitialized forward references. This also shows that just outlawing forward references is really a restriction. (I get the impression that initialization semantics must not be Turing complete in order to be able to definitively discriminate legitimate from illegitimate forward references, right? Wouldn’t that spread over the whole language, then?)

So, to return to the context of your message: as I said earlier, I very much agree that the compiler should warn about this on a best-effort basis. Just know that you cannot absolutely rely on the correctness of the warning.

Regards,

Roland

Eugen Labun

unread,
Dec 6, 2011, 11:52:10 AM12/6/11
to martin odersky, Daniel Sobral, Dave, scala-user
Martin, thank you for your detailed answer. I can only imagine how busy you are, and still finding
time to answer in forums to us, simple users! (A big plus for Scala, btw!)

On 2011-12-05 20:57, martin odersky wrote:
> On Sun, Dec 4, 2011 at 7:29 AM, Eugen Labun<la...@gmx.net> wrote:
>> On 2011-12-03 10:02, martin odersky wrote:
>>>
>>> At the end of https://issues.scala-lang.org/browse/SI-4856 .
>>> there's an example that shows that forward referencing vals can be useful.
>>> I quote:
>>>
>>> scala> val fib: Stream[Int] = 1 #:: ((0 #:: fib zip fib) map { case (x, y)
>>> => x + y })
>>> fib: Stream[Int] = Stream(1, ?)
>>
>>
>> Thank you, Martin, but I should agree with Dave that the usage of 'fib' here
>> (on the right-hand-side) is not really a forward reference. The parameter of
>> the '#::'-method will be taken by name, and therefore is in fact a function
>> object that is fully initialized *without accessing 'fib'* (the desugared
>> version of this expression is attached, the class of the function is
>> 'Fib$$anonfun$1').
>
> Of course. That's one half of my point! In general you will not be
> able to detect forward references statically. Subcases, yes, but in
> general no. That's why the best we can do is a warning.

I trust you, but still hoping (sorry!) that that might be possible, perhaps when some additional
conditions hold (such as, for example, a prohibition to override vals). In the fib-example it seems
to be possible. I would like to see an example where it is definitely not.

Issuing a warning would be an improvement anyway.

>> It should also be noted that this code doesn't compile if 'fib' is a local
>> val (instead of a member val), since the compiler sees it as a (formal)
>> forward reference: "error: forward reference extends over definition of
>> value fib".
>>
> Excact. Same as in Java. Java prescribes that local variables are
> guaranteed to be explicitly initialized but object fields need not be.
> The problem is considerably simpler for local variables because there
> are no aliases.

"aliases": you mean accessors generated for member vals or something else?

> But some things (e.g. fib) fall through the cracks,
> and are rejected by the stricter warning even though they are
> technically OK.

Yes. But it would be nice if the compiler could analyze the desugared version and see that it would
be working in the case of a local val, too. So that the same expression would work (or cause a
compile error) equally in both cases: if declared as a member val or as a local val.

The point seems to be to differentiate, where it is a simple forward reference, and where it gets
implicitly replaced by a function object, which is initialized without accessing the variable (i. e.
the 'val' in question).

The implicit replacement by the function object is triggered by the by-name parameters or by
implicit conversions. In either case the replacement become explicit in the desugared version of
expression and could be analyzed by compiler.

Or am I missing something?

>> But that were rather side notes. What is much more important in my opinion:
>>
>>
>> Java's automatic control over initialization, which ensures that fields and
>> variables are initialized before their usage, was an important improvement
>> as compared to C/C++.
>>
>> Scala may not be a step back.
>>
> I have argued two times now on this thread Java and Scala behave
> _exactly the same_. Before we go on, please show an example where they
> do not.

Here are two pieses of code that seem to be semantically equivalent to me:


Java: causes a compile error
--------------------------------------------------------------------
class Test {
final int x = y; // compile error: illegal forward reference (y)
final int y = 5;

public static void main(String[] args) {

Test t = new Test();
System.out.println("x = " + t.x);
System.out.println("y = " + t.y);
}
}
--------------------------------------------------------------------


Scala: compiles, 'x' is initialized with 0
(the expectation was: a compile error or initialization with 5)
--------------------------------------------------------------------
object Test {
val x: Int = y // compiles OK
val y: Int = 5

def main(args: Array[String]) {
println("x = " + x) // 0
println("y = " + y) // 5
}
}
--------------------------------------------------------------------


Yet another example, borrowed from the ticket 399 https://issues.scala-lang.org/browse/SI-399:


First, without using 'this' in the reference to 'foo':


Java: compile error
--------------------------------------------------------------------
class Foo {
{
System.out.println ( foo ); // compile error: illegal forward reference (foo)
}
final int foo = 1;
}

public class Test {


public static void main(String[] args) {

new Foo();
}
}
--------------------------------------------------------------------


Scala: compiles, output: 0
(the expectation was: a compile error or output 1)
--------------------------------------------------------------------
class Foo { // compiles OK
println ( foo ) // prints 0
val foo = 1
}

object Test {
def main(args: Array[String]) {
new Foo
}
}
--------------------------------------------------------------------

Now with 'this' in the reference to 'foo' (i.e. 'this.foo'):

Scala's behavior remains unchanged.

Java version surprisingly compiles now, but the output is 1, which is OK.
--------------------------------------------------------------------
class Foo { // compiles OK
{
System.out.println ( this.foo ); // prints 1
}
final int foo = 1;
}

public class Test {


public static void main(String[] args) {

new Foo();
}
}
--------------------------------------------------------------------

Environment:

Java JDK: 1.6.0_26
Scala: 2.9.1.final
OS: WinXP SP3


>> At the beginning I was delighted with Scala features such as the uniform
>> access principle or the ability to override member fields. But if it comes
>> at the costs of losing automatic control over the initialization state, I
>> would rather give up some of these new features than vice versa.
>>
> Neither Java nor Scala give you automatic control over initialization state.
>
>>
>> Assuming, you would agree, which options do we have?
>>
>> I see the following:
>>

>> 1) We could disallow overriding vals¹, if it will make possible (does it?)


>> to get automatic initialization control.
>>
> It won't. To find out more, here's a reference:
>
> Manuel Fähndrich, K. Rustan M. Leino: Declaring and checking non-null
> types in an object-oriented language. OOPSLA 2003: 302-312.
>
> The paper shows among others what you need to do to get static
> initialization checking. The type system is very complicated and still
> very restrictive. It's also strictly research. It was implemented in
> Spec# (I believe), but never in a mainstream programming language.

Thank you for the reference (I used [1]), but IIUC the article handles a slightly different set of
problems. Namely, detecting null-values *after* the corresponding fields already been initialized,
but not yet reassigned. See e.g. the code on p.2: the non-final string 'name' has been already
initialized with the default value (since it has no initializer), and the whole discussion is about
preventing access to the 'name' before it get a new non-null value in the constructor. The keyword
'final' is nowhere used in code.

That is different from our discussion about initializing 'final' values.

In my understanding, the goal in Java is to make this initialization (which might be technically a
two-step process from the default to the specified value), working as atomic for the user.

In Scala, there are additional complications due to field+accessor implementation of vals.
Nevertheless, the goal is the same.

The authors say though that their "partially-initialized types" may help also with problems that
targeting 'final' values. (That is briefly mentioned in the last two paragraphs of the last section:
see p.10 before 'REFERENCES'.) They say further: "for any readonly (in C#) or final (in Java) field
f, after the allocation of an object x and before the assignment to x.f, reading x.f will return a
zero-equivalent value". That is what I mean with "technically, a two-step process".
And is also the point of our discussion: make sure that the value is fully initialized before its
reading or prohibit the reading by generating a compile error.

And I'm hoping that this is feasible, at least for the most situations (like the Java/Scala examples
above).


>> 2) Another option would be to handle all member-'val's as 'lazy vals'².


>> As an addition, perhaps, it would make sense to introduce another keywords
>> for local variables (which are simple old variables) as opposed to member
>> fields (which are field/accessor[/mutator] pairs/triples)?
>>
> We considered that. But lazy vals interact badly with effects. If you
> need effects to happen during object initialization, lazy vals are
> problematic.

What do you mean with "interact badly with effects"?
I'm asking because this approach seems to be an interesting and robust solution!
Also any references will be highly appreciated.

Thank you,
Eugen

[1] http://research.microsoft.com/en-us/um/people/leino/papers/krml109.pdf

Eugen Labun

unread,
Dec 6, 2011, 12:10:36 PM12/6/11
to scala...@googlegroups.com, rkuhn
Hi Roland,

On 2011-12-06 14:02, rkuhn wrote:
> Actually, the “fib” example is not a recursive function, it is really a forward reference. What the
> compiler must do is conceptually this:
>
> val temp = 1 #:: ((0 #:: fib zip fib) map { case (x, y) => x + y })
> val fib = temp

no, the 'fib' (and the whole part after "1 #:: ") gets replaced by something like 'new
Anon$$Func$$()'. 'fib' is not read during the instance creation. So, no 'temp' variable is needed,
and so this is (technically) not a forward reference.

--
Eugen

rkuhn

unread,
Dec 6, 2011, 1:05:57 PM12/6/11
to scala...@googlegroups.com, rkuhn
The fact that “fib” is not read during the construction of the closure is not something that the compiler can statically prove: the closure receives “this” as constructor parameter precisely in order to be able to access “fib”, so how should the compiler know that #:: does NOT evaluate the closure? And the return of #:: is what gets stored in the “fib” field, so, yes, this really is a forward reference, and a clear example of why the compiler cannot reliably warn about this.

Replying to your other mail to Martin: desugaring does not help at all, since the behavior of #:: is not statically known (i.e. it is not part of the type signature that this method does not evaluate the by-name parameter).

Regards,

Roland

Dave

unread,
Dec 6, 2011, 2:11:13 PM12/6/11
to scala-user

On 6 dec, 14:02, rkuhn <goo...@rkuhn.info> wrote:
> Actually, the “fib” example is not a recursive function, it is really a
> forward reference. What the compiler must do is conceptually this:
>
> val temp = 1 #:: ((0 #:: fib zip fib) map { case (x, y) => x + y })
> val fib = temp
>
> and since it is not possible (halting problem) to determine whether the
> first line actually evaluates “fib” (in the general case),

The left hand side expression and right hand side of the expression
are evaluated as one whole statement, not two separate statements.
To determine the return type therefore an explicit type annotation is
needed.

You could also rewrite it like:
scala> def temp : Stream[Int] = 1 #:: ((0 #:: temp zip temp) map


{ case (x, y) =
> x + y })

temp: Stream[Int]

scala> val fib = temp


fib: Stream[Int] = Stream(1, ?)

scala> fib take 10 toList


res0: List[Int] = List(1, 1, 2, 3, 5, 8, 13, 21, 34, 55)

then it is clearly visible that it is a recursive function.

What is the difference with:
scala> def fib(i: Int): Int = i match {
| case _ if i <= 1 => i
| case _ => fib(i-1) + fib(i-2)
| }
fib: (i: Int)Int

scala> fib(5)
res1: Int = 5

Except the stream is infinite and the other is finite

Eugen Labun

unread,
Dec 6, 2011, 3:04:58 PM12/6/11
to scala...@googlegroups.com, rkuhn
On 2011-12-06 19:05, rkuhn wrote:
>
>
> Am Dienstag, 6. Dezember 2011 18:10:36 UTC+1 schrieb Eugen Labun:
>
> Hi Roland,
>
> On 2011-12-06 14:02, rkuhn wrote:
> > Actually, the “fib” example is not a recursive function, it is really a forward reference.
> What the
> > compiler must do is conceptually this:
> >
> > val temp = 1 #:: ((0 #:: fib zip fib) map { case (x, y) => x + y })
> > val fib = temp
>
> no, the 'fib' (and the whole part after "1 #:: ") gets replaced by something like 'new
> Anon$$Func$$()'. 'fib' is not read during the instance creation. So, no 'temp' variable is needed,
> and so this is (technically) not a forward reference.
>
>
> The fact that “fib” is not read during the construction of the closure is not something that the
> compiler can statically prove: the closure receives “this” as constructor parameter precisely in
> order to be able to access “fib”, so how should the compiler know that #:: does NOT evaluate the
> closure? And the return of #:: is what gets stored in the “fib” field, so, yes, this really is a
> forward reference, and a clear example of why the compiler cannot reliably warn about this.

You are right! I somehow overseen that the closure (i.e. the constructor of the anon. function)
takes 'this' as parameter, and that there is no info about what could happen in the method itself.
Sorry.

Then how about following:

forward_reference match {
case by-name-parameter => warning "reference might not be initialized"
case _ => error "illegal forward reference"
}

?


> Replying to your other mail to Martin: desugaring does not help at all, since the behavior of #:: is
> not statically known (i.e. it is not part of the type signature that this method does not evaluate
> the by-name parameter).

Yes, I see now. Thank you!


Regards,
Eugen

rkuhn

unread,
Dec 6, 2011, 3:18:28 PM12/6/11
to scala...@googlegroups.com

Yes, would look reasonable, but I sympathize with Martin on this one: if the rule were that if the compiler can prove you wrong it is an error, then people would probably be disappointed in cases where it fails to do so. Since the border of what can be proven and what not (and I mean “practically”, not “theoretically”) is a rather convoluted construct, that would be very difficult to explain, and since an error discriminates between legal and illegal programs this would make the language spec “indeterministic” in a sense (i.e. as seen from the user). Thus, the best we can hope for is a warning which is as reliable as it can get without exploding the build time.
 
Regards,

Roland

rkuhn

unread,
Dec 6, 2011, 3:28:03 PM12/6/11
to scala...@googlegroups.com


Am Dienstag, 6. Dezember 2011 20:11:13 UTC+1 schrieb Dave:

On 6 dec, 14:02, rkuhn <goo...@rkuhn.info> wrote:
> Actually, the “fib” example is not a recursive function, it is really a
> forward reference. What the compiler must do is conceptually this:
>
> val temp = 1 #:: ((0 #:: fib zip fib) map { case (x, y) => x + y })
> val fib = temp
>
> and since it is not possible (halting problem) to determine whether the
> first line actually evaluates “fib” (in the general case),

The left hand side expression and right hand side of the expression
are evaluated as one whole statement, not two separate statements.
To determine the return type therefore an explicit type annotation is
needed.


Functionally or conceptually that may be true, but computers execute sequences of commands, and this sequence must compute the rvalue before assigning to the lvalue, which means that evaluating the rvalue must not actually read the lvalue. Which is, of course, the core of the problem (i.e. that a program cannot determine whether another program does a specific thing without actually running it, which may not ever terminate).
 

You could also rewrite it like:
scala> def temp : Stream[Int] = 1 #:: ((0 #:: temp zip temp) map
{ case (x, y) =
> x + y })
temp: Stream[Int]


Yes, you _could_ rewrite it, but then you change it from a clever forward reference to an eerily mind-boggling recursive method ;-)
 

Eugen Labun

unread,
Dec 9, 2011, 7:41:30 AM12/9/11
to martin odersky, Daniel Sobral, Dave, scala-user
Any comments on the code examples that showing inconsistencies between Java and Scala?

http://groups.google.com/group/scala-user/msg/36376d8fae5fdf41

Thank you,
Eugen

Daniel Sobral

unread,
Dec 9, 2011, 9:04:41 AM12/9/11
to Eugen Labun, martin odersky, Dave, scala-user
On Fri, Dec 9, 2011 at 10:41, Eugen Labun <la...@gmx.net> wrote:
> Any comments on the code examples that showing inconsistencies between Java
> and Scala?
>
> http://groups.google.com/group/scala-user/msg/36376d8fae5fdf41

final int x != val x: Int
final int x == final val x: Int

Eugen Labun

unread,
Dec 9, 2011, 11:20:03 AM12/9/11
to Daniel Sobral, martin odersky, Dave, scala-user
On 2011-12-09 15:04, Daniel Sobral wrote:
> On Fri, Dec 9, 2011 at 10:41, Eugen Labun<la...@gmx.net> wrote:
>> Any comments on the code examples that showing inconsistencies between Java
>> and Scala?
>>
>> http://groups.google.com/group/scala-user/msg/36376d8fae5fdf41
>
> final int x != val x: Int
> final int x == final val x: Int

I changed the Scala code in examples accordingly (i.e. to "final val x: Int = ...") and retested.
(Although I think that 'final' in Scala adds another semantics that are not present in Java).

The results are exactly the same!


Dave

unread,
Dec 9, 2011, 11:46:15 AM12/9/11
to scala-user
> I changed the Scala code in examples accordingly (i.e. to "final val x: Int
> = ...") and retested.
> (Although I think that 'final' in Scala adds another semantics that are not
> present in Java).
>
> The results are exactly the same!

(I'm not on scala-user.) Try this one to see 5/5. (I'm not suggesting
this is super intuitive.)

object Test {
final val x = y // compiles OK
final val y = 5

def main(args: Array[String]) {

println("x = " + x) // 5


println("y = " + y) // 5
}
}


C:\scala-2.9.1.final\examples>scalac testscala.scala

C:\scala-2.9.1.final\examples>scala testscala.Test
x = 0
y = 5

Are you testing with 2.10 ?

Eugen Labun

unread,
Dec 9, 2011, 11:58:35 AM12/9/11
to Dave, scala-user
> ... Try this one to see 5/5. ...

>
> object Test {
> final val x = y // compiles OK
> final val y = 5
>
> def main(args: Array[String]) {
> println("x = " + x) // 5
> println("y = " + y) // 5
> }
> }
>
>
> C:\scala-2.9.1.final\examples>scalac testscala.scala
>
> C:\scala-2.9.1.final\examples>scala testscala.Test
> x = 0
> y = 5

But you get 0/5, too (not 5/5). Or am I missing something?

>
> Are you testing with 2.10 ?

I'm testing with Scala 2.9.1.final on Windows XP SP3.

Alec Zorab

unread,
Dec 9, 2011, 12:00:49 PM12/9/11
to Eugen Labun, Dave, scala-user
just tried this - I get 5/5 with final and 0/5 without. scala 2.9.1,
win7, java 6u29

Eugen Labun

unread,
Dec 9, 2011, 12:05:15 PM12/9/11
to Dave, scala-user
> But you get 0/5, too (not 5/5). Or am I missing something?
>
>>
>> Are you testing with 2.10 ?
> I'm testing with Scala 2.9.1.final on Windows XP SP3.

Oh, sorry. That was your answer to Pauls mail (which was not CCed to the forum).

The fact that we get different results from the same code make things much more interesting!

Dave Mahabiersing

unread,
Dec 9, 2011, 12:10:50 PM12/9/11
to scala...@googlegroups.com, pa...@improving.org, la...@gmx.net, dcso...@gmail.com, martin....@epfl.ch
Strange.
same with or without the package testscala doesn't matter
I have
Windows 7 Home Premium 32 bit + SP1
tested with
scala 2.9.1.final
jdk1.7_01
jdk1.6_18
 
> Date: Fri, 9 Dec 2011 18:05:15 +0100
> From: la...@gmx.net
> To: dave.mah...@hotmail.com
> CC: scala...@googlegroups.com
> Subject: Re: [scala-user] Re: surprised by class initialization

Eugen Labun

unread,
Dec 9, 2011, 12:12:08 PM12/9/11
to Alec Zorab, Dave, scala-user
On 2011-12-09 18:00, Alec Zorab wrote:
> just tried this - I get 5/5 with final and 0/5 without. scala 2.9.1,
> win7, java 6u29

And for me the type annotation ": Int" makes the difference!

This prints 5/0:
----------------------------------------
object Test {
final val x: Int = y // compiles OK
final val y: Int = 5

def main(args: Array[String]) {

println("x = " + x) // 0


println("y = " + y) // 5
}
}

----------------------------------------

And this 5/5:
----------------------------------------


object Test {
final val x = y // compiles OK
final val y = 5

def main(args: Array[String]) {
println("x = " + x) // 5
println("y = " + y) // 5
}
}

----------------------------------------

Wow!

Daniel Sobral

unread,
Dec 9, 2011, 12:18:00 PM12/9/11
to Eugen Labun, Dave, scala-user

On http://www.simplyscala.com/:

Creating user space...
Ready for code.


object Test {
final val x = y // compiles OK
final val y = 5

def main(args: Array[String]) {
println("x = " + x) // 5
println("y = " + y) // 5
}
}

defined module Test
Test.main(null)
x = 5
y = 5

Daniel Sobral

unread,
Dec 9, 2011, 12:18:32 PM12/9/11
to Eugen Labun, Alec Zorab, Dave, scala-user
Ah, yes. There's that...

--

Dave Mahabiersing

unread,
Dec 9, 2011, 12:25:21 PM12/9/11
to la...@gmx.net, alec...@googlemail.com, scala...@googlegroups.com
For me too
C:\scala-2.9.1.final\examples>scala Test

x = 5
y = 5 
> Date: Fri, 9 Dec 2011 18:12:08 +0100
> From: la...@gmx.net
> To: alec...@googlemail.com
> CC: dave.mah...@hotmail.com; scala...@googlegroups.com

> Subject: Re: [scala-user] Re: surprised by class initialization
>
Reply all
Reply to author
Forward
0 new messages