#1370 Overloading of constructor shortcut

Akcelisto Wed 5 Jan 2011

Simple class:

class Box{
  Int x
  Int y
  new make(Int x, Int y){
    this.x=x
    this.y=y
  }
}

Short creation:

box := Box(1,2)

I used this 100s times.

But suddenly I need serialization for Box. Ok:

@Serializable
class Box{
  Int x
  Int y

  new make(|This| f){
    f(this)
  }
  new makeMeSad(Int x, Int y){
    this.x=x
    this.y=y
  }
}

Adding @Serializable is breaking change. Short creation is not available. I must change it 100s times.

box := Box.makeMeSad(1,2)

Inaccessibility of short creation make me sad more than needing of replacing.

I suggest, deserialization must use special ctor makeDeserialization(|This| f) and if deserialization not found ctor makeDeserialization then use ctor make(|This| f).

Why deserialization take off short ctor which need to call by manual although deserialization call ctor by automatic?

brian Wed 5 Jan 2011

You definitely have a good point about how this works.

So is it worth complicating the serialization design to specify an alternate make method to ensure that "good" make is reserved for human programmers?

If did that, I would be included to do via adding a slot literal field on the Serializable facet:

@Serializable { ctor = #makeSerialized }
class Box
{
  new make() {...}
  new makeSerialized() {...}
}

andy Wed 5 Jan 2011

I had not considered this either - but makes sense. I don't write awhole lot of serializable code at the moment - so can't really gauge the impact one way or the other - but the ctor facet meta seems to make sense.

Akcelisto Wed 5 Jan 2011

Why I should configure facet? Why makeSerialized not can be default (with fallback to make for backward compatibility)?

I want: convention over configuration.

P.S. Why deserialization require specific ctor? Java require nothing for deserialization.

DanielFath Wed 5 Jan 2011

There is a solution that would bypass both, Contructor overload. Though Akcelisto's questions is good question might be simple overall.

andy Wed 5 Jan 2011

I'm cool with just making makeSerialized the standard mechanism (can't see ever wanting to use that name for something else).

qualidafial Thu 6 Jan 2011

I've used serialization a bit, and did a bit of grumbling about how I had to mangle my constructor to play nice with serialization.

I favor the idea but not makeSerialized. You're constructing an object using a callback init function. The fact that we're using this idiom to deserialize an object is secondary.

I propose makeFunc, since it focuses on the idiom rather than a single use case.

qualidafial Thu 6 Jan 2011

Depending on how it goes, this may even be worth implementing compiler sugar for, similar to how the compiler converts Point("1,2") to Point.fromStr("1,2").

This way you can have both the regular constructor and the it-block constructor free of boilerplate:

@Serializable
class Box{
  Int x
  Int y

  new make(Int x, Int y){
    this.x=x
    this.y=y
  }

  new makeFunc(|This| f){
    f(this)
  }
}

box := Box(1, 2)
box2 := Box { x = 1; y = 2 }

Akcelisto Thu 6 Jan 2011

to qualidafial

I disagree with rename makeSerialized to makeFunc. I need separate ctor for deserialization and separate ctor for manual creation. Because I need to enhance obj after deserialization.

@Serializable
class Box{
  Int x
  Int y

  new make(Int x, Int y){
    this.x=x
    this.y=y
  }

  new makeFunc(|This| f){
    f(this)
  }

  new makeSerialized(|This| f){
    f(this)
    afterDeserialize
  }

  private Void afterDeserialize(){      
    // here I usually fill refs which not serialized and not deserialized 
    // this method not need to call when manual creation
  }
}

to DanielFath

There is a solution that would bypass both, Contructor overload.

I like ctor overload. Operators already have overload. Ctors needs this too.

But ctor overload may complicate problem with deserialization ctor. Compiler say for this class: "duplicate list of args for makeMeSad and make"

@Serializable
class Box{
  Int x
  Int y    

  new makeMeSad(|This| f){
    f(this)
  }

  new make(|This| f){
    f(this)
    postDeserialize
  }

  private Void afterDeserialize(){      
    // here I usually fill refs which not serialized and not deserialized 
    // this method not need to call when manual creation
  }
}   

Ideally:

  • ctor overload exists
  • deserialization not require special ctor
  • deserialization call method with facet AfterDeserialization if it exists in class
  • deserialization checks notnull fields after creation

end of list

@Serializable
class Box{
  Int x
  Int y

  new make(Int x, Int y){
    this.x=x
    this.y=y
  }

  @AfterDeserialization
  private Void fillRefs(){      
    // fill refs which not serialized and not deserialized 
    // this method not need to call when manual creation
  } 
}

to Bryan

Why makeSerialized? This sounds like we create serialized obj but we create de-serialized obj. May be better makeDeserialized?

qualidafial Thu 6 Jan 2011

@Akcelisto: it sounds like you want the equivalent of Java's readResolve. If so we could add that while still using makeFunc as a more use-case agnostic convention.

ivan Mon 10 Jan 2011

Totally agree with @qualidafial

  1. Compiler sugar for makeFunc constructor seems to be much easier to do than constructor overloading and should cover all the cases. Plus if constructor overloading will be added later, the code won't be broken.
  2. `http://download.oracle.com/javase/6/docs/platform/serialization/spec/input.html#5903` method is much more clear than separate deserialization constructor and gives more abilities than @AfterDeserialization private Void whatever()

brian Tue 15 Nov 2011

Renamed from Deserialization take off short ctor to Overloading of constructor shortcut

brian Tue 15 Nov 2011

Promoted to ticket #1370 and assigned to brian

This issue and also the discussion per #1674 presents a clear case that we should provide a more flexible mechanism to overload the constructor shortcut syntax of Type(...) to map to more than one method.

Simplest design would be to reuse the @Operator facet on any method that it prefixed with make or from. But I don't think we want to require that facet on existing make or fromStr methods. At the same time, I don't want to create an inconsistency.

Not sure yet what correct design should be.

qualidafial Tue 15 Nov 2011

I've been thinking about this with regard to default constructors. If your class has no fields, or has only nullable fields, then it's ok to have the compiler inject a no-arg, no-op constructor.

However if you have any non-nullable fields, it would be nice to have the compiler inject a makeFunc(|This| f) constructor for us. The rest of the time, these callback constructors are just repetitive boilerplate.

qualidafial Tue 15 Nov 2011

This could also serve as a nice compromise for #1696 (using <ObjType>.defVal for uninitialized non-nullable fields).

qualidafial Mon 21 Nov 2011

Simplest design would be to reuse the @Operator facet on any method that it prefixed with make or from. But I don't think we want to require that facet on existing make or fromStr methods. At the same time, I don't want to create an inconsistency.

As far as backward-incompatible changes go, this ranks pretty low on the pain scale. If the compiler fails, just go add the missing @Operator facet and compile again. Not a big deal.

To help users make the transition, we could always do a two-phase rollout:

  • Phase one: Compiler begins honoring @Operator facets on from* and make* methods. Existing make and fromStr methods lacking the facet will act as if they had the facet, but will issue a compiler warning.
  • Phase two: Remove special case exception for make and fromStr. Only methods having the @Operator facet will receive constructor shortcut treatment from the compiler.

This way users will have a full release cycle to update their code.

MoOm Wed 23 Nov 2011

I'm not fond of the @Operator facet on constructors. Constructors are not operators in my mind.

Can't we just make this the default behavior for constructors? We may not want to have that on all constructors though (I have no example in mind...). In this case, we could use an @Explicit annotation on constructors on which we don't want to allow the short syntax.

from* and to* methods might be seen as conversion operations so @Operator annotation would work for me but then we would probably want a conversion operator (like the ~ operator proposed by Jodastephen iirc). This would work well for consistency and for ease of use imho.

brian Fri 25 Nov 2011

Can't we just make this the default behavior for constructors?

I was sort of thinking of this approach - basically just allowing any constructor prefixed with "make" to use shortcut approach. I am not sure it would really hurt since it would be traditional overload. But I suspect it will cause new compilation problems where existing code will have ambiguous calls to multiple constructors.

brian Sat 16 Jun 2012

Ticket resolved in 1.0.62

Just going back through and this ticket was really dup of 1776 and closed out in last build.

Login or Signup to reply.