Newsgroups: php.internals
Path: news.php.net
Xref: news.php.net php.internals:125264
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 911781A00BD
	for <internals@lists.php.net>; Mon, 26 Aug 2024 10:43:57 +0000 (UTC)
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=php.net; s=mail;
	t=1724669149; bh=y/DC0iOY4BNpEd8Nd5SZc/eyj769fRQtoWUlxbsUJEg=;
	h=Subject:From:In-Reply-To:Date:Cc:References:To:From;
	b=DUdRaijXqqiFfg98uaZXzWEMMFhw8rNBmqUi/GjIO8Z9k59Jis4MzFv4AMnVmsYBm
	 B94S4dkPTX1FfY5baJH4wiwW0/7fTKUTRRoSfRkNBNHg2HhBVG9JCRC9g+UkeQBrlc
	 RZ/ckSKT9tQYsOpHOyqqu6ywTQNiCC/dSMTRe8b/V2Gv2WW3H+mrOilC7YCzC8fKC/
	 BBBu8nZ5+g2l2Immt78zYz4r/3UQNFqn5FvQ2MuFDavXhW+R47YOEKNlKOwC8KpoM5
	 c07Y4eMn/p+s0YtzceCRdhB8xXMgFD9G4nKxsG8I7L7jAuVQjhoLyzXqDRvy6dsXfD
	 Axm20eq/eoq6w==
Received: from php-smtp4.php.net (localhost [127.0.0.1])
	by php-smtp4.php.net (Postfix) with ESMTP id B322B1801E3
	for <internals@lists.php.net>; Mon, 26 Aug 2024 10:45:45 +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.8 required=5.0 tests=BAYES_50,DKIM_SIGNED,
	DKIM_VALID,DMARC_MISSING,RCVD_IN_DNSWL_NONE,RCVD_IN_MSPIKE_H2,
	SPF_HELO_NONE,SPF_NONE autolearn=no autolearn_force=no version=4.0.0
X-Spam-Virus: No
X-Envelope-From: <mike@newclarity.net>
Received: from mail-yw1-f172.google.com (mail-yw1-f172.google.com [209.85.128.172])
	(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 <internals@lists.php.net>; Mon, 26 Aug 2024 10:45:43 +0000 (UTC)
Received: by mail-yw1-f172.google.com with SMTP id 00721157ae682-6b59a67ba12so37888477b3.1
        for <internals@lists.php.net>; Mon, 26 Aug 2024 03:43:50 -0700 (PDT)
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
        d=newclarity-net.20230601.gappssmtp.com; s=20230601; t=1724669030; x=1725273830; darn=lists.php.net;
        h=to:references:message-id:content-transfer-encoding:cc:date
         :in-reply-to:from:subject:mime-version:from:to:cc:subject:date
         :message-id:reply-to;
        bh=RFLLeY+ubJNOMz083yas/4CxwHBrTxtECCmEwXVWds4=;
        b=RaN8X5qk41FS79Yf2a5yUYEn9aSiDUZWd6du5IOi5FdwDkmRRDLKx/X3tnLxpWNwRb
         bS5QJzD9khnvsYgrN8YO4BMX/SuS5qOXZ0QobTpykxjMQeuB7i7Za9hq1oq2vV5xlCJU
         8s6IYtmWXoo6VHuNyvTsCFodoma8K+ZLYJ83XYWdVhQc2sBoPhbRY3EYWhIyJJUfvF7Z
         6t8Mn1BhnevvQt9zI/GEBQUGeWnrMlaf05GNmLx1X4+Mah1+uc7UbKI553lVoEJvTfA1
         ZX2ZF3ISUeMRH6eWFfLC8Ed1Cm5h0xSbOuG7w32VQzd0Mx/Pp2IPJCcqCqbivdje7ZJW
         UZwQ==
X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
        d=1e100.net; s=20230601; t=1724669030; x=1725273830;
        h=to:references:message-id:content-transfer-encoding:cc:date
         :in-reply-to:from:subject:mime-version:x-gm-message-state:from:to:cc
         :subject:date:message-id:reply-to;
        bh=RFLLeY+ubJNOMz083yas/4CxwHBrTxtECCmEwXVWds4=;
        b=BH/rNlO0zmOr9WqwOzKm0D51/5HqWYFyhTDqhPTDUVehVXpdhBG+AS74w0u/1azKl1
         LxZGPAQKBDDEwrfnhEcs02FIv6pEb5kCRJZwULrQYk3FKfjf4pdBIhuo3Tt9+Yh61aHP
         zbeiI9MVoadshWwvBGYG1LP6AWyMkpM5HLl6vANbKTs9FS0M1442zHRKQNqGCQXWcPK8
         UNmbsx6V0ngJnrcpUsd8lpl0pR8SbP2EqWer+cgr/Dmg3L063RMAsy9TXGi0jwn03yhi
         kU8ne18gwi59ZrARNvynBjrZzWxLN3WPRaIR5CIhhVf3OVGrB0DD29G5zoJLk5xFeqY/
         3o5Q==
X-Gm-Message-State: AOJu0YzAygqnUMbRj7S7kxjq4vvgZJk9OkvWENl5/kXoHo4omeDrMZo1
	aZ7g6SHrgpI1IlsZv/EBNMZNsDbz52h4brOP1x4yb/SJ7c/tS0iuSdM/X7v8hG3GF0M13Qg7XiA
	eX4w=
X-Google-Smtp-Source: AGHT+IGKm2EP1iG8Yuu8hSIHfglTadnR6DU08ceZ3Fi7WLEBXcXg0eLLGo7f/kQjsntlfohqkGUxgQ==
X-Received: by 2002:a05:690c:6608:b0:64a:9832:48a with SMTP id 00721157ae682-6c61f3a5766mr136088827b3.0.1724669029768;
        Mon, 26 Aug 2024 03:43:49 -0700 (PDT)
Received: from smtpclient.apple (c-98-252-216-111.hsd1.ga.comcast.net. [98.252.216.111])
        by smtp.gmail.com with ESMTPSA id 00721157ae682-6c399cb5435sm14880237b3.2.2024.08.26.03.43.49
        (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128);
        Mon, 26 Aug 2024 03:43:49 -0700 (PDT)
Content-Type: text/plain;
	charset=utf-8
Precedence: bulk
list-help: <mailto:internals+help@lists.php.net
list-unsubscribe: <mailto:internals+unsubscribe@lists.php.net>
list-post: <mailto:internals@lists.php.net>
List-Id: internals.lists.php.net
x-ms-reactions: disallow
Mime-Version: 1.0 (Mac OS X Mail 16.0 \(3696.120.41.1.10\))
Subject: Re: [PHP-DEV] [RFC] Default expression
In-Reply-To: <0B974990-05FE-4C46-9C2B-3C243C7E8E81@getmailspring.com>
Date: Mon, 26 Aug 2024 06:43:48 -0400
Cc: "Rowan Tommins [IMSoP]" <imsop.php@rwec.co.uk>,
 Andreas Leathley <a.leathley@gmx.net>,
 Andreas Heigl <andreas@heigl.org>,
 John Coggeshall <john@coggeshall.org>
Content-Transfer-Encoding: quoted-printable
Message-ID: <07E4ABD5-B9CE-47FA-BD9E-E42A82CFC737@newclarity.net>
References: <99846F78-38F8-4549-B0A6-6310933AA78D@newclarity.net>
 <0B974990-05FE-4C46-9C2B-3C243C7E8E81@getmailspring.com>
To: "internals@lists.php.net" <internals@lists.php.net>
X-Mailer: Apple Mail (2.3696.120.41.1.10)
From: mike@newclarity.net (Mike Schinkel)

> On Aug 26, 2024, at 2:26 AM, John Coggeshall <john@coggeshall.org> =
wrote:
> The proposal in the RFC creates a new dependency and backward =
compatibility issue for API developers that currently does not exist. It =
is not just because it allows for non-sensical expressions, but that it =
allows perfectly sensical expressions that would create dependencies =
between libraries that I don't think are a worthwhile tradeoff. See my =
code example below.

If you reread my email you'll note I divided it into two objections and =
your reply seems not to recognize that.=20

Your comment defends against the objection I listed as #2 but you quoted =
the discussion of the objective listed as #1.=20

> (TL;DR; Down the thread a bit I put together a concrete example of why =
I'm opposed to this RFC)

I read through the entire thread and I did not see any examples you =
provided that provided any real-world concerns, unless I misunderstood. =
But since you provided a better example let us just ignore my comments =
on those.

> Consider this metaphor -- If I have a object with private properties, =
PHP doesn't allow me to reach into that object and extract those values =
without a lot of work (e.g. Reflection) by design. Right now, there are =
lots of libraries out there defining default values and right now today =
they are in the same sense "private" to the function/method they are =
defined for. This PR would change the visibility of those default =
values, pulling them higher up into the call stack where IMO they don't =
belong -- essentially making them a brand new dependency API devs need =
to worry about.

I acknowledged that. However, I was not convinced that the default value =
concern is an actual concern vs. a theoretical concern.  So that is why =
I asked for specific examples where it would cause a real problem vs. =
just a theoretical concern.=20

> Sure if I have `foo(int $index=3D1)`, a developer calls with =
`foo(default*3)`, and then the author of foo changes the signature to =
`foo(int $index=3D0)` that might cause problems, but what is a real =
world scenario where a developer would actually do that, the author then =
change it, and then is causes a non-trivial problem?
>=20
> How is that not a real-world contrived scenario?

Because there was no use-case described for:

1. The purpose of the function,
2. Why someone would multiply the default times three, nor
3. Why the API developer would break BC and change the default for the =
use-case.=20

That kind of information is what I was looking for.

Besides, are you really calling a function named "foo()" a "real-world =
scenario?"  ;-)

Anyway, I am going to skip to your example because otherwise I would =
just be repeating my call for concrete examples in response to your =
earlier comments in that reply.

> Let me propose this example and see if you still hold firm to your =
option that the following expression would not be valid and that it =
still would not be a good idea for the language:
> class MyDependency {...}
> function doSomething(MyDependency $dep=3D new MyDependency) {...}
> doSomething((default)->WithLogger(new Logger));
>=20
> Let's make that a little more complicated so you'll see the problem -- =
Consider this rather lengthy example building off the concept of your =
example:
>=20
> <snip>
>=20
> (new A)->withLogger((default)->log('B'));

Well, this is not the example I asked you to comment on. Yes, you are =
using `WithLogger()` but you are not using it in the same context I =
asked about.=20

Nonetheless, I will consider your example. It would be nice if you would =
revisit mine.

> This would be valid under this RFC, right?

No, because the method log() is implicitly void.  WithLogger() expects a =
Logger, not a null. But then I expect your example just had some logic =
errors, no?

BTW, in your example I am not sure I understand what `log()` does. I =
assume it is logging a value?

> But now as the author of class A I later want to change the default of =
my withLogger  method. Instead of just passing in new DatabaseLogger, I =
now want to change my API default to just a LoggerType::DB enum for =
reasons (What the reasons aren't relevant).
>=20
> Today I don't have to think too hard about that change from an API BC =
perspective because the consumer has either passed in a LoggerInterface  =
or a LoggerType  -- or left it empty and used it's default value. I can =
just change the default to LoggerType::DB  and be on my way. The =
downstream developer will never know or care that I did it because if =
they wanted to call log()  they had to first create their own instance =
of LoggerInterface  and have a reference to that object in their local =
context like so:
>=20
> $logger =3D LoggerType::DB->getLogger();
> (new A)->withLogger($logger);
> $logger->log('B');
>=20
> With this RFC, now I can't  change this API call without introducing a =
BC break in my library because I have no idea at this point if some =
downstream caller decided to use my default value directly or not.

Okay, this I can work with. Thank you.

=46rom the example you gave it appears that we can have a concrete =
problem when:=20

1. There is a parameter with a default value,=20
2. That parameter is type-hinted,=20
3. The hinted type is declared as a union type,
4. An earlier version of the library initialized the default with a =
value having one of the union types,=20
5. End-user developers used the library and then use `default` as an =
expression of that type, and finally
6. The library developer changed the initialization of the `default` to =
a different type from the union.

Did I correctly identify the problematic use-case?

Let us assume I did. Clearly this would be a BC break and the type you =
are concerned with.

Ok, so for argument sake, what if they revise the RFC to only allow =
`default` to be used in an expression when the parameter is not =
type-hinted with a union?  Would that address your concern? Or are there =
other use-cases that are problematic that do not hinge on the parameter =
being type-hinted as a union type?=20

Note they could also propose a default value be able to be set for each =
type in a union, with the first one being the default default if all =
else is equal. That might not be exactly to your liking, but it seems =
like it could address your stated problematic use-case.

BTW, you could also guard against that problem you claim you cannot =
guard against by ensuring your parameters with defaults are single types =
with no extraneous methods. In your example if you instead used a =
LoggerGetter interface with a single method GetLogger() you would =
sidestep the problem you are concerned with completely, and you might =
end up with a better API, too.:

public function withLogger(LoggerGetterInterface $a =3D new =
DatabaseLoggerGetter): static
{
    $this->log =3D $a->getLogger();
    return $this;     =20
}


> You can argue if this is a good API design or not, but it was only =
written to provide a real example of how pulling the default value =
higher up the call chain and allowing it to be used in expressions is =
problematic for library authors all to save a couple of lines of code on =
the consumer side.

FWIW, I do not see the RFCs benefit as "saving lines of code" so much as =
instead "improving clarity of code."

> I'm honestly not sure what you're asking here. PHP currently doesn't =
allow you access to the "default value" of a function you are calling =
(maybe Reflection? I don't know offhand).

You wrote (paraphrasing) "Developers shouldn't be allowed to access =
defaults because they API did not explicitly allow them to."  Well then, =
it is simple completion of the binary logic to ask "How then do they =
explicitly give permission?"

IOW, if the argument is it can't be accessed because of lack of explicit =
permission it seems maybe what is needed is an RFC to decide:

1. Should defaults just be assumed to be part of the public API, and=20
2. If no, then how can defaults be explicitly made public? =20

Maybe this?

function withLogger(Logger $a =3D public new Logger): Logger {...}

> So yes, I am pointing out a problem but not providing a solution =
because I don't currently agree a solution is even needed.

Fair enough.

OTOH, depending on the number who support this (I have no idea the =
number) you might be in a position that you'll get what you don't want =
unless you can propose a solution that addresses your concerns  while =
also meeting their needs to. Just something to consider.

> On Aug 26, 2024, at 3:28 AM, Rowan Tommins [IMSoP] =
<imsop.php@rwec.co.uk> wrote:
> I was responding to someone justifying anything and everything the =
proposal allows, because Reflection already allows it. If the feature =
was "first class syntax to access private methods of a class", I don't =
think it would be controversial to challenge it. Saying "Reflection can =
already do it" would be a poor defence, because part of Reflection's job =
is to break the normal rules of the language.

But saying that we can be certain default values are private and we want =
to keep them that way is provably false by the existence of Reflection. =20=


Yes, I get that it is more likely people would use this `default` =
keyword in this way that they would use Reflection but not permitting =
default as expression that does not provide any more _guarantee_ they =
won't then they already have.

> The overriding rule, in my head, is that the caller shouldn't need to =
know, and shouldn't be able to find out, what a particular =
implementation has chosen as the default. So the particularly =
problematic operations are things like this:=20
>=20
> foo($whatWasTheDefault=3Ddefault)=20
> foo(logAndReturn(default))
> foo(doSomethingUnrelated(default) && false?: default)=20
>=20
> I can see people inventing use cases for these as soon as they're =
available, but by doing so they will be completely changing the meaning =
of default parameters, which currently mean "I trust the implementation =
to substitute something valid here, and have no interest in what it is".

Yes, that is the way it has (mostly) been, but justifying your argument =
that (you believe) the caller shouldn't know the default's value simply =
because it is currently not (generally) accessible is a poor defense for =
why it should not be accessible. Or as they say; live by the sword, die =
by the sword.

It is unrealistic to say that a developer shouldn't know anything about =
what a default value is because =E2=80=94 without knowing what what the =
default actually is =E2=80=94 how does a developer know the default =
value is the proper value for their use-case?

Given that, I think there is an argument to be made that default values =
*should* be made public and accessible and that developers who write =
functions should plan accordingly.  (I am not 100% sure I buy the =
argument I just made yet, but I am also not sure that I do not.). Making =
it part of the signature would make even more behavior explicit, and in =
most cases that is a good thing.

> Note that under inheritance, the default value may even change type:
>=20
> class A { public function foo(int $bar=3D42) { ... } }
> class B extends A { public function foo(int|string $bar=3D'hello') { =
... } }

*Arguably*, that could violate LSP.  But even if not, it could violate =
the principle of least astonishment.

OTOH, you are still presenting abstract hypotheticals with no evidence =
that there exists use-case where developers would actually do what you =
are worried about them doing. How about providing a concrete example =
use-case as John Coggeshall did? Note we discovered a specific =
problematic use-case that might be avoided or worked-around by our =
collaborating in that way.

> You ask how a library can provide access to that default, and the =
answer is generally pretty trivial: define a public constant, and refer =
to it in the parameter definition.=20

A global? Really?

Yes, you can namespace them, but that doesn't tie them to the function =
or the class. Too bad.=20

> The only exception I think is "new in initializer", where you would =
have to provide a function/method instead of a constant, and couldn't =
currently reuse it in the actual signature.

Besides that Mrs Lincoln, how was the play?

Oops, sorry. I forgot you were not an American. ;-)

> Aside: one of those examples brings up an interesting question: is the =
value pulled out by "default" calculated only once, or each time it's =
mentioned? In other words, would this create 3 pointers to the same =
object, or 3 different objects?=20
>=20
> foo(array|Something $x=3Dnew Something);
> foo([default, default, default]);

That is a question for the RFC author.

> On Aug 26, 2024, at 5:03 AM, Andreas Leathley <a.leathley@gmx.net> =
wrote:
> When I have the GzipCompression class, I would know there is a default
> value for $level, but when using the interface there might or might =
not
> be a default value, depending on the implementation. As far as I read
> the RFC, using "default" when there is no default would lead to a
> runtime exception, but there is no way of finding out if there is a
> default if you do not already know. Being able to test that could be
> useful, although I am not sure about the syntax for that.=20

Great catch!

> On Aug 26, 2024, at 6:32 AM, Andreas Heigl <andreas@heigl.org> wrote:
> I think I am missing something here. =46rom my understanding we are =
*either* coding against the interface and then it should not be possible =
to use `default` at all as no default is set in the interface. So the =
fatal error is totally valid for me.

Maybe default value for method parameters in interfaces is something the =
RFC should consider enabling?=20

-Mike=