Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:124396 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 7AA0B1A00B7 for ; Thu, 11 Jul 2024 17:30:40 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=php.net; s=mail; t=1720719126; bh=jWEagflSSF3Pm3UIHSSU+gOVIkCBFhe7PkHeUNkCMDE=; h=In-Reply-To:References:Date:From:To:Subject:From; b=JR3/5zm/SCYZH1Hx7bzFI26JYNeRDMfi+7+YD5v2IiFQmTunlEUi1FmY5bg92e9rl 2VxSOIHLTDQwexhUC05HD6n5fX9azgy0Xs+naA8SLUnm5qKeD8FNJqrafpUypvtVAG ufok85Tyb6sBRKeEx9G3IckpvZUypCfkcMla6nWnJrtq9JjfLtY1TPwhXB+K4zkAdw Orp7HWi5a3+U72LNfPoLDjXDDbbhL1UEfNbA/9ag+O2UkxDYKgPflQOadRrULTXx1U s5+sIAL+Lb8DhOsYppejDBNgX7kp4YHf0FHUmJJOj7I6/1Pqx+5ZHnP1lSZQcjOs++ UKpfrPx3kwVkQ== Received: from php-smtp4.php.net (localhost [127.0.0.1]) by php-smtp4.php.net (Postfix) with ESMTP id A15EE1806CC for ; Thu, 11 Jul 2024 17:32:05 +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,SPF_HELO_PASS,SPF_PASS autolearn=no autolearn_force=no version=4.0.0 X-Spam-Virus: No X-Envelope-From: Received: from fhigh5-smtp.messagingengine.com (fhigh5-smtp.messagingengine.com [103.168.172.156]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits)) (No client certificate requested) by php-smtp4.php.net (Postfix) with ESMTPS for ; Thu, 11 Jul 2024 17:32:05 +0000 (UTC) Received: from compute1.internal (compute1.nyi.internal [10.202.2.41]) by mailfhigh.nyi.internal (Postfix) with ESMTP id 215311140EAB for ; Thu, 11 Jul 2024 13:30:38 -0400 (EDT) Received: from imap49 ([10.202.2.99]) by compute1.internal (MEProxy); Thu, 11 Jul 2024 13:30:38 -0400 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=bottled.codes; 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=1720719038; x=1720805438; bh=1l14PorVCU sneyPxRjdk1Sm7UG5wI3Qd5CdNe1vDcqc=; b=o+UV0sM0R0eNnIsv20Dn2F/BVk Cl+OrfhoxHDQsG3cg453uSOPR10HeoWaychUfNO3WqnaKLPETjWYZvGkHlancw75 33Y0U7lbFo6byLFPjU8qJHbPdTx3w8AWFOYCfnj/Idr7BwRs2w9CyAauJtbc0QG6 VfjWV5fpFXexDB85n9weTI5S2SQUJMEhtWOQWqpyTuFxJu3FKxEP/Is07TVFLRCl PMFU2kMO/TRWdR4Mo1/aB6ylZm/BojGi2V5wCYeKI/91dMh34veZdV+fvadyVaHB d4X642/1BBBwLKRfVB33f638oAGPasW1i1OPdCr0hqYUJPn7j6Z4XIoq9QPg== 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-proxy:x-me-sender:x-me-sender:x-sasl-enc; s= fm2; t=1720719038; x=1720805438; bh=1l14PorVCUsneyPxRjdk1Sm7UG5w I3Qd5CdNe1vDcqc=; b=O4hspuyfhPHdLVyKEdH/G+RUZ7TV7OM/lgy2KNIojlwJ exxKLkujy1vhibYaI8g2WtHjuTILZ7fN99UpM4LFedXw0UOax4CpQtt3mEm3aLMF S2I/uR/G1ao0c0h+Y4c998ZysLwAVjVNq0CG6NkF9Kv1IQW1oZgoCwplVqReCxqM FS9ayZstRBlqaNleM7vTr2klEefy2b9LQLW17dQiOWMzWam34vONA4MAs673msoK sxvOi0d0tSH22rj1sI0LRF7C/bulPCaLwkWvCbazHGDFPW/CBcKGT3j1P+yaOLu1 zkBZtZXyAGH1BwhAmQlomq24UbY8a/T5LkQydK9HqA== X-ME-Sender: X-ME-Proxy-Cause: gggruggvucftvghtrhhoucdtuddrgeeftddrfeeggdduudefucetufdoteggodetrfdotf fvucfrrhhofhhilhgvmecuhfgrshhtofgrihhlpdfqfgfvpdfurfetoffkrfgpnffqhgen uceurghilhhouhhtmecufedttdenucenucfjughrpefofgggkfgjfhffhffvufgtsegrtd erreerreejnecuhfhrohhmpedftfhosgcunfgrnhguvghrshdfuceorhhosgessghothht lhgvugdrtghouggvsheqnecuggftrfgrthhtvghrnhepfeefudfhudduieekkedugffhud fgleejgfekgefhvdeikeelvddvjeehteegteegnecuvehluhhsthgvrhfuihiivgeptden ucfrrghrrghmpehmrghilhhfrhhomheprhhosgessghothhtlhgvugdrtghouggvsh X-ME-Proxy: Feedback-ID: ifab94697:Fastmail Received: by mailuser.nyi.internal (Postfix, from userid 501) id A1F0815A0093; Thu, 11 Jul 2024 13:30:36 -0400 (EDT) X-Mailer: MessagingEngine.com Webmail Interface User-Agent: Cyrus-JMAP/3.11.0-alpha0-568-g843fbadbe-fm-20240701.003-g843fbadb Precedence: bulk list-help: list-post: List-Id: internals.lists.php.net MIME-Version: 1.0 Message-ID: <6257b4ed-39b3-4dee-a277-d532dfa53719@app.fastmail.com> In-Reply-To: <5b87c29d-dfb9-4b47-957c-f89bf0209640@app.fastmail.com> References: <14b769e9-acb7-46d2-85ab-f276e82f3069@app.fastmail.com> <0cfec69f-2b0c-4c1e-9e47-e6379cf90ea2@app.fastmail.com> <5b87c29d-dfb9-4b47-957c-f89bf0209640@app.fastmail.com> Date: Thu, 11 Jul 2024 19:29:45 +0200 To: internals@lists.php.net Subject: Re: [PHP-DEV] [low priority] WeakMaps with scalar keys Content-Type: multipart/alternative; boundary=9748cb7373284e47a1591d1898ef786a From: rob@bottled.codes ("Rob Landers") --9748cb7373284e47a1591d1898ef786a Content-Type: text/plain;charset=utf-8 Content-Transfer-Encoding: quoted-printable On Thu, Jul 11, 2024, at 08:45, Rob Landers wrote: >=20 >=20 > On Thu, Jul 11, 2024, at 01:11, Benjamin Morel wrote: >>> The answer is: it depends. If you don=E2=80=99t need the array to cl= ean up after itself, you can indeed use an array of WeakReference to get= most of the way there. If you want it to clean up after an object gets = removed, you either need to add support to the stored object=E2=80=99s d= estructor (which isn=E2=80=99t always possible for built-in or final typ= es), or create your own garbage collector that scans the array.=20 >>=20 >> It is indeed doable in userland using WeakReferences, with a small pe= rformance penalty: >>=20 >> ``` >> class ReverseWeakMap implements Countable, IteratorAggregate, ArrayAc= cess >> { >> /** >> * @var array >> */ >> private array $map =3D []; >>=20 >> public function count(): int >> { >> foreach ($this->map as $value =3D> $weakReference) { >> if ($weakReference->get() =3D=3D=3D null) { >> unset($this->map[$value]); >> } >> } >>=20 >> return count($this->map); >> } >>=20 >> public function getIterator(): Generator >> { >> foreach ($this->map as $value =3D> $weakReference) { >> $object =3D $weakReference->get(); >>=20 >> if ($object =3D=3D=3D null) { >> unset($this->map[$value]); >> } else { >> yield $value =3D> $object; >> } >> } >> } >>=20 >> public function offsetExists(mixed $offset) >> { >> if (isset($this->map[$offset])) { >> $object =3D $this->map[$offset]->get(); >>=20 >> if ($object !=3D=3D null) { >> return true; >> } >>=20 >> unset($this->map[$offset]); >> } >>=20 >> return false; >> } >>=20 >> public function offsetGet(mixed $offset): object >> { >> if (isset($this->map[$offset])) { >> $object =3D $this->map[$offset]->get(); >>=20 >> if ($object !=3D=3D null) { >> return $object; >> } >>=20 >> unset($this->map[$offset]); >> } >>=20 >> throw new Exception('Undefined offset'); >> } >>=20 >> public function offsetSet(mixed $offset, mixed $value): void >> { >> $this->map[$offset] =3D WeakReference::create($value); >> } >>=20 >> public function offsetUnset(mixed $offset): void >> { >> unset($this->map[$offset]); >> } >> } >> ```=20 >>=20 >>> Now that I think about it, it might be simpler to add an =E2=80=9Con= Remove()=E2=80=9D method that takes a callback for the WeakReference cla= ss.=20 >>>=20 >>> =E2=80=94 Rob >>=20 >>=20 >> A callback when an object goes out of scope would be a great addition= to both WeakReference & WeakMap indeed, it would allow custom userland = weak maps like the above, with next to no performance penalty! >>=20 >> - Benjamin >> =20 >=20 > The callback is surprisingly easy to implement, at least for WeakRefer= ence (did it in about 10 minutes on the train as a hack). I haven=E2=80=99= t looked into WeakMap yet, but I suspect much of the plumbing is the sam= e.=20 >=20 > I also looked into the ReverseWeakMap a bit and it seems there are jus= t too many foorguns to make it worthwhile. For example: >=20 > $reverseWeakMap[$key] =3D new Obj(); >=20 > is actually a noop as in it does absolutely nothing. It gets worse, bu= t I won=E2=80=99t bore you with the details since I won=E2=80=99t be doi= ng it.=20 >=20 > Anyway, while I feel like the implementation for a callback will be ex= tremely straightforward and the RFC rather simple, I need to go back and= read the original discussion threads for this feature first to see if a= callback was addressed. So, still not until after 8.4. >=20 > =E2=80=94 Rob It looks like this idea was brought up a couple of times on internals bu= t the conversation always died out. That being said, it never made it pa= st that point. ... WeakReference is relatively easy to reason about, I'm thinking something= like the following // WeakReference::onRemove(callable): void $wr->onRemove(fn() =3D> doSomething()); If your callable closes over the item in the weak reference, I feel that= a warning should be emitted. However, this will depend on implementatio= n details, but it would be a "really nice to have" since that is a class= of errors easily avoided. As for the lifecycle, this will be called AFTER the item is removed from= the weak reference. WeakMap is a bit more nebulous, but I'm thinking something like the foll= owing // WeakMap::onRemove(callable): void $wm->onRemove(fn($value) =3D> doSomething($value)); In this case, the callable is given the value of the key (since the key = is already gone), but is otherwise exactly the same as WeakReference. I'= m even toying with the idea of omitting the value altogether. =E2=80=94 Rob --9748cb7373284e47a1591d1898ef786a Content-Type: text/html;charset=utf-8 Content-Transfer-Encoding: quoted-printable
On Thu, Ju= l 11, 2024, at 08:45, Rob Landers wrote:


On Thu, Jul = 11, 2024, at 01:11, Benjamin Morel wrote:
The answer i= s: it depends. If you don=E2=80=99t need the array to clean up after its= elf, you can indeed use an array of WeakReference to get most of the way= there. If you want it to clean up after an object gets removed, you eit= her need to add support to the stored object=E2=80=99s destructor (which= isn=E2=80=99t always possible for built-in or final types), or create y= our own garbage collector that scans the array. 

It is indeed doable in userland using Weak= References, with a small performance penalty:

```
class ReverseWeakMap implements Countable, Ite= ratorAggregate, ArrayAccess
{
    = /**
     * @var array<int|string, WeakRe= ference>
     */
  &n= bsp; private array $map =3D [];

  &nbs= p; public function count(): int
    {
<= div>        foreach ($this->map as $value =3D>= $weakReference) {
          &nbs= p; if ($weakReference->get() =3D=3D=3D null) {
  &= nbsp;             unset($this->map[$val= ue]);
            }
        }

  =       return count($this->map);
  &= nbsp; }

    public function getIt= erator(): Generator
    {
  &= nbsp;     foreach ($this->map as $value =3D> $weakRefere= nce) {
            $object =3D= $weakReference->get();

    &n= bsp;       if ($object =3D=3D=3D null) {
&n= bsp;               unset($this->ma= p[$value]);
            } el= se {
              &nbs= p; yield $value =3D> $object;
      &nbs= p;     }
        }
=
    }

    public f= unction offsetExists(mixed $offset)
    {
        if (isset($this->map[$offset])) {=
            $object =3D $th= is->map[$offset]->get();

   = ;         if ($object !=3D=3D null) {
=                 return true;
=
            }
            unset($this->ma= p[$offset]);
        }
<= br>
        return false;
&n= bsp;   }

    public function= offsetGet(mixed $offset): object
    {
        if (isset($this->map[$offset])) {
            $object =3D $this= ->map[$offset]->get();

    =         if ($object !=3D=3D null) {
&n= bsp;               return $object;
            }
            unset($this->m= ap[$offset]);
        }
=
        throw new Exception('Undefine= d offset');
    }

&= nbsp;   public function offsetSet(mixed $offset, mixed $value): voi= d
    {
       = ; $this->map[$offset] =3D WeakReference::create($value);
    }

    public func= tion offsetUnset(mixed $offset): void
    {
<= /div>
        unset($this->map[$offset]);
=
    }
}
``` =

Now that I think about it, it might be s= impler to add an =E2=80=9ConRemove()=E2=80=9D method that takes a callba= ck for the WeakReference class. 

=E2=80=94 Rob


A callback when an object goes= out of scope would be a great addition to both WeakReference & Weak= Map indeed, it would allow custom userland weak maps like the above, wit= h next to no performance penalty!

- Benjami= n
 

=
The callback is surprisingly easy to implement, at least for WeakRe= ference (did it in about 10 minutes on the train as a hack). I haven=E2=80= =99t looked into WeakMap yet, but I suspect much of the plumbing is the = same. 

I also looked into the ReverseW= eakMap a bit and it seems there are just too many foorguns to make it wo= rthwhile. For example:

$reverseWeakMap[$key= ] =3D new Obj();

is actually a noop as in i= t does absolutely nothing. It gets worse, but I won=E2=80=99t bore you w= ith the details since I won=E2=80=99t be doing it. 
<= br>
Anyway, while I feel like the implementation for a callbac= k will be extremely straightforward and the RFC rather simple, I need to= go back and read the original discussion threads for this feature first= to see if a callback was addressed. So, still not until after 8.4.
<= /div>

=E2=80=94 Rob
<= /blockquote>

It looks like this idea was brought up a= couple of times on internals but the conversation always died out. That= being said, it never made it past that point.

<= div>...

WeakReference is relatively easy to rea= son about, I'm thinking something like the following

<= /div>
// WeakReference::onRemove(callable): void
$wr->o= nRemove(fn() =3D> doSomething());

If you= r callable closes over the item in the weak reference, I feel that a war= ning should be emitted. However, this will depend on implementation deta= ils, but it would be a "really nice to have" since that is a class of er= rors easily avoided.

As for the lifecycle, = this will be called AFTER the item is removed from the weak reference.


WeakMap is a bit more nebulou= s, but I'm thinking something like the following

// WeakMap::onRemove(callable): void
$wm->onRemov= e(fn($value) =3D> doSomething($value));

= In this case, the callable is given the value of the key (since the key = is already gone), but is otherwise exactly the same as WeakReference. I'= m even toying with the idea of omitting the value altogether.
<= div>
=E2=80=94 Rob
--9748cb7373284e47a1591d1898ef786a--