Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:125979 X-Original-To: internals@lists.php.net Delivered-To: internals@lists.php.net Received: from php-smtp4.php.net (php-smtp4.php.net [45.112.84.5]) by qa.php.net (Postfix) with ESMTPS id D67CC1A00BD for ; Sun, 17 Nov 2024 23:13:09 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=php.net; s=mail; t=1731885347; bh=NhaPmUiGoc5czuqMUp3e2JC/am/Gc0g/Bm1ezvmJWGQ=; h=Date:Subject:To:References:From:In-Reply-To:From; b=h8/ZWXqlPlUho8g71GqlfqIylxSEgUUHlusGPXknOCLEX0NCZ0fVPOFrEAyGKfNxM 6Ux/kviHyOMe1NJ/BjqsB5dfd2ilF4f+viqm9Vv1tFPwO1SLtMwD6eE9HtfvXnbb7M hN8yjbpe1tp5u7uLL0c5hpZV/M7mKhaT0VKCbXpnUXDoGDgMM/hFCXfdet/0wdOsjw e/h0/RBHqmGJTt1AUk3hrYjsITxZ30LMeBIOZQB9pNO/H8N5rmTbz7LA/rquuVE/7W 6KAF4HBANz4HMow/xSAZQBTuX3wUkv/TuQOqnA9ISFnZyRxDzU850hYEZJ/0Is2hM1 xZTiMLwbEty3g== Received: from php-smtp4.php.net (localhost [127.0.0.1]) by php-smtp4.php.net (Postfix) with ESMTP id B51F818003E for ; Sun, 17 Nov 2024 23:15:46 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 4.0.0 (2022-12-13) on php-smtp4.php.net X-Spam-Level: X-Spam-Status: No, score=-0.1 required=5.0 tests=BAYES_50,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,DMARC_MISSING,HTML_MESSAGE, RCVD_IN_DNSWL_LOW,RCVD_IN_VALIDITY_CERTIFIED_BLOCKED, RCVD_IN_VALIDITY_RPBL_BLOCKED,RCVD_IN_VALIDITY_SAFE_BLOCKED, SPF_HELO_PASS,SPF_PASS autolearn=no autolearn_force=no version=4.0.0 X-Spam-Virus: No X-Envelope-From: Received: from fout-b5-smtp.messagingengine.com (fout-b5-smtp.messagingengine.com [202.12.124.148]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by php-smtp4.php.net (Postfix) with ESMTPS for ; Sun, 17 Nov 2024 23:15:46 +0000 (UTC) Received: from phl-compute-04.internal (phl-compute-04.phl.internal [10.202.2.44]) by mailfout.stl.internal (Postfix) with ESMTP id A94521140108 for ; Sun, 17 Nov 2024 18:13:07 -0500 (EST) Received: from phl-mailfrontend-01 ([10.202.2.162]) by phl-compute-04.internal (MEProxy); Sun, 17 Nov 2024 18:13:07 -0500 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=rwec.co.uk; h=cc :content-type:content-type:date:date:from:from:in-reply-to :in-reply-to:message-id:mime-version:references:reply-to:subject :subject:to:to; s=fm3; t=1731885187; x=1731971587; bh=CuHfmt9Qcm eGmCgYHKJ2nM65v5tiB4McCTjqm62O+KA=; b=BcAEv88wR08zXqL1qeyfykf5Ub oIDm65ayvbDGKOZDFVknuEKH8F55Dyb3sYwzQx3i5udayLm3ZB15s1h0aj6xkujM n4OZN1+/846LuFdw6skx75jTMUd6V19VixSazHfl/VE2QaPN2UX0xQ6i6mUbxJMi ybIXr7u4jMo0EGey9dK0hFcg0NJseKz0PLemkz7vM0B+GfY7wgeUx9BCvDJQlmVk xZAsnOJNQqiuRxfbAfAhc/AZwwlHtkz2gh509dSrRtMkIxkAvrKIl09NFujO7sIq dJemzDBtIncc0pRfJhj8UFnDJkhiSBvKwRfivxpeFXrNhhyM+XXiaDHQRcqQ== DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d= messagingengine.com; h=cc:content-type:content-type:date:date :feedback-id:feedback-id:from:from:in-reply-to:in-reply-to :message-id:mime-version:references:reply-to:subject:subject:to :to:x-me-proxy:x-me-sender:x-me-sender:x-sasl-enc; s=fm3; t= 1731885187; x=1731971587; bh=CuHfmt9QcmeGmCgYHKJ2nM65v5tiB4McCTj qm62O+KA=; b=Iy7FZcJrq6PsHvrMcRUlXHvaR8HpeoX9K27msJUvUYBBghFiMtl /Ow0847gF0qyTRZ6aW2OrZwctr5tCiYH6LqJ7Y/HFFLcVt1mih2M7ZLJzQkbMxlP YkIzlJW3kzp34sRz1TAF0IEuzsBfkkL8fmOLBe0GK6jx+KWdSK9D3nVkvhE93N8R Zp/fEWhy/q01iZd/BSGgbWp43woeaoUAH8cJLn25m7GE2royRxlT+0tudOiH+Jge 0rWC5FUbnbPL/aHoHhjm9Wk6VDXQ94iSzeEEKIrxHU0LliP5BPQlKKs5AC33Bd/C U94JLnVZvhnLGTLC61zG5CbbPErVa1B9DeQ== X-ME-Sender: X-ME-Received: X-ME-Proxy-Cause: gggruggvucftvghtrhhoucdtuddrgeefuddrvdelgddtkecutefuodetggdotefrodftvf curfhrohhfihhlvgemucfhrghsthforghilhdpggftfghnshhusghstghrihgsvgdpuffr tefokffrpgfnqfghnecuuegrihhlohhuthemuceftddtnecunecujfgurheptgfkffggfg fuvfhfhfgjsegrtderredtvdejnecuhfhrohhmpedftfhofigrnhcuvfhomhhmihhnshcu lgfkoffuohfrngdfuceoihhmshhophdrphhhphesrhifvggtrdgtohdruhhkqeenucggtf frrghtthgvrhhnpeehteelieeigfeuudeiueeiffdvveehudeufeekjeeugffffedtiedt geettdelteenucevlhhushhtvghrufhiiigvpedtnecurfgrrhgrmhepmhgrihhlfhhroh hmpehimhhsohhprdhphhhpsehrfigvtgdrtghordhukhdpnhgspghrtghpthhtohepuddp mhhouggvpehsmhhtphhouhhtpdhrtghpthhtohepihhnthgvrhhnrghlsheslhhishhtsh drphhhphdrnhgvth X-ME-Proxy: Feedback-ID: id5114917:Fastmail Received: by mail.messagingengine.com (Postfix) with ESMTPA for ; Sun, 17 Nov 2024 18:13:06 -0500 (EST) Content-Type: multipart/alternative; boundary="------------rQWv1780KumNh803ioZG0gL4" Message-ID: <9e45ff96-7388-466e-813a-74e0dd2700af@rwec.co.uk> Date: Sun, 17 Nov 2024 23:13:03 +0000 Precedence: bulk list-help: list-post: List-Id: internals.lists.php.net x-ms-reactions: disallow MIME-Version: 1.0 User-Agent: Mozilla Thunderbird Subject: Re: [PHP-DEV] RFC: Records To: internals@lists.php.net References: <68eba37e-1bfe-4509-af41-112bb196415c@app.fastmail.com> Content-Language: en-GB In-Reply-To: From: imsop.php@rwec.co.uk ("Rowan Tommins [IMSoP]") This is a multi-part message in MIME format. --------------rQWv1780KumNh803ioZG0gL4 Content-Type: text/plain; charset=UTF-8; format=flowed Content-Transfer-Encoding: 8bit Hi Rob, I'm torn on this one. On the one hand, it does look like a nice solution for adding custom value objects to the language; on the other hand, it's a lot of things that are "just slightly different" for users to get used to. On 17/11/2024 21:30, Rob Landers wrote: > One of the main reasons for the alternative creation syntax is because > I felt that "new" was misleading at best, and just plain wrong at > worst. It is also why I chose "&", to make it clear you are not > getting "a new one" but one that "just happens to exist with the > values you asked for." I'd be open to a different keyword or something > else entirely. It's just that "new" is the wrong one for records. I'm not convinced by this, because I'm not convinced the "mental model" in the RFC is the one that most developers need to care about. I think a much simpler mental model (for someone who understands the rest of PHP) is: - records are copy-on-write (like arrays, not like objects) - the === operator returns true for two records with the same value (again like arrays, not like objects) - the implementation optimises two records with the same values to share memory The third point is useful to know if you're creating a lot of them, but probably irrelevant most of the time. We also might not want to make it a hard guarantee, because there may be cases where a different trade-off is more efficient. For instance, maybe we will optimise $foo->with(a: 1)->with(b: 2)->with(c: 3) to overwrite values in-place, at the cost of an extra condition in the `===` implementation. That would be similar to some of the changes to zvals in PHP 7; for instance, "$foo=42; $bar=$foo;" will copy the value 42 to a new piece of memory, not increase a zval reference count as PHP 5 would have done. That leaves the mental model as mostly "records are a bit like arrays". Now, it's true that we don't write "new array(1,2,3)"; but I have heard people calling the "array()" syntax "the array constructor", and the manual describes it as "creating an array". Similarly, you use "constructor" and "construction" throughout the RFC. All of that makes it feel perfectly natural for me to have "new Point(1, 5)" mean "create a Point record; feel free to save memory by reusing one with the same values". > A *record* may contain a traditional constructor with zero arguments > to perform further initialization. > A *record* body may also declare properties whose values are only > mutable during a constructor call. At any other time, the property is > immutable. Talking of constructors, I find the proposed syntax rather confusing, because it's doing the same job as constructor property promotion, but in almost the opposite way: taking things out of the constructor signature, vs putting them in: readonly class Foo {    public string $bytes;    public function __construct(public int $len) {        $this->bytes = random_bytes($this->len);    } } record Foo (int $len) {    public string $bytes;    public function __construct() {        $this->bytes = random_bytes($this->len);    } } While writing this example, I realised that the behaviour is also confusing: when exactly will the constructor be called? Consider: $a = &Foo(42); // new Record; constructor called $b = &Foo(42); // re-use cached Record; is the constructor skipped? or called, but the result discarded? what if the constructor modifies $this->len? unset($a, $b); $c = &Foo(42); // does this re-use the Record? or has it been garbage collected, so this will call the constructor again? I think we should decide between two paths: - structs/records as a special kind of object, keeping as much behaviour and syntax from classes as we can; that means no "inline constructor", and probably no "pull a cached instance from memory" - structs/records as a brand new thing, with new syntax that only allows the parts that fit the model; that means no non-constructor properties, and no constructor bodies Regards, -- Rowan Tommins [IMSoP] --------------rQWv1780KumNh803ioZG0gL4 Content-Type: text/html; charset=UTF-8 Content-Transfer-Encoding: 8bit
Hi Rob,

I'm torn on this one. On the one hand, it does look like a nice solution for adding custom value objects to the language; on the other hand, it's a lot of things that are "just slightly different" for users to get used to.


On 17/11/2024 21:30, Rob Landers wrote:
One of the main reasons for the alternative creation syntax is because I felt that "new" was misleading at best, and just plain wrong at worst. It is also why I chose "&", to make it clear you are not getting "a new one" but one that "just happens to exist with the values you asked for." I'd be open to a different keyword or something else entirely. It's just that "new" is the wrong one for records.


I'm not convinced by this, because I'm not convinced the "mental model" in the RFC is the one that most developers need to care about. I think a much simpler mental model (for someone who understands the rest of PHP) is:

- records are copy-on-write (like arrays, not like objects)
- the === operator returns true for two records with the same value (again like arrays, not like objects)
- the implementation optimises two records with the same values to share memory


The third point is useful to know if you're creating a lot of them, but probably irrelevant most of the time. 

We also might not want to make it a hard guarantee, because there may be cases where a different trade-off is more efficient. For instance, maybe we will optimise $foo->with(a: 1)->with(b: 2)->with(c: 3) to overwrite values in-place, at the cost of an extra condition in the `===` implementation.

That would be similar to some of the changes to zvals in PHP 7; for instance, "$foo=42; $bar=$foo;" will copy the value 42 to a new piece of memory, not increase a zval reference count as PHP 5 would have done.


That leaves the mental model as mostly "records are a bit like arrays". Now, it's true that we don't write "new array(1,2,3)"; but I have heard people calling the "array()" syntax "the array constructor", and the manual describes it as "creating an array". Similarly, you use "constructor" and "construction" throughout the RFC.

All of that makes it feel perfectly natural for me to have "new Point(1, 5)" mean "create a Point record; feel free to save memory by reusing one with the same values".


A record may contain a traditional constructor with zero arguments to perform further initialization.
A record body may also declare properties whose values are only mutable during a constructor call. At any other time, the property is immutable.


Talking of constructors, I find the proposed syntax rather confusing, because it's doing the same job as constructor property promotion, but in almost the opposite way: taking things out of the constructor signature, vs putting them in:

readonly class Foo {
   public string $bytes;
   public function __construct(public int $len) {
       $this->bytes = random_bytes($this->len);
   }
}

record Foo (int $len) {
   public string $bytes;
   public function __construct() {
       $this->bytes = random_bytes($this->len);
   }
}

While writing this example, I realised that the behaviour is also confusing: when exactly will the constructor be called? Consider:

$a = &Foo(42); // new Record; constructor called
$b = &Foo(42); // re-use cached Record; is the constructor skipped? or called, but the result discarded? what if the constructor modifies $this->len?
unset($a, $b);
$c = &Foo(42); // does this re-use the Record? or has it been garbage collected, so this will call the constructor again?


I think we should decide between two paths:

- structs/records as a special kind of object, keeping as much behaviour and syntax from classes as we can; that means no "inline constructor", and probably no "pull a cached instance from memory"
- structs/records as a brand new thing, with new syntax that only allows the parts that fit the model; that means no non-constructor properties, and no constructor bodies


Regards,

-- 
Rowan Tommins
[IMSoP]
--------------rQWv1780KumNh803ioZG0gL4--