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(
=
span>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:
=
div>
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--