This page is no longer maintained — Please continue to the home page at www.scala-lang.org

abstracts defs & vals & self-esteem issues

7 replies
extempore
Joined: 2008-12-17,
User offline. Last seen 35 weeks 3 days ago.

Working my way through the collections classes attempting to reintegrate
those left behind, I discovered all the clone methods are totally broken.
Each one looks about like this:

override def clone(): Stack[A] = new StackProxy[A] {
def self = StackProxy.this.self.clone()
}

Notice "def self" as opposed to "val self". This means your cloned
proxy is going to clone a new collection from the original every time it
references self (which is essentially on every method, since that's what
the proxies do - forward method calls to self.) You may think you just
added some elements to your stack, but you actually added them to a very
short-lived clone which your proxy immediately forgot about.

I can only figure nobody ever used these clone methods since this would
be hard to miss, at least on mutable collections.

This issue was only possible because scala.Proxy has no expectations
about "self" even though the proxy classes make no sense without some
consistent sense of self:

trait Proxy {
def self: Any // I should really be some sort of val
}

Given scalac's current abilities it is not so obvious how to fix this,
thus this email. Okay, it is easy enough to make it a val here and in
all the other proxies, but the underlying issue has come up for me many
times, and it is: there's no way to declare an abstract val that doesn't
come with big traps.

We're all probably familiar with this one by now:

abstract class Foo { val ok: Boolean ; val isOK = ok.toString }
(new Foo { val ok = true }).isOK
res2: java.lang.String = false
(new { val ok = true } with Foo).isOK // so we should do this instead
res3: java.lang.String = true

However if you are writing traits you intend to be composed (by
yourself, or especially by anyone else) expecting early defs syntax is a
totally unreasonable burden, especially because it doesn't fail to
compile - it just silently uses the uninitialized value. Even though I
should know all about this by now, I was stumped for quite a while
recently as to why a boolean had the wrong value, because the pieces
came together less obviously than in this example.

What I'd usually like to say is:

scala> abstract class Foo { lazy val ok: Boolean }

but

:1: error: lazy values may not be abstract

And sadness ensues. Is there a fundamental reason for this, or is it
just a matter of implementing it?

So what Proxy does, and I usually end up doing, is:

abstract class Foo { def ok: Boolean }

Now the initialization order issue goes away, but everyone who uses it
has to know and remember to implement it with a val, else we get all the
broken clone methods above.

There is some temptation to write

lazy val ok: Boolean = throw new Exception("I am abstract!")

...and make subclasses override it since at least that way it fails
loudly, but I'm sure abstract lazy val would be a lot nicer for all.

Or, maybe I'm missing some better way?

odersky
Joined: 2008-07-29,
User offline. Last seen 45 weeks 6 days ago.
Re: abstracts defs & vals & self-esteem issues

Hi Paul,

Why not use an abstract val in Proxy and lazy vals in implementations of it?

Cheers

Kris Nuttycombe
Joined: 2009-01-16,
User offline. Last seen 42 years 45 weeks ago.
Re: abstracts defs & vals & self-esteem issues

Wasn't this really his main complaint?

>> However if you are writing traits you intend to be composed (by
>> yourself, or especially by anyone else) expecting early defs syntax is a
>> totally unreasonable burden, especially because it doesn't fail to
>> compile - it just silently uses the uninitialized value.

I've been bitten by this one before too, with the result of much confusion.

Kris

On Tue, Jun 16, 2009 at 10:38 AM, martin odersky wrote:
> Hi Paul,
>
> Why not use an abstract val in Proxy and lazy vals in implementations of it?
>
> Cheers
>
>  -- Martin
>

dubochet
Joined: 2008-06-30,
User offline. Last seen 1 year 36 weeks ago.
Re: abstracts defs & vals & self-esteem issues

Hello.

Concerning the problems with clone, I am just fixing these now (in
relation to #2021).

I don't want to enter the discussion on the abstract val story, but I
share your concern: there is a certain kind of declaration, between
val and def, that is often desirable to write but currently impossible.

Cheers,
Gilles.

> Working my way through the collections classes attempting to
> reintegrate
> those left behind, I discovered all the clone methods are totally
> broken.
> Each one looks about like this:
>
> override def clone(): Stack[A] = new StackProxy[A] {
> def self = StackProxy.this.self.clone()
> }
>
> Notice "def self" as opposed to "val self". This means your cloned
> proxy is going to clone a new collection from the original every
> time it
> references self (which is essentially on every method, since that's
> what
> the proxies do - forward method calls to self.) You may think you just
> added some elements to your stack, but you actually added them to a
> very
> short-lived clone which your proxy immediately forgot about.
>
> I can only figure nobody ever used these clone methods since this
> would
> be hard to miss, at least on mutable collections.
>
> This issue was only possible because scala.Proxy has no expectations
> about "self" even though the proxy classes make no sense without some
> consistent sense of self:
>
> trait Proxy {
> def self: Any // I should really be some sort of val
> }
>
> Given scalac's current abilities it is not so obvious how to fix this,
> thus this email. Okay, it is easy enough to make it a val here and in
> all the other proxies, but the underlying issue has come up for me
> many
> times, and it is: there's no way to declare an abstract val that
> doesn't
> come with big traps.
>
> We're all probably familiar with this one by now:
>
> abstract class Foo { val ok: Boolean ; val isOK = ok.toString }
> (new Foo { val ok = true }).isOK
> res2: java.lang.String = false
> (new { val ok = true } with Foo).isOK // so we should do this instead
> res3: java.lang.String = true
>
> However if you are writing traits you intend to be composed (by
> yourself, or especially by anyone else) expecting early defs syntax
> is a
> totally unreasonable burden, especially because it doesn't fail to
> compile - it just silently uses the uninitialized value. Even
> though I
> should know all about this by now, I was stumped for quite a while
> recently as to why a boolean had the wrong value, because the pieces
> came together less obviously than in this example.
>
> What I'd usually like to say is:
>
> scala> abstract class Foo { lazy val ok: Boolean }
>
> but
>
> :1: error: lazy values may not be abstract
>
> And sadness ensues. Is there a fundamental reason for this, or is it
> just a matter of implementing it?
>
> So what Proxy does, and I usually end up doing, is:
>
> abstract class Foo { def ok: Boolean }
>
> Now the initialization order issue goes away, but everyone who uses it
> has to know and remember to implement it with a val, else we get all
> the
> broken clone methods above.
>
> There is some temptation to write
>
> lazy val ok: Boolean = throw new Exception("I am abstract!")
>
> ...and make subclasses override it since at least that way it fails
> loudly, but I'm sure abstract lazy val would be a lot nicer for all.
>
> Or, maybe I'm missing some better way?
>

extempore
Joined: 2008-12-17,
User offline. Last seen 35 weeks 3 days ago.
Re: abstracts defs & vals & self-esteem issues

On Tue, Jun 16, 2009 at 06:38:00PM +0200, martin odersky wrote:
> Why not use an abstract val in Proxy and lazy vals in implementations
> of it?

This may be the best combination available right now, but it still
imposes the same "don't forget to declare 'lazy' or your class will be
silently broken" detail on everyone who uses it, every time they use it.
Unless of course their class involves no regular vals whatsoever, only
lazy vals and defs, but that is a tough and not very attractive
criterion to meet.

dubochet
Joined: 2008-06-30,
User offline. Last seen 1 year 36 weeks ago.
Re: abstracts defs & vals & self-esteem issues

Hello.

>> Why not use an abstract val in Proxy and lazy vals in implementations
>> of it?
>
> This may be the best combination available right now, but it still
> imposes the same "don't forget to declare 'lazy' or your class will be
> silently broken" detail on everyone who uses it, every time they use
> it.
> Unless of course their class involves no regular vals whatsoever, only
> lazy vals and defs, but that is a tough and not very attractive
> criterion to meet.

The implementation scheme for *Proxy classes I am leaning towards
right now is as follows:

> trait StackProxy[A] extends Stack[A] with SeqProxy[A] {
>
> def self: Stack[A]
> protected def self_=(q: Stack[A]): Unit
>
> ...
>
> override def clone(): Stack[A] = {
> val clonedInst = super.clone.asInstanceOf[StackProxy[A]]
> clonedInst.self = StackProxy.this.self.clone()
> clonedInst
> }
>
> }

Basically, in the simplest case, implementations of the proxy would
just have to define self as a var.

Feel free to complain if you have issues with this scheme.

Cheers,
Gilles.

extempore
Joined: 2008-12-17,
User offline. Last seen 35 weeks 3 days ago.
Re: abstracts defs & vals & self-esteem issues

On Tue, Jun 16, 2009 at 09:32:28PM +0200, Gilles Dubochet wrote:
> Basically, in the simplest case, implementations of the proxy would
> just have to define self as a var.
>
> Feel free to complain if you have issues with this scheme.

Only my slightly irrational but powerful distaste for vars.

Assuming that means you are working on the Proxy classes - I don't want
to collide with you but I have buffer proxy in pieces on my floor right
now, so if you find a good spot for committing any of it it might save a
little merge pain later.

odersky
Joined: 2008-07-29,
User offline. Last seen 45 weeks 6 days ago.
Re: abstracts defs & vals & self-esteem issues

On Tue, Jun 16, 2009 at 9:18 PM, Paul Phillips wrote:
> On Tue, Jun 16, 2009 at 06:38:00PM +0200, martin odersky wrote:
>> Why not use an abstract val in Proxy and lazy vals in implementations
>> of it?
>
> This may be the best combination available right now, but it still
> imposes the same "don't forget to declare 'lazy' or your class will be
> silently broken" detail on everyone who uses it, every time they use it.
> Unless of course their class involves no regular vals whatsoever, only
> lazy vals and defs, but that is a tough and not very attractive
> criterion to meet.
>
That's correct. I think any resolution of this will have to come from
the updated early defs proposal. Essentially, it should be possible to
define an abstract early val. That could then be implemented by either
a concrete early val or a lazy val. But a plain val would not do. Ingo
is working with me on a concrete design for this.

Cheers

Copyright © 2012 École Polytechnique Fédérale de Lausanne (EPFL), Lausanne, Switzerland