Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:127685 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 3CE361A00BC for ; Mon, 16 Jun 2025 15:31:44 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=php.net; s=mail; t=1750087785; bh=sXNXIrthFmxDIkx+1nHMe/+VRI28kgfYbe9TsDAF17I=; h=Date:From:To:In-Reply-To:References:Subject:From; b=cD5VyWar+hkJj9cMeQA/vT7getL70vAsLHqYwiBhvig0DwNRblrJSDxrAtljm41mP kuIOwq0BkeWXKWvzsgOSKF0YSgom19kb/Y/kdaaZMRtD4PVs3MQAA22TTLmWFsbISM KwK3+Bk2QXtnTUrZztdC5MM/TBXvN9khXsxZsA8GQzWltbC7ahD+O3QlWJY80uFYPy Y8Nhyd1JWydPMtsdLRuNV8R8432YfeZZmruE4te8SUXlk2ZSZzd0RWJXawKgmokCZZ 8sIhstI9bTUU6JhFYaN0xo73zFsr1szjJgFeDh6qoRB1HXM5DahkWLxk6/AWlKgNM3 /H2CgfUuKPfMg== Received: from php-smtp4.php.net (localhost [127.0.0.1]) by php-smtp4.php.net (Postfix) with ESMTP id 4ED36180084 for ; Mon, 16 Jun 2025 15:29:44 +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 fout-a6-smtp.messagingengine.com (fout-a6-smtp.messagingengine.com [103.168.172.149]) (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 ; Mon, 16 Jun 2025 15:29:44 +0000 (UTC) Received: from phl-compute-05.internal (phl-compute-05.phl.internal [10.202.2.45]) by mailfout.phl.internal (Postfix) with ESMTP id 929B313803CC for ; Mon, 16 Jun 2025 11:31:42 -0400 (EDT) Received: from phl-imap-05 ([10.202.2.95]) by phl-compute-05.internal (MEProxy); Mon, 16 Jun 2025 11:31:42 -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=1750087902; x=1750174302; bh=fQU/h+xD8Z 5fScqbx0vN0rhmaP9BuyOwsCF3g48jpQE=; b=gQHwq0fc0S8CcoPyEg4txKpKMj lJG9LIhU8fKXCE+Fpj9xHOybE9jfnuEy9xGaU/7rqJonp7C9jaYjC7jhuOYXICN9 3yR+I2kSM/u8PnQO5owQGebUeZIAVGU8c+2/XrmQas7336RGfy9fFJ6dGzl82b3c qSgYHidiJv1AzBl1ldnvcMrsoy1hjicIU/eDG93qNT/Bj9BGAWCy1L5x7qeeaHaY 78rq1gggBC+lT/0ADmG/CEkZUVBOtCgws+jlzLYlu3wThEk1eDBdZeFw3HGKYSdh FiFxvwqgMjWiA8BHWT5AaOopoMg1davGB9SHHoX78jwKmV3Wx3CDTDMTSBAQ== 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= 1750087902; x=1750174302; bh=fQU/h+xD8Z5fScqbx0vN0rhmaP9BuyOwsCF 3g48jpQE=; b=n8q4Xrck6rFnNkKv9DaTrFjYB+kVfbSlQ/apNNyTCiIqgG+jDFl Uk1efomp6/QkS/G65XWLfAJJF+VbUkDPB1kMpfplwcqho1xic/QFiYlPLMPt4GK9 pbVgFrNW5RS8KCRIbk/P8BnGU/svBaS78rNKrjDUA8dfz2+XF7H+h5+HUkQuCDCM Ld8o1w3c8S+5UL+/0ouFrD7/9bKIl9JyAZejbIrq/KSrBll1ZKLF1VngQ2OQGTpa YEImGW2Rvzn9f6PTTh2G7GzDU5UsJ1U09TlEcZhEn287aYRSBiu1UcDd7brwlkZS Gb6+ReF2aivUQaaa1XbOyajj71zW2DhR1FQ== X-ME-Sender: X-ME-Proxy-Cause: gggruggvucftvghtrhhoucdtuddrgeeffedrtddugddvieelhecutefuodetggdotefrod ftvfcurfhrohhfihhlvgemucfhrghsthforghilhdpggftfghnshhusghstghrihgsvgdp uffrtefokffrpgfnqfghnecuuegrihhlohhuthemuceftddtnecunecujfgurhepofggff fhvffkjghfufgtsegrtderreertdejnecuhfhrohhmpedftfhosgcunfgrnhguvghrshdf uceorhhosgessghothhtlhgvugdrtghouggvsheqnecuggftrfgrthhtvghrnhepgfekhe ejkefftdelteevhfeklefhgfffvddvheevueduledtgeekleekheeviedvnecuffhomhgr ihhnpehsthhufhhffihithhhshhtuhhffhdrtghomhdpohgtrghmlhdrohhrghenucevlh hushhtvghrufhiiigvpedtnecurfgrrhgrmhepmhgrihhlfhhrohhmpehrohgssegsohht thhlvggurdgtohguvghspdhnsggprhgtphhtthhopedupdhmohguvgepshhmthhpohhuth dprhgtphhtthhopehinhhtvghrnhgrlhhssehlihhsthhsrdhphhhprdhnvght X-ME-Proxy: Feedback-ID: ifab94697:Fastmail Received: by mailuser.phl.internal (Postfix, from userid 501) id 1B4681820071; Mon, 16 Jun 2025 11:31:42 -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 17:31:20 +0200 To: internals@lists.php.net Message-ID: <327faa47-c602-4323-a782-e2a063bd6b60@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=73110bb0122d4df09c17d11ef673a1ed From: rob@bottled.codes ("Rob Landers") --73110bb0122d4df09c17d11ef673a1ed Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable On Mon, Jun 16, 2025, at 17:18, Olle H=C3=A4rstedt wrote: > Hello Internals, >=20 > I was pondering a little about effect handlers today, and how they cou= ld > work as a replacement for dependency injection and mocking. Let me sho= w an > example: >=20 > =20 > require_once("vendor/autoload.php"); >=20 > use Latitude\QueryBuilder\Engine\MySqlEngine; > use Latitude\QueryBuilder\QueryFactory; > use function Latitude\QueryBuilder\field; >=20 > // Dummy db connection > class Db > { > public function getQueryBuilder() > { > return new QueryFactory(new MySqlEngine()); > } > } >=20 > interface Effect {} >=20 > class QueryEffect implements Effect > { > public $query; >=20 > public function __construct($query) > { > $this->query =3D $query; > } > } >=20 > class Plugin > { > /* The "normal" way to do testing, by injecting the db object. Not > needed here. > public function __construct(Db $db) > { > $this->db =3D $db; > } > */ >=20 > 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(); >=20 > // Normal way: Call the injected dependency class dire= ctly. > //$result =3D $this->db->search($query->sql(), > $query->params()); >=20 > // Generator way, push the side-effect up the stacktra= ce > using generators. > $result =3D yield new QueryEffect($query); > if ($result) { > $item['card_product_name'] =3D > $result[0]['card_product_name']; > } > } > } > } > } >=20 > // 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 st= ack, > with "yield from"? :( > // Effect handlers solve this by forcing an effect up in the stack > trace similar to exceptions. >=20 > // Dummy db result > $rows =3D [ > [ > 'card_product_name' =3D> 'KLARNA', > ] > ]; > $gen->send($rows); > } >=20 > // Receipt item now has card_product_name populated properly. > print_r($receipt); >=20 > --- >=20 > OK, so the problem with above code is that, in order for it to work, y= ou > have to add "yield from" from the top to the bottom of the call stack, > polluting the code-base similar to what happens with "async" in JavaSc= ript. > Also see the "Which color is your function" article [1]. >=20 > For this design pattern to work seamlessly, there need to be a way to = yield > "all the way", so to speak, similar to what an exception does, and how > effect handlers work in OCaml [2]. >=20 > 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 generat= ors? > Would it be easier to add a way to "jump back" from a catched exception > (kinda abusing the exception use-case, but that's how effect handlers = work, > more or less)? >=20 > Thanks for reading :) >=20 > Olle >=20 > --- >=20 > [1] - > https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-funct= ion/ >=20 > [2] - https://ocaml.org/manual/5.3/effects.html You want to jump out of the current stack frame to one a higher one, and= possibly resume execution? The best way to do that is to use fibers. They basically do exactly this= behavior.=20 =E2=80=94 Rob --73110bb0122d4df09c17d11ef673a1ed Content-Type: text/html; charset=utf-8 Content-Transfer-Encoding: quoted-printable


On Mon, Jun 16, 2025, at 17:18, Olle H=C3=A4rstedt wro= te:
Hello Internals,

I was pondering a little= about effect handlers today, and how they could
work as a rep= lacement for dependency injection and mocking. Let me show an
= example:

<?php

requ= ire_once("vendor/autoload.php");

use Latitude\Q= ueryBuilder\Engine\MySqlEngine;
use Latitude\QueryBuilder\Quer= yFactory;
use function Latitude\QueryBuilder\field;
=
// Dummy db connection
class Db
{
    public function getQueryBuilder()
  =   {
        return new QueryFactory(n= ew MySqlEngine());
    }
}

<= /div>
interface Effect {}

class QueryEffect= implements Effect
{
    public $query;

    public function __construct($query)=
    {
        $this-&= gt;query =3D $query;
    }
}
class Plugin
{
    /* The "nor= mal" way to do testing, by injecting the db object. Not
needed= here.
    public function __construct(Db $db)
=
    {
        $this->db = =3D $db;
    }
    */
=
    public function populateCreditCardData(&= ;$receipt)
    {
      &nbs= p; foreach ($receipt['items'] as &$item) {
    &= nbsp;       // 2 =3D credit card
    =         if ($item['payment_type'] =3D=3D 2) {
<= div>                $query =3D $= this->db->getQueryBuilder()
        =             ->select('card_product_name= ')
                &n= bsp;   ->from('card_transactions')
     = ;               ->where(field('id'= )->eq($item['card_transaction_id']))
      &= nbsp;             ->compile();

               = // Normal way: Call the injected dependency class directly.
&= nbsp;               //$result =3D $th= is->db->search($query->sql(),
$query->params());

             =   // Generator way, push the side-effect up the stacktrace
using generators.
           =     $result =3D yield new QueryEffect($query);
&nbs= p;               if ($result) {
=
                  &nbs= p; $item['card_product_name'] =3D
$result[0]['card_product_nam= e'];
                }=
            }
  =       }
    }
}
<= br>
// Dummy receipt
$receipt =3D [
 =   'items' =3D> [
        [
<= div>            'payment_type' =3D> 2
        ]
    ]
];
$p =3D new Plugin();  // Database is not injected
$gen =3D $p->populateCreditCardData($receipt);
for= each ($gen as $effect) {
    // Call $db here instea= d of injecting it.
    // But now I have to propagat= e the $gen logic all over the call stack,
with "yield from"? := (
    // Effect handlers solve this by forcing an ef= fect up in the stack
trace similar to exceptions.
    // Dummy db result
  &nbs= p; $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 w= ork, you
have to add "yield from" from the top to the bottom o= f the call stack,
polluting the code-base similar to what happ= ens with "async" in JavaScript.
Also see the "Which color is y= our function" article [1].

For this design patt= ern to work seamlessly, there need to be a way to yield
"all t= he way", so to speak, similar to what an exception does, and how
effect handlers work in OCaml [2].

The quest= ion is, would this be easy, hard, or very hard to add to the
c= urrent PHP source code? Is it conceptually too different from generators= ?
Would it be easier to add a way to "jump back" from a catche= d exception
(kinda abusing the exception use-case, but that's = how effect handlers work,
more or less)?

<= div>Thanks for reading :)

Olle

You want to jump out of the current s= tack frame to one a higher one, and possibly resume execution?

The best way to do that is to use fibers. They basically= do exactly this behavior. 

=E2=80=94 Rob
--73110bb0122d4df09c17d11ef673a1ed--