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

null option - initialization order

11 replies
Razvan Cojocaru 3
Joined: 2010-07-28,
User offline. Last seen 42 years 45 weeks ago.

For example:

 

scala> class A { val x  = Some(1); println (x) }

defined class A

 

scala> class B extends A { override val x = Some(2); println(x) }

defined class B

 

scala> new B

null

Some(2)

res1: B = B@48b89bc5

 

scala>

 

 

I understand Paul’s explanation here https://github.com/paulp/scala-faq/wiki/Initialization-Order  -  initializing fields only once -  but this kind of side-effect from a client derived class onto a base library class is bad… it gives birth to monsters like this null Option…

 

Yes, this is also possible, but it’s not an excuse:

 

scala> val x : Option[Int] = null

x: Option[Int] = null

 

is there nothing we can do to make this behavior more intuitive? Just a not in the style guide?

 

thanks,

Razie

 

 

 

 

dcsobral
Joined: 2009-04-23,
User offline. Last seen 38 weeks 5 days ago.
Re: null option - initialization order

You should really *not* override a val. If it is a val, then you are
kind of guaranteeing the return value is that one. To change it in a
subclass is a violation of the liskov principle.

The simplest way to achieve the same thing is this:

class A { def x = Some(1); println(x) }
class B extends A { override def x = Some(2); println(x) }

You can even override A's def x with a val x.

This will only cause trouble with frameworks that search for fields
through reflection. In that case, you'd better just go with a var
instead, and possibly make it protected and add a public getter if
needed.

On Tue, Aug 23, 2011 at 13:21, Razvan Cojocaru wrote:
> For example:
>
>
>
> scala> class A { val x  = Some(1); println (x) }
>
> defined class A
>
>
>
> scala> class B extends A { override val x = Some(2); println(x) }
>
> defined class B
>
>
>
> scala> new B
>
> null
>
> Some(2)
>
> res1: B = B@48b89bc5
>
>
>
> scala>
>
>
>
>
>
> I understand Paul’s explanation here
> https://github.com/paulp/scala-faq/wiki/Initialization-Order  -
>  initializing fields only once -  but this kind of side-effect from a client
> derived class onto a base library class is bad… it gives birth to monsters
> like this null Option…
>
>
>
> Yes, this is also possible, but it’s not an excuse:
>
>
>
> scala> val x : Option[Int] = null
>
> x: Option[Int] = null
>
>
>
> is there nothing we can do to make this behavior more intuitive? Just a not
> in the style guide?
>
>
>
> thanks,
>
> Razie
>
>
>
>
>
>
>
>

Naftoli Gugenheim
Joined: 2008-12-17,
User offline. Last seen 42 years 45 weeks ago.
Re: null option - initialization order


On Tue, Aug 23, 2011 at 12:40 PM, Daniel Sobral <dcsobral [at] gmail [dot] com> wrote:
You should really *not* override a val. If it is a val, then you are
kind of guaranteeing the return value is that one. To change it in a
subclass is a violation of the liskov principle.

Is overriding a method and changing its behavior a violation?
 

The simplest way to achieve the same thing is this:

class A { def x = Some(1); println(x) }
class B extends A { override def x = Some(2); println(x) }

You can even override A's def x with a val x.

This will only cause trouble with frameworks that search for fields
through reflection. In that case, you'd better just go with a var
instead, and possibly make it protected and add a public getter if
needed.

On Tue, Aug 23, 2011 at 13:21, Razvan Cojocaru <pub [at] razie [dot] com> wrote:
> For example:
>
>
>
> scala> class A { val x  = Some(1); println (x) }
>
> defined class A
>
>
>
> scala> class B extends A { override val x = Some(2); println(x) }
>
> defined class B
>
>
>
> scala> new B
>
> null
>
> Some(2)
>
> res1: B = B@48b89bc5
>
>
>
> scala>
>
>
>
>
>
> I understand Paul’s explanation here
> https://github.com/paulp/scala-faq/wiki/Initialization-Order  -
>  initializing fields only once -  but this kind of side-effect from a client
> derived class onto a base library class is bad… it gives birth to monsters
> like this null Option…
>
>
>
> Yes, this is also possible, but it’s not an excuse:
>
>
>
> scala> val x : Option[Int] = null
>
> x: Option[Int] = null
>
>
>
> is there nothing we can do to make this behavior more intuitive? Just a not
> in the style guide?
>
>
>
> thanks,
>
> Razie
>
>
>
>
>
>
>
>



--
Daniel C. Sobral

I travel to the future all the time.

Tony Morris 2
Joined: 2009-03-20,
User offline. Last seen 42 years 45 weeks ago.
Re: null option - initialization order
On 08/24/2011 02:40 AM, Daniel Sobral wrote:
JfwwdM0qUmLTbXvyaZvEAAdmx8Oo7ni5u4g [at] mail [dot] gmail [dot] com" type="cite">
You should really *not* override a val. If it is a val, then you are
kind of guaranteeing the return value is that one. To change it in a
subclass is a violation of the liskov principle.
Bzzt.

sealed trait StrictIdentity[A] { val value: A }; object StrictIdentity { implicit def ToStrictIdentity[A](a: A): StrictIdentity[A] = new StrictIdentity[A] { val value = a } }


-- 
Tony Morris
http://tmorris.net/

Naftoli Gugenheim
Joined: 2008-12-17,
User offline. Last seen 42 years 45 weeks ago.
Re: null option - initialization order
What?

On Tue, Aug 23, 2011 at 7:15 PM, Tony Morris <tonymorris [at] gmail [dot] com> wrote:
On 08/24/2011 02:40 AM, Daniel Sobral wrote:
You should really *not* override a val. If it is a val, then you are
kind of guaranteeing the return value is that one. To change it in a
subclass is a violation of the liskov principle.
Bzzt.

sealed trait StrictIdentity[A] { val value: A }; object StrictIdentity { implicit def ToStrictIdentity[A](a: A): StrictIdentity[A] = new StrictIdentity[A] { val value = a } }


-- 
Tony Morris
http://tmorris.net/


Tony Morris 2
Joined: 2009-03-20,
User offline. Last seen 42 years 45 weeks ago.
Re: null option - initialization order
On 08/24/2011 09:25 AM, Naftoli Gugenheim wrote:
y4WZk+Q [at] mail [dot] gmail [dot] com" type="cite"> What?

On Tue, Aug 23, 2011 at 7:15 PM, Tony Morris <tonymorris [at] gmail [dot] com" rel="nofollow">tonymorris [at] gmail [dot] com> wrote:
On 08/24/2011 02:40 AM, Daniel Sobral wrote:
You should really *not* override a val. If it is a val, then you are
kind of guaranteeing the return value is that one. To change it in a
subclass is a violation of the liskov principle.
Bzzt.

sealed trait StrictIdentity[A] { val value: A }; object StrictIdentity { implicit def ToStrictIdentity[A](a: A): StrictIdentity[A] = new StrictIdentity[A] { val value = a } }


-- 
Tony Morris
http://tmorris.net/



There are many legitimate use-cases for overriding a val. Suggest revision.

-- 
Tony Morris
http://tmorris.net/

Naftoli Gugenheim
Joined: 2008-12-17,
User offline. Last seen 42 years 45 weeks ago.
Re: null option - initialization order
I think Daniel's point was that if the superclass assigned one value to the val, then code that accepts an object with the superclass type would assume it has the value specified in the superclass. If then a subclass assigns a different value to it, that breaks the code's assumption. As opposed to where the superclass only defines an abstract val; then there's no assumption to break. I disagreed. As implied by the quote I posted, Liskov doesn't say that subclasses can't behave differently then the superclass; only that they must follow its contract. Not all code is a contract. Types certainly are, and I suppose anything documented explicitly as being a contract is; but actual implementation is not.

On Tue, Aug 23, 2011 at 7:26 PM, Tony Morris <tonymorris [at] gmail [dot] com> wrote:
On 08/24/2011 09:25 AM, Naftoli Gugenheim wrote:
What?

On Tue, Aug 23, 2011 at 7:15 PM, Tony Morris <tonymorris [at] gmail [dot] com> wrote:
On 08/24/2011 02:40 AM, Daniel Sobral wrote:
You should really *not* override a val. If it is a val, then you are
kind of guaranteeing the return value is that one. To change it in a
subclass is a violation of the liskov principle.
Bzzt.

sealed trait StrictIdentity[A] { val value: A }; object StrictIdentity { implicit def ToStrictIdentity[A](a: A): StrictIdentity[A] = new StrictIdentity[A] { val value = a } }


-- 
Tony Morris
http://tmorris.net/



There are many legitimate use-cases for overriding a val. Suggest revision.

-- 
Tony Morris
http://tmorris.net/


Tony Morris 2
Joined: 2009-03-20,
User offline. Last seen 42 years 45 weeks ago.
Re: null option - initialization order
Sure. I just put all that aside to point out that there is at least one legitimate use-case for overriding a val. I chose that particular use-case because it is used specifically to *adhere* to the liskov substitution rule rather than break it. For example, to implement equals properly:

trait TypeSafeEquals[A] { def equal: A => A => Boolean }
sealed trait StrictIdentity[A] { val value: A; def ===(a: A)(implicit e: TypeSafeEqual[A]) = e.equal(value)(a) }; object StrictIdentity { implicit def ToStrictIdentity[A](a: A): StrictIdentity[A] = new StrictIdentity[A] { val value = a } }

On 08/24/2011 09:38 AM, Naftoli Gugenheim wrote:
CANpg8PCg1CZaVwJuoRpRAAHFBG-q5zSh-F7EZrrjBcU9xKyqDw [at] mail [dot] gmail [dot] com" type="cite"> I think Daniel's point was that if the superclass assigned one value to the val, then code that accepts an object with the superclass type would assume it has the value specified in the superclass. If then a subclass assigns a different value to it, that breaks the code's assumption. As opposed to where the superclass only defines an abstract val; then there's no assumption to break. I disagreed. As implied by the quote I posted, Liskov doesn't say that subclasses can't behave differently then the superclass; only that they must follow its contract. Not all code is a contract. Types certainly are, and I suppose anything documented explicitly as being a contract is; but actual implementation is not.

On Tue, Aug 23, 2011 at 7:26 PM, Tony Morris <tonymorris [at] gmail [dot] com" rel="nofollow">tonymorris [at] gmail [dot] com> wrote:
On 08/24/2011 09:25 AM, Naftoli Gugenheim wrote:
What?

On Tue, Aug 23, 2011 at 7:15 PM, Tony Morris <tonymorris [at] gmail [dot] com" target="_blank" rel="nofollow">tonymorris [at] gmail [dot] com> wrote:
On 08/24/2011 02:40 AM, Daniel Sobral wrote:
You should really *not* override a val. If it is a val, then you are
kind of guaranteeing the return value is that one. To change it in a
subclass is a violation of the liskov principle.
Bzzt.

sealed trait StrictIdentity[A] { val value: A }; object StrictIdentity { implicit def ToStrictIdentity[A](a: A): StrictIdentity[A] = new StrictIdentity[A] { val value = a } }


-- 
Tony Morris
http://tmorris.net/



There are many legitimate use-cases for overriding a val. Suggest revision.

-- 
Tony Morris
http://tmorris.net/




-- 
Tony Morris
http://tmorris.net/

Naftoli Gugenheim
Joined: 2008-12-17,
User offline. Last seen 42 years 45 weeks ago.
Re: null option - initialization order
But you're overriding an abstract val, about which there was no doubt.

On Tue, Aug 23, 2011 at 7:46 PM, Tony Morris <tonymorris [at] gmail [dot] com> wrote:
Sure. I just put all that aside to point out that there is at least one legitimate use-case for overriding a val. I chose that particular use-case because it is used specifically to *adhere* to the liskov substitution rule rather than break it. For example, to implement equals properly:

trait TypeSafeEquals[A] { def equal: A => A => Boolean }
sealed trait StrictIdentity[A] { val value: A; def ===(a: A)(implicit e: TypeSafeEqual[A]) = e.equal(value)(a) }; object StrictIdentity { implicit def ToStrictIdentity[A](a: A): StrictIdentity[A] = new StrictIdentity[A] { val value = a } }

On 08/24/2011 09:38 AM, Naftoli Gugenheim wrote:
I think Daniel's point was that if the superclass assigned one value to the val, then code that accepts an object with the superclass type would assume it has the value specified in the superclass. If then a subclass assigns a different value to it, that breaks the code's assumption. As opposed to where the superclass only defines an abstract val; then there's no assumption to break. I disagreed. As implied by the quote I posted, Liskov doesn't say that subclasses can't behave differently then the superclass; only that they must follow its contract. Not all code is a contract. Types certainly are, and I suppose anything documented explicitly as being a contract is; but actual implementation is not.

On Tue, Aug 23, 2011 at 7:26 PM, Tony Morris <tonymorris [at] gmail [dot] com> wrote:
On 08/24/2011 09:25 AM, Naftoli Gugenheim wrote:
What?

On Tue, Aug 23, 2011 at 7:15 PM, Tony Morris <tonymorris [at] gmail [dot] com> wrote:
On 08/24/2011 02:40 AM, Daniel Sobral wrote:
You should really *not* override a val. If it is a val, then you are
kind of guaranteeing the return value is that one. To change it in a
subclass is a violation of the liskov principle.
Bzzt.

sealed trait StrictIdentity[A] { val value: A }; object StrictIdentity { implicit def ToStrictIdentity[A](a: A): StrictIdentity[A] = new StrictIdentity[A] { val value = a } }


-- 
Tony Morris
http://tmorris.net/



There are many legitimate use-cases for overriding a val. Suggest revision.

-- 
Tony Morris
http://tmorris.net/




-- 
Tony Morris
http://tmorris.net/


dcsobral
Joined: 2009-04-23,
User offline. Last seen 38 weeks 5 days ago.
Re: null option - initialization order

On Tue, Aug 23, 2011 at 19:43, Naftoli Gugenheim wrote:
>
>
> On Tue, Aug 23, 2011 at 12:40 PM, Daniel Sobral wrote:
>>
>> You should really *not* override a val. If it is a val, then you are
>> kind of guaranteeing the return value is that one. To change it in a
>> subclass is a violation of the liskov principle.
>
> Is overriding a method and changing its behavior a violation?

Sure, in this sense:

class A {
def lightState = // true if on
}

class B extends A {
override def lightState = // false if on
}

That changed the behavior. But note that a *val* gives more guarantees
than a *def*. In particular, you are guaranteed that two calls to a
val will always return the same value for an instance.

dcsobral
Joined: 2009-04-23,
User offline. Last seen 38 weeks 5 days ago.
Re: null option - initialization order

On Tue, Aug 23, 2011 at 20:15, Tony Morris wrote:
> On 08/24/2011 02:40 AM, Daniel Sobral wrote:
>
> You should really *not* override a val. If it is a val, then you are
> kind of guaranteeing the return value is that one. To change it in a
> subclass is a violation of the liskov principle.
>
> Bzzt.

No Bzzt.

> sealed trait StrictIdentity[A] { val value: A }; object StrictIdentity {
> implicit def ToStrictIdentity[A](a: A): StrictIdentity[A] = new
> StrictIdentity[A] { val value = a } }

You should have declared the abstract "value" to be a def, though you
might well want to provide the guarantees of a "val" here.

BUT, in this case you are overriding an abstract member, which is a
very different thing than what was being done.

Kjetil Ørbekk
Joined: 2011-08-24,
User offline. Last seen 42 years 45 weeks ago.
Re: null option - initialization order
On Wed, Aug 24, 2011 at 9:48 AM, Daniel Sobral <dcsobral [at] gmail [dot] com> wrote:
> sealed trait StrictIdentity[A] { val value: A }; object StrictIdentity { > implicit def ToStrictIdentity[A](a: A): StrictIdentity[A] = new
> StrictIdentity[A] { val value = a } }

You should have declared the abstract "value" to be a def, though you
might well want to provide the guarantees of a "val" here.

 I think the issue for the original poster was that the current behavior is unintuitive, not whether or not it is good design to override a val.
Perhaps the compiler could warn about overridden vals?

Best,Kjetil

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