This is a little tangent from the Enums RFC, but I want to flag it because it it's the sort of in-passing decision that could have far-reaching implications, so shouldn't be done implicitly.
At the moment, the Enum RFC for scalar enums includes two methods:
public function has(string $name): bool
public function from(string $name): self throws ValueError
Nikita raised the point that has() seems kinda pointless as it would only ever be used to wrap a possibly-unsafe from() call. (In most other cases, calling cases() would be more useful or at least equally useful.) One of the proposed alternatives was the following:
public function from(string $name): self throws ValueError
public function tryFrom(string $name): ?self
The "a method that begins with try is nullable, so watch out" idiom is present in C# and Rust, but to my knowledge has never existed in PHP. That doesn't make it bad; it actually combines quite well with the null coalesce operator to allow for default values, making a valueOrDefault() method unnecessary.
$order = SortOrder::tryFrom($input) ?? SortOrder::Asc;
I'm not opposed to following that pattern here; it would allow both a "hard fail" and "soft fail" variant of the operation. However, as noted that idiom has never appeared in PHP before that I'm aware. If we adopt it here, that means it will either start to spread and become a more common PHP idiom over time, OR it won't spread and Enums will have this weird one-off naming convention for a nullable method. The former would, of course, be considerably preferable to the latter.
So, explicit decision time: Are we OK with introducing that idiom, and then following it consistently in the future in similar situations? (viz, tryX() means nullable, and no-try means not nullable.) I'm good with it if the consensus is good with it, but I want to see what the consensus is first.
--
Larry Garfield
larry@garfieldtech.com
The "a method that begins with try is nullable, so watch out" idiom is present in C# and Rust, but to my knowledge has never existed in PHP. That doesn't make it bad; it actually combines quite well with the null coalesce operator to allow for default values, making a valueOrDefault() method unnecessary.
I get the advantages of returning null, in particular WRT the null
coalescing operator.
However, when I see 'try' I instictively think 'exceptions' i.e.
try/catch when the reality is it's the opposite and it would be the
non-try functions which throw.
But it makes sense for those to throw.
However if you will permit me a tangent...
There is an alternative, of sorts. Something I tried investigating when
I first started looking into PHP-SRC, but lack the skill and knowledge
to implement myself.
A shorthand try / catch of something like the form:
<tryname>(<expr, ExceptionType => ResultExpr, ...)
At which point tryFrom becomes:
$order = attempt(SortOrder::tryFrom($input), ValueError => SortOrder::Asc);
Which would be the equivilent of:
function attempt(callable $try, array $map): mixed {
try {
return $try();
}
catch ($e) {
foreach ($map as $class => $expr) {
if (is_subclass_of($e, $class, true)) {
return $expr();
}
}
throw $e;
}
}
Or just allow a mixed value to be given without a mapping for catching
Throwable.
If added as a language construct, except each expr and $try itself would
be parsed closure at language construct level to avoid needing to fn()
them all e.g.
$foo = attempt(fn() => SortOrder::tryFrom($input), [ ValueError => fn()
=> null ]);
Just a thought, perhaps a cleaner solution to a wider problem. A try /
catch combined with a match.
It avoids the need for two methods, just provide the one that throws and
use shorthand to assign a null if it throws.
Mark Randall
making a valueOrDefault() method unnecessary.
$order = SortOrder::tryFrom($input) ?? SortOrder::Asc;
tbh, I would prefer a valueOrDefault() method, partly because it's
what I'm used to in my own code* but also I think it would be easier
to explain to a junior, or for a junior to remember.
cheers
Dan
Ack
interface VarMap
{
/*
* @param string $name
* @return mixed
* @throws \VarMap\Exception\VariableMissingException
*/
public function get(string $name);
/**
* @param string $name
* @return bool
*/
public function has(string $name) : bool;
/**
* @param string $name
* @param mixed $defaultValue
* @return mixed
*/
public function getWithDefault(string $name, $defaultValue);
}
Le 10/01/2021 à 22:27, Larry Garfield a écrit :
This is a little tangent from the Enums RFC, but I want to flag it because it it's the sort of in-passing decision that could have far-reaching implications, so shouldn't be done implicitly.
At the moment, the Enum RFC for scalar enums includes two methods:
public function has(string $name): bool
public function from(string $name): self throws ValueError
Nikita raised the point that has() seems kinda pointless as it would only ever be used to wrap a possibly-unsafe from() call. (In most other cases, calling cases() would be more useful or at least equally useful.) One of the proposed alternatives was the following:
public function from(string $name): self throws ValueError
public function tryFrom(string $name): ?self
The "a method that begins with try is nullable, so watch out" idiom is present in C# and Rust, but to my knowledge has never existed in PHP. That doesn't make it bad; it actually combines quite well with the null coalesce operator to allow for default values, making a valueOrDefault() method unnecessary.
$order = SortOrder::tryFrom($input) ?? SortOrder::Asc;
I'm not opposed to following that pattern here; it would allow both a "hard fail" and "soft fail" variant of the operation. However, as noted that idiom has never appeared in PHP before that I'm aware. If we adopt it here, that means it will either start to spread and become a more common PHP idiom over time, OR it won't spread and Enums will have this weird one-off naming convention for a nullable method. The former would, of course, be considerably preferable to the latter.
So, explicit decision time: Are we OK with introducing that idiom, and then following it consistently in the future in similar situations? (viz, tryX() means nullable, and no-try means not nullable.) I'm good with it if the consensus is good with it, but I want to see what the consensus is first.
I'm OK with the tryBar(): ?Foo pattern, but my preference goes over just
bar(): ?Foo, as long as API are explicitly typed and correctly documented.
I will not fight against or for any or another solution, as long as the
convention remains the same for everything.
Larry, I think your proposal is missing something: if a convention
emerge, it should be documented as such, and be considered as a law for
subsequent language / API addition, the real question, IMHO, is : Are
people OK with writing naming and design conventions by law in PHP core ?
For that matters, I'm OK, this would avoid many bikesheds in the future:
this is the law, and the law says, name it tryX(), no more useless
votes, no more flavor-oriented flame wars on naming :)
--
Pierre
Le 10/01/2021 à 22:27, Larry Garfield a écrit :
This is a little tangent from the Enums RFC, but I want to flag it because it it's the sort of in-passing decision that could have far-reaching implications, so shouldn't be done implicitly.
At the moment, the Enum RFC for scalar enums includes two methods:
public function has(string $name): bool
public function from(string $name): self throws ValueError
Nikita raised the point that has() seems kinda pointless as it would only ever be used to wrap a possibly-unsafe from() call. (In most other cases, calling cases() would be more useful or at least equally useful.) One of the proposed alternatives was the following:
public function from(string $name): self throws ValueError
public function tryFrom(string $name): ?self
The "a method that begins with try is nullable, so watch out" idiom is present in C# and Rust, but to my knowledge has never existed in PHP. That doesn't make it bad; it actually combines quite well with the null coalesce operator to allow for default values, making a valueOrDefault() method unnecessary.
$order = SortOrder::tryFrom($input) ?? SortOrder::Asc;
I'm not opposed to following that pattern here; it would allow both a "hard fail" and "soft fail" variant of the operation. However, as noted that idiom has never appeared in PHP before that I'm aware. If we adopt it here, that means it will either start to spread and become a more common PHP idiom over time, OR it won't spread and Enums will have this weird one-off naming convention for a nullable method. The former would, of course, be considerably preferable to the latter.
So, explicit decision time: Are we OK with introducing that idiom, and then following it consistently in the future in similar situations? (viz, tryX() means nullable, and no-try means not nullable.) I'm good with it if the consensus is good with it, but I want to see what the consensus is first.
I'm OK with the tryBar(): ?Foo pattern, but my preference goes over just
bar(): ?Foo, as long as API are explicitly typed and correctly documented.
Well, the concern is that we also want to have a from(): Foo throws ValueError version, but both cannot be called from().
The possible patterns are:
Interface:
Suit::from(string): ?self
Usage:
$s = Suit::from($var) ?? Suit::SomeDefault;
Interface:
Suit::from(string): self throws ValueError
Suit::has(string): bool
Usage:
if (Suit::has($var)) {
$s = Suit::from($var);
}
Technically, one could create the second from the first more easily than vice versa, but both could implement each other.
try {
$s = Suit::from($var);
}
catch (ValueError) {
$s = Suit::SomeDefault;
}
// or
$s= Suit::from($var) ?? throw new ValueError();
While the nullable return ?? is very appealing, we know from experience that most people don't think to check the null case, leading to unhelpful "method called on non object" errors 6 months later when someone passes in bad data. An Optional/Maybe type is the ideal solution as that forces people to account for that possibility, but we don't have those yet. (Enums are a step toward being able to build them cleanly.) A thrown exception is next, although nothing forces people to include the try-catch so they could forget that, too. At least static analyzers tend to warn you about it. Nullable is least likely to get handled in practice, even if the resulting code may, in all honestly, look the cleanest if you're OK with ??.
Offering both exception and nullable variants is a compromise option, but that requires disambiguating the names somehow. TryX -> nullable is the least-bad suggestion so far, IMO.
I'm still not sure of how we want to proceed. :-/
I will not fight against or for any or another solution, as long as the
convention remains the same for everything.Larry, I think your proposal is missing something: if a convention
emerge, it should be documented as such, and be considered as a law for
subsequent language / API addition, the real question, IMHO, is : Are
people OK with writing naming and design conventions by law in PHP core ?For that matters, I'm OK, this would avoid many bikesheds in the future:
this is the law, and the law says, name it tryX(), no more useless
votes, no more flavor-oriented flame wars on naming :)
While such a formal convention would be nice, that's a separate fight, and the last time I picked that fight (https://wiki.php.net/rfc/php_namespace_policy) we lost. :-) For now I just want to keep enums de facto consistent.
--Larry Garfield