Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:127692 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 lists.php.net (Postfix) with ESMTPS id 05FB81A00C1 for ; Mon, 16 Jun 2025 18:31:09 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=php.net; s=mail; t=1750098549; bh=P/pxnF/hP+3ox7Hcj5zD3VO/FPspNibx3vsu3OvGZgE=; h=Date:From:To:In-Reply-To:References:Subject:From; b=gocemSVOfpJD93UY4gaBcfD12IboMGednnwbNPLagRjnaDJHxTWzXitMkTFVIfGkn WjL4koUw9nOHZO06xPWSzMkAJScf/wYzE6Cq522BYX8EYsmB7eKBR7x0OA3kOSufr5 TvJ5yfhKNskVL/OUhz3oyOiQCxi5JckNMO8TDR17x7d2KnIXWluI2O5jkraXma2SYq qXcjXgxblr7kji2w451XWo9tk1pWqbe+7Fep8/IUWNdxoaYCPkxZhTPsJMJ3gJdoV5 ns5Tmzyar/l7dwQTJA1zCQqgithnaJtk+CaoHcQYVHwLE7lGDhK9o5yH2RyyfvXmmS h11aI0WS4Raqg== Received: from php-smtp4.php.net (localhost [127.0.0.1]) by php-smtp4.php.net (Postfix) with ESMTP id 431EE180391 for ; Mon, 16 Jun 2025 18:29:05 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 4.0.1 (2024-03-25) on php-smtp4.php.net X-Spam-Level: X-Spam-Status: No, score=-2.8 required=5.0 tests=BAYES_00,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.1 X-Spam-Virus: Error (Cannot connect to unix socket '/var/run/clamav/clamd.ctl': connect: Connection refused) X-Envelope-From: Received: from fhigh-a1-smtp.messagingengine.com (fhigh-a1-smtp.messagingengine.com [103.168.172.152]) (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 ; Mon, 16 Jun 2025 18:29:02 +0000 (UTC) Received: from phl-compute-05.internal (phl-compute-05.phl.internal [10.202.2.45]) by mailfhigh.phl.internal (Postfix) with ESMTP id 4E5741140184 for ; Mon, 16 Jun 2025 14:31:01 -0400 (EDT) Received: from phl-imap-06 ([10.202.2.83]) by phl-compute-05.internal (MEProxy); Mon, 16 Jun 2025 14:31:01 -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=fm2; t=1750098661; x=1750185061; bh=5LXJ/0B6Sj Oh7OMACjgYBCBX2TnSyaSR47ux151PuDk=; b=S7rPVFBkxtojnmTnnObI+EsV5v LTllQpoykEWh5dvZQ3k81/MZAnI0zvop7CYIyHJ6d7LLKgbSYgO4iGNhEQecxvNM dZSdkWDYYaRYD7nZULnu+G7fufwDqQnyOCauXcTpVhq5xmRkqBsZKCAezJmmGeNN 6N/k3AMio+/vg8s7JARP4hrN5hiXQeVycvJn7v/1BpWOqljp1m1JNUXl83wUovyA Ps/muhdAw8Sun/DAuuY2UFHv4oL1n4rpSl+qx2DNWT9p6Hrk+YWVKatDVu/uYbD5 KO3zo/0BwwnPPEP8xSRVO/Vx9e6GoUtj6IQuhvO/YdjtQiZ4I3qZb5p2oq9w== 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=fm1; t= 1750098661; x=1750185061; bh=5LXJ/0B6SjOh7OMACjgYBCBX2TnSyaSR47u x151PuDk=; b=kSw1DwEpOFOL5fNmDXlq3sYoQwP5E8Fm1qNb1iWw95IyrZW1Nug YMu9gNIAX7Fl4UxjDB9iZLvVIq3W6VgY/jasz6M8rZ66jbt+XPteoxKDdH1tLoe8 E/2G3LFuQ25ipX0CMJDCfbklAsX8iwFKUb1hNvPmNpZHSpDMcZWHc4/DlJf6Izwm TyPrkgfS5ZoJEZ1jcho46hOFEseaJIWQ8Kjd2r+CUARFHhpSuDboX+m5+SLdZEFW /yku52cJS4SpXr9GCXNub9YB2RZE1cBD/sk0yPMnqy543a7cdwI+03rlQvlXqtDU PuQlhxE/6N9wNAvhzsjcLsR7RBaCYd7PLhg== X-ME-Sender: X-ME-Proxy-Cause: gggruggvucftvghtrhhoucdtuddrgeeffedrtddugddvjeefudcutefuodetggdotefrod ftvfcurfhrohhfihhlvgemucfhrghsthforghilhdpggftfghnshhusghstghrihgsvgdp uffrtefokffrpgfnqfghnecuuegrihhlohhuthemuceftddtnecunecujfgurhepofggff fhvffkjghfufgtsegrtderreertdejnecuhfhrohhmpedftfhosgcunfgrnhguvghrshdf uceorhhosgessghothhtlhgvugdrtghouggvsheqnecuggftrfgrthhtvghrnheptdeuje dttefhueelhfdtleeiudetlefftdduleehffegtdeihefhleeijefgveegnecuvehluhhs thgvrhfuihiivgeptdenucfrrghrrghmpehmrghilhhfrhhomheprhhosgessghothhtlh gvugdrtghouggvshdpnhgspghrtghpthhtohepuddpmhhouggvpehsmhhtphhouhhtpdhr tghpthhtohepihhnthgvrhhnrghlsheslhhishhtshdrphhhphdrnhgvth X-ME-Proxy: Feedback-ID: ifab94697:Fastmail Received: by mailuser.phl.internal (Postfix, from userid 501) id EA9BC2400094; Mon, 16 Jun 2025 14:31:00 -0400 (EDT) X-Mailer: MessagingEngine.com Webmail Interface Precedence: bulk list-help: list-post: List-Id: internals.lists.php.net x-ms-reactions: disallow MIME-Version: 1.0 X-ThreadId: T4ff567cf41cc68a3 Date: Mon, 16 Jun 2025 20:30:40 +0200 To: internals@lists.php.net Message-ID: <637b16e2-fb03-46c4-a40f-31da439bacc9@app.fastmail.com> In-Reply-To: References: Subject: Re: [PHP-DEV] How hard would it be to add a "superyield" keyword? Content-Type: multipart/alternative; boundary=a17032ff41a545928a45c31fc1bc63e4 From: rob@bottled.codes ("Rob Landers") --a17032ff41a545928a45c31fc1bc63e4 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable On Mon, Jun 16, 2025, at 20:10, Larry Garfield wrote: > On Mon, Jun 16, 2025, at 10:18 AM, Olle H=C3=A4rstedt wrote: > > Hello Internals, > > > > I was pondering a little about effect handlers today, and how they c= ould > > work as a replacement for dependency injection and mocking. Let me s= how an > > example: > > > > > > > require_once("vendor/autoload.php"); > > > > use Latitude\QueryBuilder\Engine\MySqlEngine; > > use Latitude\QueryBuilder\QueryFactory; > > use function Latitude\QueryBuilder\field; > > > > // Dummy db connection > > class Db > > { > > public function getQueryBuilder() > > { > > return new QueryFactory(new MySqlEngine()); > > } > > } > > > > interface Effect {} > > > > class QueryEffect implements Effect > > { > > public $query; > > > > public function __construct($query) > > { > > $this->query =3D $query; > > } > > } > > > > class Plugin > > { > > /* The "normal" way to do testing, by injecting the db object. N= ot > > needed here. > > public function __construct(Db $db) > > { > > $this->db =3D $db; > > } > > */ > > > > public function populateCreditCardData(&$receipt) > > { > > foreach ($receipt['items'] as &$item) { > > // 2 =3D credit card > > if ($item['payment_type'] =3D=3D 2) { > > $query =3D $this->db->getQueryBuilder() > > ->select('card_product_name ') > > ->from('card_transactions') > > ->where(field('id')->eq($item['card_transaction_= id'])) > > ->compile(); > > > > // Normal way: Call the injected dependency class di= rectly. > > //$result =3D $this->db->search($query->sql(), > > $query->params()); > > > > // Generator way, push the side-effect up the stackt= race > > using generators. > > $result =3D yield new QueryEffect($query); > > if ($result) { > > $item['card_product_name'] =3D > > $result[0]['card_product_name']; > > } > > } > > } > > } > > } > > > > // Dummy receipt > > $receipt =3D [ > > 'items' =3D> [ > > [ > > 'payment_type' =3D> 2 > > ] > > ] > > ]; > > $p =3D new Plugin(); // Database is not injected > > $gen =3D $p->populateCreditCardData($receipt); > > foreach ($gen as $effect) { > > // Call $db here instead of injecting it. > > // But now I have to propagate the $gen logic all over the call = stack, > > with "yield from"? :( > > // Effect handlers solve this by forcing an effect up in the sta= ck > > trace similar to exceptions. > > > > // Dummy db result > > $rows =3D [ > > [ > > 'card_product_name' =3D> 'KLARNA', > > ] > > ]; > > $gen->send($rows); > > } > > > > // Receipt item now has card_product_name populated properly. > > print_r($receipt); > > > > --- > > > > OK, so the problem with above code is that, in order for it to work,= you > > have to add "yield from" from the top to the bottom of the call stac= k, > > polluting the code-base similar to what happens with "async" in Java= Script. > > Also see the "Which color is your function" article [1]. > > > > For this design pattern to work seamlessly, there need to be a way t= o yield > > "all the way", so to speak, similar to what an exception does, and h= ow > > effect handlers work in OCaml [2]. > > > > The question is, would this be easy, hard, or very hard to add to the > > current PHP source code? Is it conceptually too different from gener= ators? > > Would it be easier to add a way to "jump back" from a catched except= ion > > (kinda abusing the exception use-case, but that's how effect handler= s work, > > more or less)? > > > > Thanks for reading :) > > > > Olle >=20 > Algebraic effects is a... big and interesting topic. :-) If we were t= o go that route, though, I would want to see something more formal than = just a "yield far." That's basically another kind of unchecked exceptio= n, whereas I want us to move more toward checked exceptions. >=20 > --Larry Garfield >=20 I think this might be entirely possible via an extension... there might = need to be some hooks added to php-src but nothing that would require an= RFC. =E2=80=94 Rob --a17032ff41a545928a45c31fc1bc63e4 Content-Type: text/html; charset=utf-8 Content-Transfer-Encoding: quoted-printable


On Mon, Jun 16, 2025, at 20:10, Larry Garfield wrote:<= /div>
On Mon, Jun 16,= 2025, at 10:18 AM, Olle H=C3=A4rstedt wrote:
> Hello Inter= nals,
>
> I was pondering a little about effec= t handlers today, and how they could
> work as a replacemen= t for dependency injection and mocking. Let me show an
> ex= ample:
>
> <?php
>
&= gt; require_once("vendor/autoload.php");
>
> u= se Latitude\QueryBuilder\Engine\MySqlEngine;
> use Latitude= \QueryBuilder\QueryFactory;
> use function Latitude\QueryBu= ilder\field;
>
> // Dummy db connection
<= div>> class Db
> {
>    = ; public function getQueryBuilder()
>   &nbs= p; {
>         retu= rn new QueryFactory(new MySqlEngine());
>   =   }
> }
>
> interface Effect= {}
>
> class QueryEffect implements Effect
> {
>     public $query;
>
>     public function __con= struct($query)
>     {
>&n= bsp;        $this->query =3D $quer= y;
>     }
> }
&= gt;
> class Plugin
> {
> &nb= sp;   /* The "normal" way to do testing, by injecting the db o= bject. Not
> needed here.
>   &= nbsp; public function __construct(Db $db)
>  &nbs= p;  {
>        = ; $this->db =3D $db;
>     }
>     */
>
> &n= bsp;   public function populateCreditCardData(&$receipt)
>     {
>   =       foreach ($receipt['items'] as &$item)= {
>         &= nbsp;   // 2 =3D credit card
>   &= nbsp;         if ($item['payment= _type'] =3D=3D 2) {
>      &n= bsp;          $query =3D $t= his->db->getQueryBuilder()
>    &= nbsp;           &= nbsp;    ->select('card_product_name ')
>=             =          ->from('card_transac= tions')
>        &n= bsp;            -= >where(field('id')->eq($item['card_transaction_id']))
&g= t;           &nbs= p;         ->compile();
=
>
>        =          // Normal way: Call the= injected dependency class directly.
>   &nb= sp;           &nb= sp; //$result =3D $this->db->search($query->sql(),
&g= t; $query->params());
>
>   =             =   // Generator way, push the side-effect up the stacktrace
> using generators.
>     &nb= sp;           $result = =3D yield new QueryEffect($query);
>    = ;            = ; if ($result) {
>       = ;            = ;  $item['card_product_name'] =3D
> $result[0]['card_p= roduct_name'];
>       &= nbsp;         }
>&n= bsp;            }=
>         }
<= div>>     }
> }
>
=
> // Dummy receipt
> $receipt =3D [
>&= nbsp;    'items' =3D> [
>  &nbs= p;      [
>    = ;         'payment_type' =3D>= 2
>         ]
>     ]
> ];
> $p= =3D new Plugin();  // Database is not injected
> $gen= =3D $p->populateCreditCardData($receipt);
> foreach ($g= en as $effect) {
>     // Call $db here= instead of injecting it.
>     // But = now I have to propagate the $gen logic all over the call stack,
> with "yield from"? :(
>     // E= ffect handlers solve this by forcing an effect up in the stack
> trace similar to exceptions.
>
> &n= bsp;   // Dummy db result
>   &nbs= p; $rows =3D [
>       &= nbsp; [
>        &n= bsp;    'card_product_name' =3D> 'KLARNA',
&= gt;         ]
>&nbs= p;    ];
>     $gen->= send($rows);
> }
>
> // Receipt i= tem now has card_product_name populated properly.
> print_r= ($receipt);
>
> ---
>
&= gt; OK, so the problem with above code is that, in order for it to work,= you
> have to add "yield from" from the top to the bottom = of the call stack,
> polluting the code-base similar to wha= t happens with "async" in JavaScript.
> Also see the "Which= color is your function" article [1].
>
> For = this design pattern to work seamlessly, there need to be a way to yield<= /div>
> "all the way", so to speak, similar to what an exception = does, and how
> effect handlers work in OCaml [2].
>
> The question is, would this be easy, hard, or very= hard to add to the
> current PHP source code? Is it concep= tually too different from generators?
> Would it be easier = to add a way to "jump back" from a catched exception
> (kin= da abusing the exception use-case, but that's how effect handlers work,<= /div>
> more or less)?
>
> Thanks for r= eading :)
>
> Olle

Al= gebraic effects is a... big and interesting topic. :-)  If we were = to go that route, though, I would want to see something more formal than= just a "yield far."  That's basically another kind of unchecked ex= ception, whereas I want us to move more toward checked exceptions.
=

--Larry Garfield


I think this might be entirely possible via an extensi= on... there might need to be some hooks added to php-src but nothing tha= t would require an RFC.

=E2= =80=94 Rob
--a17032ff41a545928a45c31fc1bc63e4--