On Tue, Nov 19, 2024, at 5:56 AM, Michał Marcin Brzuchalski wrote:
> Hi Larry,
>
> niedz., 17 lis 2024 o 08:24 Larry Garfield <la...@garfieldtech.com> 
> napisał(a):
>> ...
>> I can see the benefit of an inline constructor.  Kotlin has something 
>> similar.  But I can see the benefit of it for all classes, even service 
>> classes, not just records.  (In Kotlin, it's used for service classes all 
>> the time.)
>
> What visibility would you expect for inline constructor properties in 
> service classes?

Good question.  If we follow Kotlin's lead[1] (Kotlin has like 3-4 different 
places to do constructor-ish logic, it's kinda weird), then they'd default to 
public, but you can optionally specify a visibility. So, the same as any other 
property.  They do not imply readonly, but Kotlin has per-arg val/var markers 
on everything anyway to handle that.

[1] https://kotlinlang.org/docs/classes.html#constructors

>> I would far prefer assembling record-ish behavior myself, using the smaller 
>> parts above.  Eg:
>> 
>> final readonly data class Point(int $x, int $y);
>> 
>> "final" prevents extension.  "readonly" makes it immutable.  "data" gives it 
>> value-passing semantics.  Any class can use an inline constructor.  "with" 
>> is designed to work automatically on all objects.  Boom, I've just assembled 
>> a Record out of its constituent parts, which also makes it easier for others 
>> to learn what I'm doing, because the features opted-in to are explicit, not 
>> implicit.
>
> It opens a bunch of concerns, questions like why would you use "data" 
> keyword without having "final"?
> Although I like the "data" keyword very much. 

To borrow a little syntax from Kotlin again, and randomly spitball:

data class Rectangle(int $h, int $w) {
    public int $area { get => $this->h * $this->w; }
}

data class Square(int $side) extends Rectangle($side, $side);

(That calls the constructor of Rectangle with the provided values.)

I'm generally not in favor of final-by-default. final has its place, certainly, 
but I am firmly on team "no, you should not be making every class final by 
default, that's just silly."

Another thing to consider is ADTs.  (cf https://wiki.php.net/rfc/tagged_unions, 
though the text there is quite old and out dated at this point; do not take 
literally.)  The intent for enum cases with associated data was that you would 
specify properties and types, and those would be public-readonly-enforced. 
That's the only option.  Ilija also had plans (and I think implementation, but 
I'm not sure) for reusing instances with matching properties so they would 
point to the same object in memory, making === work.  So:

enum Move {
  case Left(int $distance);
  case Right(int $distance);
}

$step1 = Move::Left(5);
$step2 = Move::Left(5);
$step1 === $step2; // true

That sounds somewhat similar to the restrictions proposed for Records, though 
in context I think they make more sense on enums.  I can see there being 
overlap, though, so I mention it here for completeness.  I'm not sure how this 
all dovetails together.

To reiterate, my main issue with the Records concept is that it couples too 
many features together into an all-or-nothing package.  readonly did that with 
only 2 features, and it caused Ilija and I a lot of heartburn and helped keep 
aviz from passing in 8.3.  Records would couple 4 features together.  Once 
bitten, twice as cautious.

--Larry Garfield

Reply via email to