Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:127686 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 938481A00BC for ; Mon, 16 Jun 2025 15:36:09 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=php.net; s=mail; t=1750088050; bh=7/XKhzA0CteKxEnMaxThKcbrH8Acb8PhPyqQeuYH+HQ=; h=References:In-Reply-To:From:Date:Subject:To:Cc:From; b=BCtfehMXv4aqDmNhxWR/jCkuefIaxypjPVFHH8ufmCuHN8HZcu70Nu8mVOJq2dd6H w3Rcb7psg+2115MSOSNrBCl0XjZ+rrbQRgrnKh8wGqhYBT8nGm5upDFm1LhNWedm42 vMjmUrjOxGC9hX0XXxYhjGfNMHqLFvjw28ZQ8tgSMLiFOfl8E3/N9KNSD9tdfcyRwx s3bhyQkb4D7BKX4VazkjFdFIoMNeam4UMBJT8FEjHBeIdZnbwDQR1ahadWdCwHO5Hz 4bWYeXdlDdT22+bXTbpLj6eCBqAvw0D34yd/+Ioi5Oe1peFv3esXJ7aVyaj8CEj2dc cne7/vRgt7GdA== Received: from php-smtp4.php.net (localhost [127.0.0.1]) by php-smtp4.php.net (Postfix) with ESMTP id BF30E1801D6 for ; Mon, 16 Jun 2025 15:34:07 +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=-0.2 required=5.0 tests=BAYES_40,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,DMARC_PASS,FREEMAIL_FROM, HTML_MESSAGE,RCVD_IN_DNSWL_NONE,RCVD_IN_MSPIKE_H3,RCVD_IN_MSPIKE_WL, SPF_HELO_NONE,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 mail-ej1-f51.google.com (mail-ej1-f51.google.com [209.85.218.51]) (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:34:05 +0000 (UTC) Received: by mail-ej1-f51.google.com with SMTP id a640c23a62f3a-acae7e7587dso709353666b.2 for ; Mon, 16 Jun 2025 08:36:04 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1750088163; x=1750692963; darn=lists.php.net; h=cc:to:subject:message-id:date:from:in-reply-to:references :mime-version:from:to:cc:subject:date:message-id:reply-to; bh=TPNjtm2xlwZ91pf8fWfDJdEgLk9rC0q/Dznp9iN0K50=; b=dsyJ9xPG4ZsYckwVZMcgEq5V/MwNrNDXR0fg35cKB+ZoIX5cIxafzWek5CA1qIu3Lh FxfxYcwUWHRBTkrG2j4bFRekkIKEUMBYe8HI2NTkBgso+mq5QpQldwwzAnLb5XXwoRfs NJboDcGj5f4YJ9CxZ1LrPbfuhVaqpvAcSZJQL/8TQKtvaVMslYRAnP9r8wSxQoQO5+CX OaRiEwJVp0IAtQAQRYvFRShZHo6cS8wrQDy7oDIYJ+dZ9aPuzkII41mxnsByq5IM4TR3 FlgkPt/z1InUmtmXQc18YfthBSt0EmTGoZep3HxJiEgOCmNuSQvtaE3AfqeSa/QkxuSR WGqA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1750088163; x=1750692963; h=cc:to:subject:message-id:date:from:in-reply-to:references :mime-version:x-gm-message-state:from:to:cc:subject:date:message-id :reply-to; bh=TPNjtm2xlwZ91pf8fWfDJdEgLk9rC0q/Dznp9iN0K50=; b=F7LmhcORoy9erqeT4dbpL2575Rh9JjDGtRrdJdTz64ehqupWGeipq59c2QGVxXe2TW O9xqK6b0pXj3JybtLOLKq2JdR2/ieVIgbxCbNTce68JpWR9JSc5jUg1T8x/QqyzUfnA1 2TPVtucFumLQHHNYV21IdyOpUVH5vhGzJKdAGlKwjssm70j2a/jL8ZxngdYbnrpaIw70 eYlRS5dWUtU+/pD6B2iQjs6x6Dfg3sGyNqCufF742U51iN5mDMU1EEqKJ5AjZJo5AZ++ 26EYAlR3iWtt6lkuAVhIlPnS6Wiil84iRF5BDcWIW9rOPXo2BJyH4JPykZffiETgcA6s x+oA== X-Gm-Message-State: AOJu0YwuwPP1jR5yzkzlsDahQgYiZgL6zm0YXr9bH0eoOBG7wqaHh5vY F7juZRjYcRpOFGuzHUalYI5EJv9X2ruRXToTt5DHnCQa6kca7QKakCjM/quT6Q/udo63pxn09xe sDyH8L9SeoBLcga13LI//0w+eUCGCjfs8MQ== X-Gm-Gg: ASbGncu6NRKovQdcuUBagpu/F38vzXL8iaahSZLr/xJuax7ievvQHzipWMTbJsmnTGG 6123IR56cnNWBBviBju3Y0QpSUmtG9MB6n0CsooaTzYqVnpHL9/TEQ971H+foR4qnxnB8ppERwO +bWtBJ5ga4XeghKNpAgHgjIw6ncuO1EU9p0bLRmG6Dq/3O X-Google-Smtp-Source: AGHT+IEqT6CJATFrEFAiZo6Rl4XKq1hvRhCzb85uHt7Vskh/L/8cNuuVu0oN2S2ibFQZOrHBOMTH8ZOi5nMi3GnARl4= X-Received: by 2002:a17:907:d24:b0:add:fc52:898f with SMTP id a640c23a62f3a-adfad4a4824mr765917066b.42.1750088163101; Mon, 16 Jun 2025 08:36:03 -0700 (PDT) Precedence: bulk list-help: list-post: List-Id: internals.lists.php.net x-ms-reactions: disallow MIME-Version: 1.0 References: <327faa47-c602-4323-a782-e2a063bd6b60@app.fastmail.com> In-Reply-To: <327faa47-c602-4323-a782-e2a063bd6b60@app.fastmail.com> Date: Mon, 16 Jun 2025 17:35:50 +0200 X-Gm-Features: AX0GCFuYOzyue60zOH99FOke0ujp6tIpTCl6fWm_J9zB7og0oRJJjXhkowwvL64 Message-ID: Subject: Re: [PHP-DEV] How hard would it be to add a "superyield" keyword? To: Rob Landers Cc: internals@lists.php.net Content-Type: multipart/alternative; boundary="00000000000049902b0637b22701" From: olleharstedt@gmail.com (=?UTF-8?Q?Olle_H=C3=A4rstedt?=) --00000000000049902b0637b22701 Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable Hi, Great tip, thank you, I had forgot about those! Will try. Olle Den m=C3=A5n 16 juni 2025 kl 17:32 skrev Rob Landers : > > > On Mon, Jun 16, 2025, at 17:18, Olle H=C3=A4rstedt wrote: > > Hello Internals, > > I was pondering a little about effect handlers today, and how they could > work as a replacement for dependency injection and mocking. Let me show a= n > 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. Not > 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 directl= y. > //$result =3D $this->db->search($query->sql(), > $query->params()); > > // Generator way, push the side-effect up the stacktrace > 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 stack > 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 stack, > polluting the code-base similar to what happens with "async" in JavaScrip= t. > Also see the "Which color is your function" article [1]. > > For this design pattern to work seamlessly, there need to be a way to yie= ld > "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 conceptually too different from generators= ? > 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 wor= k, > more or less)? > > Thanks for reading :) > > Olle > > --- > > [1] - > https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function= / > > [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. > > =E2=80=94 Rob > --00000000000049902b0637b22701 Content-Type: text/html; charset="UTF-8" Content-Transfer-Encoding: quoted-printable
Hi,

Great tip, thank you, I = had forgot about those! Will try.

Olle
=
Den m=C3=A5n 16 juni 2025 kl 17:32 skrev Rob Landers <ro= b@bottled.codes>:


On Mon, Jun 16, 2025, a= t 17:18, Olle H=C3=A4rstedt wrote:
Hello Internals,

=
I was pondering a little about effect handlers today, and how th= ey could
work as a replacement for dependency injection and mocki= ng. Let me show an
example:

<?php

require_once("vendor/autoload.php");
<= div>
use Latitude\QueryBuilder\Engine\MySqlEngine;
= use Latitude\QueryBuilder\QueryFactory;
use function Latitude\Que= ryBuilder\field;

// Dummy db connection
= class Db
{
=C2=A0 =C2=A0 public function getQueryBuilde= r()
=C2=A0 =C2=A0 {
=C2=A0 =C2=A0 =C2=A0 =C2=A0 return = new QueryFactory(new MySqlEngine());
=C2=A0 =C2=A0 }
}<= /div>

interface Effect {}

class= QueryEffect implements Effect
{
=C2=A0 =C2=A0 public $= query;

=C2=A0 =C2=A0 public function __construct($= query)
=C2=A0 =C2=A0 {
=C2=A0 =C2=A0 =C2=A0 =C2=A0 $thi= s->query =3D $query;
=C2=A0 =C2=A0 }
}
class Plugin
{
=C2=A0 =C2=A0 /* The "n= ormal" way to do testing, by injecting the db object. Not
ne= eded here.
=C2=A0 =C2=A0 public function __construct(Db $db)
=C2=A0 =C2=A0 {
=C2=A0 =C2=A0 =C2=A0 =C2=A0 $this->db = =3D $db;
=C2=A0 =C2=A0 }
=C2=A0 =C2=A0 */
=C2=A0 =C2=A0 public function populateCreditCardData(&$rece= ipt)
=C2=A0 =C2=A0 {
=C2=A0 =C2=A0 =C2=A0 =C2=A0 foreac= h ($receipt['items'] as &$item) {
=C2=A0 =C2=A0 =C2= =A0 =C2=A0 =C2=A0 =C2=A0 // 2 =3D credit card
=C2=A0 =C2=A0 =C2= =A0 =C2=A0 =C2=A0 =C2=A0 if ($item['payment_type'] =3D=3D 2) {
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 $query =3D $t= his->db->getQueryBuilder()
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 ->select('card_product_name &= #39;)
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 ->from('card_transactions')
=C2=A0 =C2=A0 = =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 ->where(field(&#= 39;id')->eq($item['card_transaction_id']))
=C2=A0 = =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 ->compile= ();

=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 // Normal way: Call the injected dependency class directly.
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 //$result =3D= $this->db->search($query->sql(),
$query->params());<= /div>

=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 = =C2=A0 // Generator way, push the side-effect up the stacktrace
u= sing generators.
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A0 $result =3D yield new QueryEffect($query);
=C2=A0 =C2=A0 = =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 if ($result) {
=C2=A0 = =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 $item['c= ard_product_name'] =3D
$result[0]['card_product_name'= ];
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 }
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 }
=C2=A0 =C2=A0 = =C2=A0 =C2=A0 }
=C2=A0 =C2=A0 }
}

<= div>// Dummy receipt
$receipt =3D [
=C2=A0 =C2=A0 '= items' =3D> [
=C2=A0 =C2=A0 =C2=A0 =C2=A0 [
=C2= =A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 'payment_type' =3D> 2
=C2=A0 =C2=A0 =C2=A0 =C2=A0 ]
=C2=A0 =C2=A0 ]
];=
$p =3D new Plugin(); =C2=A0// Database is not injected
$gen =3D $p->populateCreditCardData($receipt);
foreach ($gen = as $effect) {
=C2=A0 =C2=A0 // Call $db here instead of injecting= it.
=C2=A0 =C2=A0 // But now I have to propagate the $gen logic = all over the call stack,
with "yield from"? :(
=C2=A0 =C2=A0 // Effect handlers solve this by forcing an effect up in th= e stack
trace similar to exceptions.

=C2= =A0=C2=A0=C2=A0 // Dummy db result
=C2=A0 =C2=A0 $rows =3D [
=C2=A0 =C2=A0 =C2=A0 =C2=A0 [
=C2=A0 =C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A0 'card_product_name' =3D> 'KLARNA',
=C2=A0 =C2=A0 =C2=A0 =C2=A0 ]
=C2=A0 =C2=A0 ];
=C2= =A0 =C2=A0 $gen->send($rows);
}

// Re= ceipt 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
ha= ve to add "yield from" from the top to the bottom of the call sta= ck,
polluting the code-base similar to what happens with "as= ync" in JavaScript.
Also see the "Which color is your f= unction" article [1].

For this design pattern= to work seamlessly, there need to be a way to yield
"all th= e way", so to speak, similar to what an exception does, and how
<= div>effect handlers work in OCaml [2].

The questio= n is, would this be easy, hard, or very hard to add to the
curren= t PHP source code? Is it conceptually too different from generators?
<= div>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)?

Thanks for reading :)

Olle

---

[1] -


You want to jump out of t= he current stack frame to one a higher one, and possibly resume execution?<= /div>

The best way to do that is to use fibers. They bas= ically do exactly this behavior.=C2=A0

=E2=80=94 Rob
--00000000000049902b0637b22701--