Skip to content

Allow for an overriden lazy val to be accessed through super #1999

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

Closed
fare opened this issue Feb 18, 2017 · 12 comments
Closed

Allow for an overriden lazy val to be accessed through super #1999

fare opened this issue Feb 18, 2017 · 12 comments

Comments

@fare
Copy link

fare commented Feb 18, 2017

When programming in jsonnet (pure lazy functional-object dynamic language), it is great to be able to use inheritance on lazy vals (all vals are lazy in jsonnet).

To reproduce the same pattern in Scala, I have to define two different entities,

lazy val lv = computeLv
def computeLv = ...

Then, when using the lazy val I must use lv, but when I want to use inheritance, I must override computeLv instead. That's awkward.

def usingLv = ... lv ...
override def computeLv = ...

I propose that there should always be an underlying computeLv, that is overridden when you override the lazy val, so that you can use lv both when referring to the value and when overriding it.

@DarkDimius
Copy link
Contributor

Welcome to Scala 2.11.8 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_45).
Type in expressions for evaluation. Or try :help.

scala> class A { lazy val s = 1}
defined class A

scala> class B extends A { override lazy val s =  2}
defined class B

Same with Dotty. Did I misunderstand what you asked?

@fare
Copy link
Author

fare commented Feb 18, 2017

class A { lazy val s = 1 }
trait B extends A { override lazy val s = super.s + 1 }
trait C extends A { override lazy val s = super.s * 2 }
trait D extends A with B with C
new D {}.s

I expect the answer 4

@DarkDimius DarkDimius changed the title Allow for inheritance of lazy val Allow for an overriden lazy val to be accessed through super Feb 18, 2017
@fare
Copy link
Author

fare commented Feb 18, 2017

The above obviously works if you use def instead of lazy val.

@DarkDimius
Copy link
Contributor

DarkDimius commented Feb 18, 2017

ok, let me ask several follow-up questions which are believe are the reasons why this is prohibited.
I'm going to write example below and please tell me how you think they should evaluate, given that the underlying fields should never be re-assigned(see #1856 for discussion why).

trait A { lazy val s = {println("A"); 1} }
trait B extends A { override lazy val s = {println("B"); super.s + super.s} }
class C extends B { override lazy val s = {println("C"); super.s + super.s} }
new C.s

edit: changed A from class to trait

@fare
Copy link
Author

fare commented Feb 18, 2017

jsonnet -

local A = { s: 1, l: ["A"] };
local B = A { s: super.s + super.s, l+: ["B"] };
local C = B { s: super.s + super.s, l+: ["C"] };
[C.s, C.l]

jsonnet says [4, ["A", "B", "C"]]

@DarkDimius
Copy link
Contributor

In jsonnet language the code that you've wrote isn't a single assignment with a side-effect, but two independent assignments.

local A = { s: 1, l: ["A"] };

the initialization of s and l are saparate, so this it doesn't showcase the issue.
In absence of side-effects you are free to recompute a field multiple times, so I'm not sure if the issue can be observed from jsonnet.

@fare
Copy link
Author

fare commented Feb 18, 2017

jsonnet computes fields a single time.

But yes, I see that super.s needs to itself be a lazy val for that to happen, so that it's not enough to reuse java inheritance on an underlying computation function.

@fare
Copy link
Author

fare commented Feb 18, 2017

Though that's better than nothing.

@fare
Copy link
Author

fare commented Feb 18, 2017

In particular, lack of caching matters only when OTHER methods refer to the method's super value. It only one method refers to it, then it can manually cache the value in a val.

One ugly implementation strategy: a hidden field (or a set of hidden fields) contains a cache of (SuperTrait, LazyFieldName) => LazyCell, and the super.x accessors use that. Ouch.

@DarkDimius
Copy link
Contributor

DarkDimius commented Feb 18, 2017

It only one method refers to it, then it can manually cache the value in a val.

you can't make such an assumption and you don't know the entire world. Someone-somewhere else may be sub-classing the same trait and also accessing the same super-lazy val.

It is technically possible in example above to create 3 underlying fields in C for all the mixed-in lazy vals by discovering that traits needed super-accessors. Note that with all super-calls in traits, adding a super-call would be binary incompatible change.

This can be implemented, the implementation would need to introduce crosstalk between SuperAccessors and LazyVals.

While this is possible, I would expect the implementation to be a can of worms and, given that there's a simple workaround, this will not have high priority.

@fare
Copy link
Author

fare commented Feb 18, 2017

What about this implementation strategy:

  1. every lazy val has an outer user access function and supporting cache field, as usual. The cache field (or a second, slower cache field) is a (sparse if needs be?) vector indexed by the position of each class in the class precedence list. (Or if you're willing to recompile on method add/substract, a dense vector indexed by class precedence list filtered for presence of a method definition).
  2. every definition and/or override gets passed this index by the super mechanism to fill in the correct array entry (alternatively, it can have this index wired in, which is faster but requires recompilation on method add/substract, or it can compute it via reflection which is slower).

Also, what about having a decent macro system a la Racket, so it can all be implemented as an independent module?

@odersky
Copy link
Contributor

odersky commented Feb 22, 2017

I think the cost of the change far outweighs the possible benefits, so I am going to close this one.

@odersky odersky closed this as completed Feb 22, 2017
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

3 participants