Re: Derived record creation and Data Oriented programming

2024-04-30 Thread Brian Goetz




So let's back up: what problem are we trying to solve here?


The problem is that the syntax of "with" does not show that with 
depends on the deconstructor and the constructor of the record, so the 
behavior in case of separate compilation is not clear.


I think the lack of clarity you are concerned about is that the names of 
the components are being used as a new kind of API, where we lift API 
elements to variables, and you're concerned that those names are "weakly 
coupled" between declaration and client?  Is this right?



I see several ways to fix that:
- restrict the uses of "with" to the package/module containing the 
record (as we have done with sealed types),


With this restriction, I think the feature is not really useful enough 
to justify.  Plus this will annoy people.



- allows a syntax variation where the components of the record are listed,


So, this would be like a C++ lambda:

    // don't take the syntax seriously, purely meant to evoke C++ 
lambda syntax

    p = p with [x,y]{ x = 3; }

and then the [x,y] would be used for the ctor/dtor lookup?

- link the canonical deconstructor/constructor of the record at 
runtime (which can be simpler if the local variable declared outside 
of the block are captured (like the variables inside a "when" 
expression)).


Not sure what you mean here, but it probably doesn't scale to classes?


and maybe there are other solutions ?

Rémi


On 4/30/2024 9:22 AM, Remi Forax wrote:

Hello,
they have been several messages on amber-dev about the compatibility of 
the derived record creation.

I think part of the issue reported is that with the proposed syntax, 
the call to the desconstructor and the canonical constructor is implicit.

Let's take an example

   record Point(int x, int y) {}

   var point = new Point(2, 3);


In fact,
   var point2 = point with { x = y; };

is a shortcut for:

   var point2 = point with {
 Point(int x, int y) = this;  // i.e. int x = this.x(); int y = 
this.y();
 x = y;
 yield new Point(x, y);
   };

One problem with the current syntax is that because the call to the 
[de]constructors is implicit. I think we shoud allow users to write the 
implicit calls if they want.

I wonder if
- we should not allow yield to be used so the compiler adds yield 
automatically only if there is no yield ?
- we should in the future when deconstructors are introduced, allow 
users to call a deconstructor explicitly and only provide one if not explicitly 
written ?

Being able to write the calls explicitly is important because it's a 
way to detect if the record has been modified without the proper 
constructor/destructor has been written to be backward compatible (Like in a 
switch, a record pattern detects when a record component is added while a type 
pattern does not).

Being able to call a the deconstructor explicitly also have the 
advantage to avoid to declare a variable/calls the accessor if not needed.
By example
   var point2 = point with {
 Point(_, var y) = this;
 x = y;
   };

regards,
Rémi





Re: Derived record creation and Data Oriented programming

2024-04-30 Thread forax
> From: "Brian Goetz" 
> To: "Remi Forax" , "amber-spec-experts"
> 
> Sent: Tuesday, April 30, 2024 3:32:35 PM
> Subject: Re: Derived record creation and Data Oriented programming

> Interesting idea. Of the two sides, allowing explicit *constructor* calls is
> significantly more practical (`yield` has control flow consequences understood
> by the language, and obviously means "yield this value from the current
> expression", whereas doing a random pattern match with `this` as a match
> candidate is not remotely clear that you intend to override the
> deconstruction.)

> But, let's back up a second: what do we gain from this? Let's say we have two
> records with similar states:

> record A(int x, int y, int z) { }
> record B(int x, int y, int z) { }

> A a = ...
> B b = a with { yield new B(x, y, z); }

> I don't see how this is more clear than:

> B b = switch (a) {
> case A(var x, var y, var z) -> new B(x, y, z);
> };

> (or an imperative match, if we have one.) In fact, it seems less clear, since 
> it
> is not really a "with" anything. It's using `with` as a short form of "shred 
> to
> components", which is not what `with` is intended to convey.

> So let's back up: what problem are we trying to solve here?
The problem is that the syntax of "with" does not show that with depends on the 
deconstructor and the constructor of the record, so the behavior in case of 
separate compilation is not clear. 

I see several ways to fix that: 
- restrict the uses of "with" to the package/module containing the record (as 
we have done with sealed types), 
- allows a syntax variation where the components of the record are listed, 
- link the canonical deconstructor/constructor of the record at runtime (which 
can be simpler if the local variable declared outside of the block are captured 
(like the variables inside a "when" expression)). 

and maybe there are other solutions ? 

Rémi 

> On 4/30/2024 9:22 AM, Remi Forax wrote:

>> Hello,
>> they have been several messages on amber-dev about the compatibility of the
>> derived record creation.

>> I think part of the issue reported is that with the proposed syntax, the 
>> call to
>> the desconstructor and the canonical constructor is implicit.

>> Let's take an example

>>   record Point(int x, int y) {}

>>   var point = new Point(2, 3);

>> In fact,
>>   var point2 = point with { x = y; };

>> is a shortcut for:

>>   var point2 = point with {
>> Point(int x, int y) = this;  // i.e. int x = this.x(); int y = this.y();
>> x = y;
>> yield new Point(x, y);
>>   };

>> One problem with the current syntax is that because the call to the
>> [de]constructors is implicit. I think we shoud allow users to write the
>> implicit calls if they want.

>> I wonder if
>> - we should not allow yield to be used so the compiler adds yield 
>> automatically
>> only if there is no yield ?
>> - we should in the future when deconstructors are introduced, allow users to
>> call a deconstructor explicitly and only provide one if not explicitly 
>> written
>> ?

>> Being able to write the calls explicitly is important because it's a way to
>> detect if the record has been modified without the proper
>> constructor/destructor has been written to be backward compatible (Like in a
>> switch, a record pattern detects when a record component is added while a 
>> type
>> pattern does not).

>> Being able to call a the deconstructor explicitly also have the advantage to
>> avoid to declare a variable/calls the accessor if not needed.
>> By example
>>   var point2 = point with {
>> Point(_, var y) = this;
>> x = y;
>>   };

>> regards,
>> Rémi


Re: Derived record creation and Data Oriented programming

2024-04-30 Thread Brian Goetz
Interesting idea.  Of the two sides, allowing explicit *constructor* 
calls is significantly more practical (`yield` has control flow 
consequences understood by the language, and obviously means "yield this 
value from the current expression", whereas doing a random pattern match 
with `this` as a match candidate is not remotely clear that you intend 
to override the deconstruction.)


But, let's back up a second: what do we gain from this?  Let's say we 
have two records with similar states:


    record A(int x, int y, int z) { }
    record B(int x, int y, int z) { }

A a = ...
B b = a with { yield new B(x, y, z); }

I don't see how this is more clear than:

B b = switch (a) {
  case A(var x, var y, var z) -> new B(x, y, z);
  };

(or an imperative match, if we have one.)  In fact, it seems less clear, 
since it is not really a "with" anything.  It's using `with` as a short 
form of "shred to components", which is not what `with` is intended to 
convey.


So let's back up: what problem are we trying to solve here?

On 4/30/2024 9:22 AM, Remi Forax wrote:

Hello,
they have been several messages on amber-dev about the compatibility of the 
derived record creation.

I think part of the issue reported is that with the proposed syntax, the call 
to the desconstructor and the canonical constructor is implicit.

Let's take an example

   record Point(int x, int y) {}

   var point = new Point(2, 3);


In fact,
   var point2 = point with { x = y; };

is a shortcut for:

   var point2 = point with {
 Point(int x, int y) = this;  // i.e. int x = this.x(); int y = this.y();
 x = y;
 yield new Point(x, y);
   };

One problem with the current syntax is that because the call to the 
[de]constructors is implicit. I think we shoud allow users to write the 
implicit calls if they want.

I wonder if
- we should not allow yield to be used so the compiler adds yield automatically 
only if there is no yield ?
- we should in the future when deconstructors are introduced, allow users to 
call a deconstructor explicitly and only provide one if not explicitly written ?

Being able to write the calls explicitly is important because it's a way to 
detect if the record has been modified without the proper 
constructor/destructor has been written to be backward compatible (Like in a 
switch, a record pattern detects when a record component is added while a type 
pattern does not).

Being able to call a the deconstructor explicitly also have the advantage to 
avoid to declare a variable/calls the accessor if not needed.
By example
   var point2 = point with {
 Point(_, var y) = this;
 x = y;
   };

regards,
Rémi


Derived record creation and Data Oriented programming

2024-04-30 Thread Remi Forax
Hello,
they have been several messages on amber-dev about the compatibility of the 
derived record creation.

I think part of the issue reported is that with the proposed syntax, the call 
to the desconstructor and the canonical constructor is implicit.

Let's take an example

  record Point(int x, int y) {}

  var point = new Point(2, 3);


In fact,
  var point2 = point with { x = y; };

is a shortcut for:

  var point2 = point with {
Point(int x, int y) = this;  // i.e. int x = this.x(); int y = this.y();
x = y;
yield new Point(x, y);
  };

One problem with the current syntax is that because the call to the 
[de]constructors is implicit. I think we shoud allow users to write the 
implicit calls if they want.

I wonder if
- we should not allow yield to be used so the compiler adds yield automatically 
only if there is no yield ?
- we should in the future when deconstructors are introduced, allow users to 
call a deconstructor explicitly and only provide one if not explicitly written ?

Being able to write the calls explicitly is important because it's a way to 
detect if the record has been modified without the proper 
constructor/destructor has been written to be backward compatible (Like in a 
switch, a record pattern detects when a record component is added while a type 
pattern does not).

Being able to call a the deconstructor explicitly also have the advantage to 
avoid to declare a variable/calls the accessor if not needed.
By example
  var point2 = point with {
Point(_, var y) = this;
x = y;
  };

regards,
Rémi