Constructor argument gets compiled into a field (should it?)

49 views
Skip to first unread message

Alexey Goncharuk

unread,
Feb 20, 2012, 8:43:54 AM2/20/12
to scala-user
Hello everybody, I'm quite new to scala and recently faced behavior
which I cannot explain.

Consider the following examlpe:

object Convert {
def asHumanStringFunc(f: => Boolean): String = {
if (f) "on" else "off"
}

def asHumanString(f: Boolean): String = {
asHumanStringFunc(f)
}
}

class B {
var someProperty: Boolean = false
}

trait A {
def prop: String
}

class AImpl(b: B) {
// Here is the point
val prop = Convert.asHumanStringFunc(b.someProperty)
}

object Test{
def main(args:Array[String]){
val b: B = new B

val a = new AImpl(b)

println(a.prop)

b.someProperty = true

println(a.prop)


println(Arrays.toString(a.getClass.getDeclaredFields.asInstanceOf[Array[Object]]))
}
}

In AImpl constructor field prop is initialized with some value that
does not depend on any external changes. Since constructor argument b
is not used in any function, I expected that it would be excluded from
the object fields, however, it becomes the public field of AImpl. If I
change asHumanStringFunc to asHumanString in AImpl constructor, this
field disappears. Can anyone explain why does scala embed constructor
argument to the object field list in this case?

Thanks in advance.

Guillaume Yziquel

unread,
Feb 20, 2012, 11:50:26 AM2/20/12
to Alexey Goncharuk, scala-user
Le Monday 20 Feb 2012 � 05:43:54 (-0800), Alexey Goncharuk a �crit :

>
> object Convert {
> def asHumanStringFunc(f: => Boolean): String = {
> if (f) "on" else "off"
> }
>
> def asHumanString(f: Boolean): String = {
> asHumanStringFunc(f)
> }
> }
>
> class B {
> var someProperty: Boolean = false
> }
>
> class AImpl(b: B) {
> // Here is the point
> val prop = Convert.asHumanStringFunc(b.someProperty)
> }
>
> In AImpl constructor field prop is initialized with some value that
> does not depend on any external changes. Since constructor argument b
> is not used in any function, I expected that it would be excluded from
> the object fields, however, it becomes the public field of AImpl. If I
> change asHumanStringFunc to asHumanString in AImpl constructor, this
> field disappears. Can anyone explain why does scala embed constructor
> argument to the object field list in this case?
>
> Thanks in advance.

Using f: => Boolean instead of f: Boolean likely implies operational
semantics which requires the argument to be managed by the GC. The
workings of the JVM GC likely require the value to be stored in some
field somewhere.

This is just a wild guess, though.

--
Guillaume Yziquel

Guillaume Yziquel

unread,
Feb 20, 2012, 12:49:26 PM2/20/12
to Alexey Goncharuk, scala-user
Le Monday 20 Feb 2012 � 17:50:26 (+0100), Guillaume Yziquel a �crit :

> Le Monday 20 Feb 2012 � 05:43:54 (-0800), Alexey Goncharuk a �crit :
> >
> > In AImpl constructor field prop is initialized with some value that
> > does not depend on any external changes. Since constructor argument b
> > is not used in any function, I expected that it would be excluded from
> > the object fields, however, it becomes the public field of AImpl. If I
> > change asHumanStringFunc to asHumanString in AImpl constructor, this
> > field disappears. Can anyone explain why does scala embed constructor
> > argument to the object field list in this case?
> >
> > Thanks in advance.
>
> Using f: => Boolean instead of f: Boolean likely implies operational
> semantics which requires the argument to be managed by the GC. The
> workings of the JVM GC likely require the value to be stored in some
> field somewhere.
>
> This is just a wild guess, though.

You should perhaps try replacing

> > val prop = Convert.asHumanStringFunc(b.someProperty)

by the following

val prop = {
val bb = b
Convert.asHumanStringFunc(bb.someProperty)
}

and check whether or not the field is still present. With a little luck,
it should disappear.

As to the public qualifier, I suspect that it's because you haven't
specified b as being private[this] that the field ends up being public
as soon as his presence is deemed necessary for runtime reasons (i.e.
lazyness of f: => Boolean). After all, public is the default.

--
Guillaume Yziquel

Bernd Johannes

unread,
Feb 20, 2012, 4:09:04 PM2/20/12
to Alexey Goncharuk, scala-user

Am Montag, 20. Februar 2012, 14:43:54 schrieb Alexey Goncharuk:


Hello Alexey - I am not sure what you want to achieve exactly - maybe something like this:


Welcome to Scala version 2.9.0.1 (Java HotSpot(TM) 64-Bit Server VM, Java 1.6.0_29).

Type in expressions to have them evaluated.

Type :help for more information.


scala> :paste

// Entering paste mode (ctrl-D to finish)


object Test {

def render(source: =>Boolean): String = source.toString

def test() {

var bool = false

val renderme = () => render(bool)


println(renderme())

bool = true

println(renderme())


}

}


// Exiting paste mode, now interpreting.


defined module Test


scala> Test.test

false

true


I guess you expect "val prop" to be a function object which it isn't. It gets evaluated at once and contains an immutable String value.


Greetings

Bernd

Rex Kerr

unread,
Feb 20, 2012, 4:34:57 PM2/20/12
to Alexey Goncharuk, scala-user
This is as it should (must?) be, but it's a little tricky to work out why.

First, look at

  f: => Boolean

Although this is called a "by-name parameter", it is actually implemented as a Function0,

  f: () => Boolean

just with different syntax used on both ends.  Now, this function has to be able to have access to

  b.someProperty

and since it evaluates lazily (that's the whole point of by-name calling!) it needs to get access to b to do it.  That is, the function is going to look like

  new Function0[Boolean] {
    def apply() = b.someProperty
  }

But, wait...how does it get b?  What I've written clearly isn't going to work since b isn't in scope.  You really need something like one of

  class ByName(b: B) extends Function0[Boolean] { ... }
  class ByName(a: AImpl) extends Function0[Boolean] { ... }

where the latter is preferred if b might change (since then it would delay being evaluated until it was actually used, which is again the whole point of by-name parameters).  So it does in fact do it the latter way.

But now we have a problem: even though b only exists as a constructor argument of AImpl, this new class needs access to it, and it needs access as late as possible.  So we have to store that argument as a field in AImpl, and, since it's an entirely separate class, make that field public.

And that's exactly what the compiler does behind the scenes.

  --Rex

Alexey Goncharuk

unread,
Feb 21, 2012, 12:33:03 AM2/21/12
to scala-user
Rex, thank you for the detailed explanation. Now I see that compiler
does not know (and can not) that the function is evaluated only once
during constructor call. And now I see why example suggested by
Guillaume Yziquel works. This was very helpful!
> <agoncha...@gridgain.com>wrote:
>
>
>
>
>
>
>
> > Hello everybody, I'm quite new to scala and recently faced behavior
> > which I cannot explain.
>
> > Consider the following examlpe:
>
> > object Convert {
> >    def asHumanStringFunc(f: => Boolean): String = {
> >        if (f) "on" else "off"
> >    }
>
> >    def asHumanString(f: Boolean): String = {
> >        asHumanStringFunc(f)
> >    }
> > }
>
> > class B {
> >    var someProperty: Boolean = false
> > }
>
> > trait A {
> >    def prop: String
> > }
>
> > class AImpl(b: B) {
> >    // Here is the point
> >    val prop = Convert.asHumanStringFunc(b.someProperty)
> > }
>
> > object Test{
> >    def main(args:Array[String]){
> >        val b: B = new B
>
> >        val a = new AImpl(b)
>
> >        println(a.prop)
>
> >        b.someProperty = true
>
> >        println(a.prop)
>
> > println(Arrays.toString(a.getClass.getDeclaredFields.asInstanceOf[Array[Obj ect]]))
Reply all
Reply to author
Forward
0 new messages