Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:125232 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 E91F91A00C5 for ; Sun, 25 Aug 2024 19:37:06 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=php.net; s=mail; t=1724614739; bh=foU6XtgbDtroy4A4DCwE9jtqvXIueeDSD+XoRTE+YXw=; h=Date:From:To:In-Reply-To:References:Subject:From; b=IUL+OGlshYXLFqEd9cItP4BhUHEepmJ93VbDTC97JITWfDZZvGbUhwUik49WtbF7B 1efnUfrUAM3oqTOVcqF3kjojJFDW5e2V/o92BcYsw/IKIzgY/AGVIdIOWtrstvpvhF iUzXxj/xj3FL5kMApTB8/PWdokfTGYPgC2Kc/CcRdb5gWwZorSClJOpuSg3jyloyPj zDvy81NegWcG4LMMSzLATqMJKYoQmPdePDmigd4JM481xwSXDHePs0K7SRupgntJ1Y E/xDc9BvU8dJSmCqHMNefzVbAWROFRepjsawQYqL+gVh3PNi1rdDOHL8AnG/pDo42E D2RC865QbBgcA== Received: from php-smtp4.php.net (localhost [127.0.0.1]) by php-smtp4.php.net (Postfix) with ESMTP id D06DB1801D6 for ; Sun, 25 Aug 2024 19:38:54 +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 fout4-smtp.messagingengine.com (fout4-smtp.messagingengine.com [103.168.172.147]) (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, 25 Aug 2024 19:38:52 +0000 (UTC) Received: from phl-compute-03.internal (phl-compute-03.nyi.internal [10.202.2.43]) by mailfout.nyi.internal (Postfix) with ESMTP id C9C261387F05 for ; Sun, 25 Aug 2024 15:36:59 -0400 (EDT) Received: from phl-imap-09 ([10.202.2.99]) by phl-compute-03.internal (MEProxy); Sun, 25 Aug 2024 15:36:59 -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=1724614619; x=1724701019; bh=Yd5/JrfZUg RiM2c7T33uYx1lM+Ayd/j7Mru1+wzg26A=; b=MsRpschQ0DkjhiP374VnjT4g04 Tw4wQd9rUJIUYYO4d944o/Gdn4J3sNlK8N9AWGHS+q7fEKhEm+/YjLah9LFWx0OM JrooU2eZZjN1Y9SW4S1PuoFJg3H5qPV3fcstt1IQVh1uSoN6ByeP9GTDubaIUWbB 6E/77p16NeBrslHCkzKHsNxbW7HYe4PYs/grNUEpaZMogMPL3Ois0mlrKuVUAUz+ gAzS+V1dtMs7Z7ZYepSmRoQWZTaGJEKfgqNYjIWHvTxVgxRPQADJ0RDwCIbYUBKh 6lb7a9gmWC4JhpiYlSlFjykKhEQnR6wGxNlK5K6q+fwEnm4d1yQJEFWc9JpA== 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= fm1; t=1724614619; x=1724701019; bh=Yd5/JrfZUgRiM2c7T33uYx1lM+Ay d/j7Mru1+wzg26A=; b=gUWinO6nCmU+kb9L9sOKNbuLDnvCeFQ/UgC9gaAXu/Hj okmH4B71tzCmiVqwuAYURKeM68HVeipXKD9+Ajt2BAcx+5Ic+bXBgi4kCUSDMZ06 wKI6wDGWBQs5+/ZJj1XiI5lguB23MvYbsR23dUc761YO1um+BQL3MELmpBsIrMkN LtFIRZrietR6XMlM6bpSnCHXu5TEOLoHUM0eK68Kkt12DzNtILKB73r/lUjvYw+s qKcwWNkcDig9rdfSJXYsbYccQEVEjMCPreNIA1HLWTwP53x5pBDbBGCaHTkCqGSa M2u+oCYjGfcD9muLCd/2VvRmFrsgP1wPGAP8wS5/qQ== X-ME-Sender: X-ME-Proxy-Cause: gggruggvucftvghtrhhoucdtuddrgeeftddruddviedgudefkecutefuodetggdotefrod ftvfcurfhrohhfihhlvgemucfhrghsthforghilhdpggftfghnshhusghstghrihgsvgdp uffrtefokffrpgfnqfghnecuuegrihhlohhuthemuceftddtnecuogfuuhhsphgvtghtff homhgrihhnucdlgeelmdenucfjughrpefoggffhffvkfgjfhfutgesrgdtreerredtjeen ucfhrhhomhepfdftohgsucfnrghnuggvrhhsfdcuoehrohgssegsohhtthhlvggurdgtoh guvghsqeenucggtffrrghtthgvrhhnpedtiedtvddvvefhudffhfegleffteegffevkeeh keefleeuuddtieevkedvteejvdenucffohhmrghinhepfehvgehlrdhorhhgnecuvehluh hsthgvrhfuihiivgeptdenucfrrghrrghmpehmrghilhhfrhhomheprhhosgessghothht lhgvugdrtghouggvshdpnhgspghrtghpthhtohepuddpmhhouggvpehsmhhtphhouhhtpd hrtghpthhtohepihhnthgvrhhnrghlsheslhhishhtshdrphhhphdrnhgvth X-ME-Proxy: Feedback-ID: ifab94697:Fastmail Received: by mailuser.nyi.internal (Postfix, from userid 501) id 86050780065; Sun, 25 Aug 2024 15:36:59 -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 Date: Sun, 25 Aug 2024 21:36:39 +0200 To: internals@lists.php.net Message-ID: <1e2b1d22-0461-481d-8ad7-413a64dc4303@app.fastmail.com> In-Reply-To: References: <0c8ed5d6-5507-4c41-8d7f-05d14ba8aa4c@scriptfusion.com> <0cfd3a28-3cb0-4478-85fb-cf086d8e5c66@app.fastmail.com> Subject: Re: [PHP-DEV] [RFC] Default expression Content-Type: multipart/alternative; boundary=c7abd9500e0442b3bd39b4276f0b44bd From: rob@bottled.codes ("Rob Landers") --c7abd9500e0442b3bd39b4276f0b44bd Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable On Sun, Aug 25, 2024, at 20:46, Rowan Tommins [IMSoP] wrote: > On 25/08/2024 18:44, John Bafford wrote: >=20 >> Although I'm not sold on the idea of using default as part of an=20 >> expression, I would argue that a default function parameter value is=20 >> fair game to be read and manipulated by callers. If the default value=20 >> was intended to be private, it shouldn't be in the function declarati= on. >=20 >=20 > There's an easy argument against this interpretation: child classes ca= n freely change the default value for a parameter, as long as they do no= t make it mandatory. https://3v4l.org/SEsRm >=20 > That matches my intuition: that the public API, as a contract, states = that the parameter is optional; the specification of what happens when i= t is not provided is an implementation detail. >=20 > For comparison, consider constructor property promotion; the caller sh= ouldn't know or care whether a class is defined as: >=20 > public function __construct(private int $bar) {} >=20 > or: >=20 >=20 > private int $my_bar; > public function __construct(int $bar) { $this->my_bar =3D $bar; } >=20 > The syntax sits in the function signature because it's convenient, not= because it's part of the API. >=20 >=20 >=20 >> One important case where reading the default value could be important= is >> in interoperability with different library versions. For example, a=20 >> library might change a default parameter value between versions. If=20 >> you're using the library, and want to support both versions, you migh= t=20 >> both not want to set the value, and yet also care what the default va= lue >> is from the standpoint of knowing what to expect out of the function. >=20 >=20 > This seems contradictory to me. If you use the default, you're telling= the library that you don't care about that parameter, and trust it to p= rovide a default. >=20 > If you want to know what the library did with its arguments, reflectin= g the signature will never be enough anyway. For example, it's quite com= mon to write code like this: >=20 >=20 > function foo(?SomethingInterface $blah =3D null) { > if ( $blah =3D=3D=3D null ) { > $blah =3D self::_setup_default_blah(); > } > // ... > } >=20 > A caller can't tell by looking at the signature that a new version of = the library has changed what _setup_default_blah() returns. If the libra= ry doesn't provide an API to get $blah out later, then it's a private de= tail that the caller has no business inspecting. >=20 >=20 >=20 > Regards, >=20 > --=20 > Rowan Tommins > [IMSoP] I think you've hit an interesting point here, but probably not what you = intended. For example, let's consider this function: json_encode(mixed $value, int $flags =3D 0, int $depth =3D 512): string|= false Already, you have to look up the default value of depth or set it to som= ething that makes sense, as well as $flags. So you do this: json_encode($value, JSON_THROW_ON_ERROR, 512); You are doing this even when you omit the default. If you set it to a va= riable to spell it out: $default_flags =3D 0 | JSON_THROW_ON_ERROR; $default_depth =3D 512; // according to docs on DATE json_encode($value, $default_flags, $default_depth); Can now be rewritten: json_encode($value, $default_flags =3D default | JSON_THROW_ON_ERROR, $d= efault_depth =3D default); This isn't just reflection, this is saving me from having to look up the= docs/implementation and hardcode values. The implementation is free to = change them, and my code will "just work." Now, let's look at a more non-trivial case from some real-life use-cases= , in the form of a plausible story: public function __construct( private LoggerInterface|null $logger =3D null, private string|null $name =3D null, Level|null $level =3D null, ) This code constructs a new logger composed from an already existing logg= er. When constructing it, I may look up what the default values are and = decide if I want to override them or not. Otherwise, I will leave it as = null. A coworker and I got to talking about this interface. It kind of sucks, = and we don't like it. It's been around for ages, so we are worried about= changing it. Specifically, we are wondering if we should use SuperNullL= ogger as the default instead of null (which happens to just create a Nul= lLogger a few lines later). We are pretty sure making this change won't = cause any issues, but to be extra safe, we will do it only on a single c= ode path; further, we are 100% sure we are going to change this signatur= e, so we need to do it in a forward-compatible way. Thus, we will set it= to SuperNullLogger if-and-only-if the default value is null: default ?? new SuperNullLogger() Now, we can run this in production and see how well it performs. Inciden= tally, we discover that NullLogger implementation is superior and we can= now change the default: public function __construct( private LoggerInterface $logger =3D new NullLogger(), private string|null $name =3D null, Level|null $level =3D null, ) That one code path "magically" updates as soon as the library is updated= , without having to make further changes. Anything that is hardcoded "nu= ll" will break in tests/static analysis, making it easy to locate. Furth= er, we can test other types of NullLoggers just as easily: default instanceof NullLogger ? new BasicNullLogger() : default So, yes, I think in isolation the feature might look strange, and some o= perations might look nonsensical, but I believe there is a use case here= that was previously rather hard to do; or statically done via someone l= ooking up some documentation/code and doing a search-and-replace. =E2=80=94 Rob --c7abd9500e0442b3bd39b4276f0b44bd Content-Type: text/html; charset=utf-8 Content-Transfer-Encoding: quoted-printable
On Sun, Aug 25,= 2024, at 20:46, Rowan Tommins [IMSoP] wrote:
On 25/08/2= 024 18:44, John Bafford=0A wrote:

Although I'm not sold on the idea of using default as part of a=
n=20=0Aexpression, I would argue that a default function parameter value=
 is=20=0Afair game to be read and manipulated by callers. If the default=
 value=20=0Awas intended to be private, it shouldn't be in the function =
declaration.


There's an easy argumen= t against this interpretation: child=0A classes can freely change t= he default value for a parameter, as=0A long as they do not make it= mandatory. https://3v4l.org/SEsRm

That matches my intuition= : that the public API, as a contract,=0A states that the parameter = is optional; the specification of what=0A happens when it is not pr= ovided is an implementation detail.

For comparison, consider c= onstructor property promotion; the=0A caller shouldn't know or care= whether a class is defined as:

public function __construct(pr= ivate int $bar) {}

or:

private int $my_bar;=
public function __construct(int $bar) { $this->my_bar= =3D $bar; }

The syntax sits in the function signatur= e because it's=0A convenient, not because it's part of the API.
=


One important case where reading the de=
fault value could be important is=0A in interoperability with different =
library versions. For example, a=20=0Alibrary might change a default par=
ameter value between versions. If=20=0Ayou're using the library, and wan=
t to support both versions, you might=20=0Aboth not want to set the valu=
e, and yet also care what the default value=0A is from the standpoint of=
 knowing what to expect out of the function.

This seems contradictory to me. If you use the default, you're=0A= telling the library that you don't care about that parameter, and=0A= trust it to provide a default.

If you want to know what = the library did with its arguments,=0A reflecting the signature wil= l never be enough anyway. For example,=0A it's quite common to writ= e code like this:

function foo(?SomethingInterface $b= lah =3D null) {
    if ( $blah =3D=3D=3D n= ull ) {
        $blah = =3D self::_setup_default_blah();
    }
=
    // ...
}

<= p>A caller can't tell by looking at the signature that a new=0A ver= sion of the library has changed what _setup_default_blah()=0A retur= ns. If the library doesn't provide an API to get $blah out=0A later= , then it's a private detail that the caller has no business=0A ins= pecting.


Regards,

--=20=0ARowan Tommins=0A[IMSoP]
=

I think you've hit an interesting point here, but pr= obably not what you intended.

For exam= ple, let's consider this function:

json_encode(mixed $value, = int $f= lags =3D 0, int $depth =3D 512): str= ing|false

Already, you= have to look up the default value of depth or set it to something that = makes sense, as well as $flags. So you do this:

json_encode($value, JSON_THROW_ON_ERROR, 512);

=
You are doing this even when you omit the default. If you set it to= a variable to spell it out:

$default_flags= =3D 0 | JSON_THROW_ON_ERROR;
$default_depth =3D 512; // a= ccording to docs on DATE

json_encode($value= , $default_flags, $default_depth);

Can now = be rewritten:

json_encode($value, $default_= flags =3D default | JSON_THROW_ON_ERROR, $default_depth =3D default);

This isn't just reflection, this is saving me= from having to look up the docs/implementation and hardcode values. The= implementation is free to change them, and my code will "just work."

Now, let's look at a more non-trivial case fr= om some real-life use-cases, in the form of a plausible story:
=


public function __construct(
private LoggerInte= rface|null $logger =3D null,
private string|null $name =3D null,
Level|null $level =3D null,
)

Th= is code constructs a new logger composed from an already existing logger= . When constructing it, I may look up what the default values are and de= cide if I want to override them or not. Otherwise, I will leave it as nu= ll.

A coworker and I got to talking about t= his interface. It kind of sucks, and we don't like it. It's been around = for ages, so we are worried about changing it. Specifically, we are wond= ering if we should use SuperNullLogger as the default instead of null (w= hich happens to just create a NullLogger a few lines later). We are pret= ty sure making this change won't cause any issues, but to be extra safe,= we will do it only on a single code path; further, we are 100% sure we = are going to change this signature, so we need to do it in a forward-com= patible way. Thus, we will set it to SuperNullLogger if-and-only-if the = default value is null:

default ?? new Super= NullLogger()

Now, we can run this in produc= tion and see how well it performs. Incidentally, we discover that NullLo= gger implementation is superior and we can now change the default:

public function __const= ruct(
private LoggerInterface $logger =3D new NullLogger(),
private string|null $name =3D= null,
= Level|null $level =3D null,
)

That o= ne code path "magically" updates as soon as the library is updated, with= out having to make further changes. Anything that is hardcoded "null" wi= ll break in tests/static analysis, making it easy to locate. Further, we= can test other types of NullLoggers just as easily:

<= /div>
default instanceof NullLogger ? new BasicNullLogger() : defaul= t

So, yes, I think in isolation the feature= might look strange, and some operations might look nonsensical, but I b= elieve there is a use case here that was previously rather hard to do; o= r statically done via someone looking up some documentation/code and doi= ng a search-and-replace.

=E2=80=94 Rob
--c7abd9500e0442b3bd39b4276f0b44bd--