Implement an interface and magic method to allow objects to represent false (or empty) while still be valid instances of the custom object (type).
https://wiki.php.net/rfc/objects-can-be-falsifiable https://wiki.php.net/rfc/objects-can-be-falsifiable
If you saw the latest from this morning, not much has changed except hopefully improved formatting and now being the official mix of things.
If this is your first time, the cues are taken from:
- __toString()
- Stringable
- and __toArray() (not accepted or approved yet)
Thank you for all the feedback and patience so far, appreciate it!
Cheers,
Josh
Implement an interface and magic method to allow objects to represent
false (or empty) while still be valid instances of the custom object (type).https://wiki.php.net/rfc/objects-can-be-falsifiable <
https://wiki.php.net/rfc/objects-can-be-falsifiable>If you saw the latest from this morning, not much has changed except
hopefully improved formatting and now being the official mix of things.If this is your first time, the cues are taken from:
- __toString()
- Stringable
- and __toArray() (not accepted or approved yet)
Thank you for all the feedback and patience so far, appreciate it!
Cheers,
Josh
I'm not sure I love this (mostly from a static analysis standpoint).
It means that there would exist some $foo
with the property that $foo && !$foo
evaluates to true.
That seems to be a bad place for a language to go, and it would increase
the false-negative rate of static analysis tools.
Implement an interface and magic method to allow objects to represent
false (or empty) while still be valid instances of the custom object (type).https://wiki.php.net/rfc/objects-can-be-falsifiable <
https://wiki.php.net/rfc/objects-can-be-falsifiable>If you saw the latest from this morning, not much has changed except
hopefully improved formatting and now being the official mix of things.If this is your first time, the cues are taken from:
- __toString()
- Stringable
- and __toArray() (not accepted or approved yet)
Thank you for all the feedback and patience so far, appreciate it!
Cheers,
JoshI'm not sure I love this (mostly from a static analysis standpoint).
It means that there would exist some
$foo
with the property that$foo && !$foo
evaluates to true.That seems to be a bad place for a language to go, and it would increase
the false-negative rate of static analysis tools.
I don't see how that would happen. What non-pathological case would allow for $foo && !$foo == true?
(I know you could do something stupid like return random_int()
from __toBool(), but I'm ignoring that as "you're dumb so of course your code is dumb.)
From the RFC, I don't quite get this line:
"Further, the union type bool|Falsifiable will be dynamically added at run time to any object implementing the reserved __toBool() method, which would allow stdClass() objects to be defined as Falsifiable also. "
What's that about?
"(Including __construct() replacing old style constructors in PHP 7.)" - __construct replaced old-style constructors in PHP 5. :-)
I think the truth tables have a formatting error; check the last line that goes wider than the rest. Also, watch out for "smart quotes" in the string parts. Also, YAY summary tables!
--Larry Garfield
I don't see how that would happen. What non-pathological case would allow
for $foo && !$foo == true?
I suppose something a little less pathological would be
$collection && $collection->pop() && !$collection
which is still pretty icky (to me at least).
The other implication is that this is now possibly not a bug:
if (!$foo) {
$foo->bar();
}
which, again, would ever-so-slightly reduce the effectiveness of static
analysis.
https://wiki.php.net/rfc/objects-can-be-falsifiable https://wiki.php.net/rfc/objects-can-be-falsifiable
Updates:
- Open issues (outstanding questions/concerns) updated
- Future scope
- References now have links to original copies on GitHub (still updating 0002) - will be removed if accepted and implemented.
- No violent opposition, but concerns - will be looking at how to implement (or set up implementing).
All:
Trying to let conversation flow w/o me; sticking to once a day batch updates. Let me know if you’d prefer something else - still learning the Roman-way. :)
To Larry’s user example.
With Falsifiable the User instance becomes more self-aware. __toBool(), which might be more for the object itself, other objects, or PHP itself through a universal, reserved API entry point (like other magic methods).
Trying to make PHP fail gracefully more by adding to the ability of not returning null (or interacting with null - nullsafe, optionals, and so on).
(Maybe that should be a table as well??)
print null; // always succeeds despite being nothing - not a fatal error
(bool) null; // always succeeds - always false
(integer|float) null; // always succeeds - always 0
(array) null; // always succeeds - always []
(string) null; // always succeeds - always “"
null->someMethod(); // always fails - crashes app
I was in the conversations leading up to null being able to handle this much interaction, part makes me go back to something talked about re new developers (paraphrased): It's frustrating to learn software development when it takes a lot to set up and with every turn you take your app crashes - people give up; so, PHP prefers failing gracefully over hard crashes.
The only interaction on null that seems to fail outright (from personal experience) is trying to call a method because it’s not an object. (It doesn’t even fail the dread array-to-string conversion. lol)
I really appreciate any effort that can make PHP a more powerful language. However, for this item, I believe it will generate much greater cognitive complexity for new users (and I hate to assume that this is a problem, but we have to remember our roots).
David: Completely agree on respecting new users and devs (see opening bit).
I’m thinking right now, for me, it’s more about always being able to message the type you expect, even if false (in the abstract), while also letting PHP to interact automatically with it in the ways we have also become accustomed.
(bool) cast working together with __toBool() is a good one, but I think that it is being discussed in another topic.
Was it an RFC or email??
If email and within the last week or so, it might be this same conversation.
Took a moment to evolve to this kinda hybrid approach. (In short, back to the original implementation, with a pretty different rationale.)
The other implication is that this is now possibly not a bug:
if (!$foo) {
$foo->bar();
}
Matthew: I can see that feeling like a bug, for me it would be a feature (can’t believe I just said that).
Objects that return an abstracted (high-level) false, can tell me why (without blowing things up via fatal errors or added syntax and time of try-catch-throw or various permutations of optionals, which is possibly frustrating for new devs - possibly specific to the self-taught, it was for me back in the day - still don’t have the muscle memory for try-catch-throw and implementing errors).
I can’t ask null any questions - it exists only in the mind (so to speak), but I can send messages to an instance that is abstractly false. (And remove all the null-checks and optionals from (at least) my code that uses it or wraps some package that doesn’t.
My contract with a function call becomes: You will always get something of this type - period. Whether it’s the one you specifically wanted, that would be debatable, but it will be that type.
"Further, the union type bool|Falsifiable will be dynamically added at run time to any object implementing the reserved __toBool() method, which would allow stdClass() objects to be defined as Falsifiable also. "
What's that about?
Larry: Totally lifted from the Stringable RFC - lol: https://wiki.php.net/rfc/stringable https://wiki.php.net/rfc/stringable - string|Stringable
And I don’t know of a way to add an interface to new stdClass()
- but thought this might be a valid use case:
$object = new \stdClass();
$object->__toBool = function() {
return false|true;
};
if (! $object) {
// made it
}
"(Including __construct() replacing old style constructors in PHP 7.)" - __construct replaced old-style constructors in PHP 5. :-)
Here’s where I got that from: https://www.php.net/manual/en/migration70.deprecated.php https://www.php.net/manual/en/migration70.deprecated.php
Might have been tired when reading it. Are old style constructors still available at all in PHP 7 (just deprecated)?? Looking for when old constructors were removed - not just deprecated.
I think the truth tables have a formatting error; check the last line that goes wider than the rest. Also, watch out for "smart quotes" in the string parts. Also, YAY summary tables!
Thanks for the table suggestion!
Not sure I’m getting the same issue. If you mean the line that starts with “row 1” - this is a different paragraph, not part of the table - its width is styled wider than the table. Margin-bottom is 1.5rem on paragraph - 0 on the table; so, they butt against one another.
Is there a way to add an empty line (don’t recall seeing it in the wiki syntax)?? Or, are you noticing something else??
Let's not confuse Java nullable issues with PHP nullability, which is quite
healthy :-)
Marco: To be fair, I don’t know anything about Java’s nullable issues - only PHP's. :)
BTW, I'm not saying you should pursue both RFCs or anything, that's
just a reference in case you are looking around how others did it.
Marcio: Thanks for the example! I’ve never used a language that has this as a possibility by this type of implementation - Swift is capable by way of overloading operators (at least last I used it). Definitely would like more examples of languages that do something like this. (Links to those conversations to glean high-level concerns and possible problems might be helpful as well.)
Definitely not looking to pursue both at this time as emptiness or nonzero is harder for me to justify to myself, much less the larger community - not that it shouldn’t happen.
Cheers,
Johs
https://wiki.php.net/rfc/objects-can-be-falsifiable https://wiki.php.net/rfc/objects-can-be-falsifiable
Updates:
- Open issues (outstanding questions/concerns) updated
- Future scope
- References now have links to original copies on GitHub (still updating 0002) - will be removed if accepted and implemented.
- No violent opposition, but concerns - will be looking at how to implement (or set up implementing).
All:
Trying to let conversation flow w/o me; sticking to once a day batch updates. Let me know if you’d prefer something else - still learning the Roman-way. :)
To Larry’s user example.
With Falsifiable the User instance becomes more self-aware. __toBool(), which might be more for the object itself, other objects, or PHP itself through a universal, reserved API entry point (like other magic methods).
Trying to make PHP fail gracefully more by adding to the ability of not returning null (or interacting with null - nullsafe, optionals, and so on).
(Maybe that should be a table as well??)
print null; // always succeeds despite being nothing - not a fatal error
(bool) null; // always succeeds - always false
(integer|float) null; // always succeeds - always 0
(array) null; // always succeeds - always []
(string) null; // always succeeds - always “"
null->someMethod(); // always fails - crashes app
I was in the conversations leading up to null being able to handle this much interaction, part makes me go back to something talked about re new developers (paraphrased): It's frustrating to learn software development when it takes a lot to set up and with every turn you take your app crashes - people give up; so, PHP prefers failing gracefully over hard crashes.
The only interaction on null that seems to fail outright (from personal experience) is trying to call a method because it’s not an object. (It doesn’t even fail the dread array-to-string conversion. lol)
I really appreciate any effort that can make PHP a more powerful language. However, for this item, I believe it will generate much greater cognitive complexity for new users (and I hate to assume that this is a problem, but we have to remember our roots).
David: Completely agree on respecting new users and devs (see opening bit).
I’m thinking right now, for me, it’s more about always being able to message the type you expect, even if false (in the abstract), while also letting PHP to interact automatically with it in the ways we have also become accustomed.
(bool) cast working together with __toBool() is a good one, but I think that it is being discussed in another topic.
Was it an RFC or email??
If email and within the last week or so, it might be this same conversation.
Took a moment to evolve to this kinda hybrid approach. (In short, back to the original implementation, with a pretty different rationale.)
The other implication is that this is now possibly not a bug:
if (!$foo) {
$foo->bar();
}Matthew: I can see that feeling like a bug, for me it would be a feature (can’t believe I just said that).
Objects that return an abstracted (high-level) false, can tell me why (without blowing things up via fatal errors or added syntax and time of try-catch-throw or various permutations of optionals, which is possibly frustrating for new devs - possibly specific to the self-taught, it was for me back in the day - still don’t have the muscle memory for try-catch-throw and implementing errors).
I can’t ask null any questions - it exists only in the mind (so to speak), but I can send messages to an instance that is abstractly false. (And remove all the null-checks and optionals from (at least) my code that uses it or wraps some package that doesn’t.
My contract with a function call becomes: You will always get something of this type - period. Whether it’s the one you specifically wanted, that would be debatable, but it will be that type.
"Further, the union type bool|Falsifiable will be dynamically added at run time to any object implementing the reserved __toBool() method, which would allow stdClass() objects to be defined as Falsifiable also. "
What's that about?
Larry: Totally lifted from the Stringable RFC - lol: https://wiki.php.net/rfc/stringable https://wiki.php.net/rfc/stringable - string|Stringable
And I don’t know of a way to add an interface to
new stdClass()
- but thought this might be a valid use case:$object = new \stdClass();
$object->__toBool = function() {
return false|true;
};if (! $object) {
// made it
}"(Including __construct() replacing old style constructors in PHP 7.)" - __construct replaced old-style constructors in PHP 5. :-)
Here’s where I got that from: https://www.php.net/manual/en/migration70.deprecated.php https://www.php.net/manual/en/migration70.deprecated.php
Might have been tired when reading it. Are old style constructors still available at all in PHP 7 (just deprecated)?? Looking for when old constructors were removed - not just deprecated.
I think the truth tables have a formatting error; check the last line that goes wider than the rest. Also, watch out for "smart quotes" in the string parts. Also, YAY summary tables!
Thanks for the table suggestion!
Not sure I’m getting the same issue. If you mean the line that starts with “row 1” - this is a different paragraph, not part of the table - its width is styled wider than the table. Margin-bottom is 1.5rem on paragraph - 0 on the table; so, they butt against one another.
Is there a way to add an empty line (don’t recall seeing it in the wiki syntax)?? Or, are you noticing something else??
Let's not confuse Java nullable issues with PHP nullability, which is quite
healthy :-)Marco: To be fair, I don’t know anything about Java’s nullable issues - only PHP's. :)
BTW, I'm not saying you should pursue both RFCs or anything, that's
just a reference in case you are looking around how others did it.Marcio: Thanks for the example! I’ve never used a language that has this as a possibility by this type of implementation - Swift is capable by way of overloading operators (at least last I used it). Definitely would like more examples of languages that do something like this. (Links to those conversations to glean high-level concerns and possible problems might be helpful as well.)
Definitely not looking to pursue both at this time as emptiness or nonzero is harder for me to justify to myself, much less the larger community - not that it shouldn’t happen.
Cheers,
Johs
Apologies: I was NOT in the conversations leading up to null
I really appreciate any effort that can make PHP a more powerful language.
However, for this item, I believe it will generate much greater cognitive
complexity for new users (and I hate to assume that this is a problem, but
we have to remember our roots).
Why, for now a variable $x will be truth, since it is an instance of an
object, now it could be false, because it implements __toBool() and
Falsiable interface in a "magic" way, making everything very confusing if
the user is not aware of each class that he is instantiating (or even
receiving from other places without much condition to know precisely what
it is).
For this type of situation, I still prefer the introduction of clearer
methods that suggest the validation of your own properties, in order to
make it much more flexible than simply returning a single "general" boolean
by-instance. For example: isValid().
if ($entity?->isAdministrator()) { /** Entity is an administrator. / }
else if ($entity?->isModerator()) { /* Entity is a moderator. / }
else if ($entity) { /* Entity should be an user or similar. / }
else { /* Entity is not defined. */ }
On the other hand, the implicity (bool) cast working together with
__toBool() is a good one, but I think that it is being discussed in another
topic.
Atenciosamente,
David Rodrigues
Em ter., 14 de jul. de 2020 às 20:59, Josh Bruce josh@joshbruce.dev
escreveu:
Implement an interface and magic method to allow objects to represent
false (or empty) while still be valid instances of the custom object (type).https://wiki.php.net/rfc/objects-can-be-falsifiable <
https://wiki.php.net/rfc/objects-can-be-falsifiable>If you saw the latest from this morning, not much has changed except
hopefully improved formatting and now being the official mix of things.If this is your first time, the cues are taken from:
- __toString()
- Stringable
- and __toArray() (not accepted or approved yet)
Thank you for all the feedback and patience so far, appreciate it!
Cheers,
Josh
I really appreciate any effort that can make PHP a more powerful language.
However, for this item, I believe it will generate much greater cognitive
complexity for new users (and I hate to assume that this is a problem, but
we have to remember our roots).Why, for now a variable $x will be truth, since it is an instance of an
object, now it could be false, because it implements __toBool() and
Falsiable interface in a "magic" way, making everything very confusing if
the user is not aware of each class that he is instantiating (or even
receiving from other places without much condition to know precisely what
it is).For this type of situation, I still prefer the introduction of clearer
methods that suggest the validation of your own properties, in order to
make it much more flexible than simply returning a single "general" boolean
by-instance. For example: isValid().if ($entity?->isAdministrator()) { /** Entity is an administrator. / }
else if ($entity?->isModerator()) { /* Entity is a moderator. / }
else if ($entity) { /* Entity should be an user or similar. / }
else { /* Entity is not defined. */ }On the other hand, the implicity (bool) cast working together with
__toBool() is a good one, but I think that it is being discussed in another
topic.Atenciosamente,
David Rodrigues
This really isn't about using the variable that comes back. For that, sure, nullsafe is helpful. It's about the type of the return value and allowing a class to have an "empty"/"false"/"nope" version.
$u = $repo->getUser($id);
What happens if the user isn't found in the DB? Options today are:
- return null, which is a non-type, and thus you need to make the return type ?User or User|null, which means the caller must always check it's nullness.
- return false, which is just wrong on numerous levels and you should never do that.
- Throw an exception, which is expensive in PHP. Now you're using an exception for flow control, which is generally disapproved of.
Allowing an object to falsify itself is a 4th, more type-safe option. It lets you return an object that both fails a boolean check (like null) but also has default values in it for the base case. A user object likely wouldn't use that, but a value object like an Address very well could.
Until we can support for-reals monads (which would require enums and generics to do properly; the former is possible the latter is very hard), it's the best option we have for more type-safe returns.
--Larry Garfield
Hey Larry,
On Wed, Jul 15, 2020 at 5:32 PM Larry Garfield larry@garfieldtech.com
wrote:
- return null, which is a non-type, and thus you need to make the return
type ?User or User|null, which means the caller must always check it's
nullness.Allowing an object to falsify itself is a 4th, more type-safe option. It
lets you return an object that both fails a boolean check (like null) but
also has default values in it for the base case. A user object likely
wouldn't use that, but a value object like an Address very well could.Until we can support for-reals monads (which would require enums and
generics to do properly; the former is possible the latter is very hard),
it's the best option we have for more type-safe returns.
Adding a "falsey" state for code that expects is_object($foo) === (bool) $foo
seems to be a massive BC break to me, and reduces type safety of
boolean operators (and checks) by expanding the object
type and all
operators and expressions that can interact with it.
In general, ?T
is very much equivalent to Maybe T
Where instance Monad Maybe
is implemented in type-safe langs as (quoting
https://en.wikibooks.org/wiki/Haskell/Understanding_monads/Maybe - I
omitted return
because the lifting bit is not really used in PHP):
(>>=) :: Maybe a -> (a -> Maybe b) -> Maybe b
(>>=) m g = case m of
Nothing -> Nothing
Just x -> g x
?T
is very much the same in PHP (pseudo-code, since nullability is not
represented the same way in Haskell):
(>>=) :: ?T -> (T -> Maybe T2) -> ?T2
(>>=) m g = case m of
null -> null
x -> g x
Let's not confuse Java nullable issues with PHP nullability, which is quite
healthy :-)
I see adding a "falsey" evaluation to something that has been assumed (for
a looooooong time) as universally "truthy" is a very major issue.
We need code examples where this is a clear advantage over previous logic:
as it stands, I don't see why I would ever prefer (bool) $object
over
$object !== null
, or $object->valid()
(which I believe to be an
anti-pattern)
Marco Pivetta
Hey Larry,
On Wed, Jul 15, 2020 at 5:32 PM Larry Garfield larry@garfieldtech.com
wrote:
- return null, which is a non-type, and thus you need to make the return
type ?User or User|null, which means the caller must always check it's
nullness.Allowing an object to falsify itself is a 4th, more type-safe option. It
lets you return an object that both fails a boolean check (like null) but
also has default values in it for the base case. A user object likely
wouldn't use that, but a value object like an Address very well could.Until we can support for-reals monads (which would require enums and
generics to do properly; the former is possible the latter is very hard),
it's the best option we have for more type-safe returns.Adding a "falsey" state for code that expects
is_object($foo) === (bool) $foo
seems to be a massive BC break to me, and reduces type safety of
boolean operators (and checks) by expanding theobject
type and all
operators and expressions that can interact with it.In general,
?T
is very much equivalent toMaybe T
I disagree entirely. The value of a Maybe over just null is
- You can bind to it even if the result is empty; the "if is null" check gets abstracted away from the main code.
- You are forced to unwrap/extract the value if you're not just binding, which makes it less likely you'll forget to check if it's null.
--Larry Garfield
Hey Larry,
http://ocramius.github.com/
On Wed, Jul 15, 2020 at 6:55 PM Larry Garfield larry@garfieldtech.com
wrote:
I disagree entirely. The value of a Maybe over just null is
- You can bind to it even if the result is empty; the "if is null" check
gets abstracted away from the main code.
That's correct: that's how the Maybe
monad works too.
- You are forced to unwrap/extract the value if you're not just binding,
which makes it less likely you'll forget to check if it's null
No need to unwrap: that's what >>=
does for you.
Assuming you use psalm or phpstan, a program that doesn't use >>=
, or
forgets to check for null
on ?T
does not type-check anyway, and can be
rejected before even writing any runtime tests: that's sound.
Marco Pivetta
Hey Larry,
http://ocramius.github.com/On Wed, Jul 15, 2020 at 6:55 PM Larry Garfield larry@garfieldtech.com
wrote:I disagree entirely. The value of a Maybe over just null is
- You can bind to it even if the result is empty; the "if is null" check
gets abstracted away from the main code.That's correct: that's how the
Maybe
monad works too.
That... is what I'm describing? A Maybe Monad?
- You are forced to unwrap/extract the value if you're not just binding,
which makes it less likely you'll forget to check if it's nullNo need to unwrap: that's what
>>=
does for you.Assuming you use psalm or phpstan, a program that doesn't use
>>=
, or
forgets to check fornull
on?T
does not type-check anyway, and can be
rejected before even writing any runtime tests: that's sound.Marco Pivetta
My Haskell experience is zilch, but since PHP lacks a dedicated bind operator that also magically unwraps, that doesn't seem relevant. (Mind you, I'd be all for a dedicated bind operator that also auto-unwraps! But that's not on the radar at the moment.)
--Larry Garfield
http://ocramius.github.com/Hey Larry,
On Wed, Jul 15, 2020 at 7:15 PM Larry Garfield larry@garfieldtech.com
wrote:
Hey Larry,
http://ocramius.github.com/On Wed, Jul 15, 2020 at 6:55 PM Larry Garfield larry@garfieldtech.com
wrote:I disagree entirely. The value of a Maybe over just null is
- You can bind to it even if the result is empty; the "if is null"
check
gets abstracted away from the main code.That's correct: that's how the
Maybe
monad works too.That... is what I'm describing? A Maybe Monad?
Sorry, my wording wasn't right.
>>=
implemented for ?T
does the exact same thing as >>=
implemented
via Maybe T
: same abstraction, different implementation (instance Monad ?T
vs instance Monad Maybe
).
Therefore, if we had monadic syntax, you could write the following code:
do
value <- someCall someInput
someOperation <- someOtherCall value
return value
The above would work with both ?T
and Maybe T
, luckily.
- You are forced to unwrap/extract the value if you're not just
binding,
which makes it less likely you'll forget to check if it's nullNo need to unwrap: that's what
>>=
does for you.Assuming you use psalm or phpstan, a program that doesn't use
>>=
, or
forgets to check fornull
on?T
does not type-check anyway, and can
be
rejected before even writing any runtime tests: that's sound.My Haskell experience is zilch, but since PHP lacks a dedicated bind
operator that also magically unwraps, that doesn't seem relevant. (Mind
you, I'd be all for a dedicated bind operator that also auto-unwraps! But
that's not on the radar at the moment.)
No need for magic operators: just because >>=
is written with an infix
operator in Haskell doesn't make it magic.
In fact, Marco Perone released a nice PHP type-safe FP library around
functional PHP abstractions today, and >>=
is implemented as bind()
there:
https://github.com/marcosh/lamphpda/blob/ebde827e78f0d5889da041fecfea353817890e35/src/Maybe.php#L175
You can use that today: it already brings some value to the table, without
any need for language-level additions (besides generics) :)
Greets,
Marco Pivetta
Hey Larry,
On Wed, Jul 15, 2020 at 5:32 PM Larry Garfield larry@garfieldtech.com
wrote:
- return null, which is a non-type, and thus you need to make the return
type ?User or User|null, which means the caller must always check it's
nullness.Allowing an object to falsify itself is a 4th, more type-safe option. It
lets you return an object that both fails a boolean check (like null) but
also has default values in it for the base case. A user object likely
wouldn't use that, but a value object like an Address very well could.Until we can support for-reals monads (which would require enums and
generics to do properly; the former is possible the latter is very hard),
it's the best option we have for more type-safe returns.Adding a "falsey" state for code that expects
is_object($foo) === (bool) $foo
seems to be a massive BC break to me, and reduces type safety of
boolean operators (and checks) by expanding theobject
type and all
operators and expressions that can interact with it.In general,
?T
is very much equivalent toMaybe T
Where
instance Monad Maybe
is implemented in type-safe langs as (quoting
https://en.wikibooks.org/wiki/Haskell/Understanding_monads/Maybe - I
omittedreturn
because the lifting bit is not really used in PHP):(>>=) :: Maybe a -> (a -> Maybe b) -> Maybe b (>>=) m g = case m of Nothing -> Nothing Just x -> g x
?T
is very much the same in PHP (pseudo-code, since nullability is not
represented the same way in Haskell):(>>=) :: ?T -> (T -> Maybe T2) -> ?T2 (>>=) m g = case m of null -> null x -> g x
Let's not confuse Java nullable issues with PHP nullability, which is quite
healthy :-)I see adding a "falsey" evaluation to something that has been assumed (for
a looooooong time) as universally "truthy" is a very major issue.We need code examples where this is a clear advantage over previous logic:
as it stands, I don't see why I would ever prefer(bool) $object
over
$object !== null
, or$object->valid()
(which I believe to be an
anti-pattern)
In my opinion, it seems reasonable to be able to swap out an array for
an object that implements Traversable, ArrayAccess, Countable, etc in
common situations. However, this is not possible in at least two
common situations:
if ($array) {
// ...
foreach ($array as $key => $value) {
// ...
}
// ...
}
if (!empty($array)) {
// ...
foreach ($array as $key => $value) {
// ...
}
// ...
}
You could swap !empty()
for count > 0
, but I can imagine objects
where we can know that we aren't empty without knowing the full count.
For example, a database result using a cursor or something; we'll know
on the first response if we have even 1 result, but we won't know the
final count ahead of time.
Of course, there are cases where we can't do this swap because of
array_*
specific functions anyway, but my point is rather that from
a language perspective it seems like a reasonable goal to pursue.
Hi!
Hey Larry,
wrote:
- return null, which is a non-type, and thus you need to make the return
type ?User or User|null, which means the caller must always check it's
nullness.Allowing an object to falsify itself is a 4th, more type-safe option.
It
lets you return an object that both fails a boolean check (like null) but
also has default values in it for the base case. A user object likely
wouldn't use that, but a value object like an Address very well could.Until we can support for-reals monads (which would require enums and
generics to do properly; the former is possible the latter is very hard),
it's the best option we have for more type-safe returns.Adding a "falsey" state for code that expects
is_object($foo) === (bool) $foo
seems to be a massive BC break to me, and reduces type safety of
boolean operators (and checks) by expanding theobject
type and all
operators and expressions that can interact with it.
I've heard a few times that this would reduce type safety. Well, on runtime
it really does, the same way when you use null in a wrong manner, but
with static analysis tools in place I wouldn't expect a formal proof that
it's impossible to analyze that kind of code. Unless we are eval'ing or
declaring variables dynamically... I'll try to research a bit on that.
What would be the default static analyser used for PHP today? Psalm?
Ty,
Márcio
Hi,
Implement an interface and magic method to allow objects to represent false
(or empty) while still be valid instances of the custom object (type).
https://wiki.php.net/rfc/objects-can-be-falsifiable <
https://wiki.php.net/rfc/objects-can-be-falsifiable>If you saw the latest from this morning, not much has changed except
hopefully improved formatting and now being the official mix of things.If this is your first time, the cues are taken from:
- __toString()
- Stringable
- and __toArray() (not accepted or approved yet)
Thank you for all the feedback and patience so far, appreciate it!
If I'm not mistaken, other languages use different methods to
express emptiness and falsyness and sometimes these methods
have the same implementation sometimes not. So I think it's good
that the RFC scope retracted a bit.
For instance, in Pyhton it's common to see code like:
class test:
def bool(self):
return False
nonzero=bool
BTW, I'm not saying you should pursue both RFCs or anything, that's
just a reference in case you are looking around how others did it.
Ty,
Márcio
Learning more everyday. This thread will be the “official thread” - other related threads have been added to the RFC (including one from 11 years ago): https://bit.ly/php-0002-rfc https://bit.ly/php-0002-rfc
Created fork - with PR on fork to explore and leave notes without hitting the mailing list with every little thing: https://github.com/joshbruce/php-src/pull/1 https://github.com/joshbruce/php-src/pull/1
Will be letting this cool down for the recommended two weeks while I learn the things and PHP 8 is prepared for release.
Not sure if this should be put up for a vote before or after the implementation based on what I’ve read so. Seems the recommendation is the vote first - unless sample implementation seems like it would be helpful. I think it would; so, will proceed on the implementation course pending feedback.
Thanks again for all the feedback, questions, and concerns!
Cheers,
Josh
Learning more everyday. This thread will be the “official thread” -
other related threads have been added to the RFC (including one from 11
years ago): https://bit.ly/php-0002-rfc https://bit.ly/php-0002-rfcCreated fork - with PR on fork to explore and leave notes without
hitting the mailing list with every little thing:
https://github.com/joshbruce/php-src/pull/1
https://github.com/joshbruce/php-src/pull/1Will be letting this cool down for the recommended two weeks while I
learn the things and PHP 8 is prepared for release.Not sure if this should be put up for a vote before or after the
implementation based on what I’ve read so. Seems the recommendation is
the vote first - unless sample implementation seems like it would be
helpful. I think it would; so, will proceed on the implementation
course pending feedback.Thanks again for all the feedback, questions, and concerns!
Cheers,
Josh
I'd say it's bad form to call a vote on something without at least a partial implementation and a path to finish the implementation. Otherwise, even if it passes it may not happen and that's just all kinds of confusing. The code doesn't have to be perfect, but at least enough to demonstrate it can be done and won't cause a 50% performance regression. That also gives voters confidence that if the RFC passes the code will actually get completed by the people that proposed it, rather than just expecting someone else to magically do it for them.
--Larry Garfield
Trying not to drown the list while some RFCs are getting worked out and preparing for 8.0.
Quick update:
-
Walking through Nikita’s instructions - writing about it here: https://joshbruce.dev/essays/software-development/php-falsifiable-objects https://joshbruce.dev/essays/software-development/php-falsifiable-objects
-
I agree some type of working concept should be a thing.
-
Have some other projects taking precedent, any assistance on implementation would be greatly appreciated.
Cheers,
Josh
Learning more everyday. This thread will be the “official thread” -
other related threads have been added to the RFC (including one from 11
years ago): https://bit.ly/php-0002-rfc https://bit.ly/php-0002-rfcCreated fork - with PR on fork to explore and leave notes without
hitting the mailing list with every little thing:
https://github.com/joshbruce/php-src/pull/1
https://github.com/joshbruce/php-src/pull/1Will be letting this cool down for the recommended two weeks while I
learn the things and PHP 8 is prepared for release.Not sure if this should be put up for a vote before or after the
implementation based on what I’ve read so. Seems the recommendation is
the vote first - unless sample implementation seems like it would be
helpful. I think it would; so, will proceed on the implementation
course pending feedback.Thanks again for all the feedback, questions, and concerns!
Cheers,
JoshI'd say it's bad form to call a vote on something without at least a partial implementation and a path to finish the implementation. Otherwise, even if it passes it may not happen and that's just all kinds of confusing. The code doesn't have to be perfect, but at least enough to demonstrate it can be done and won't cause a 50% performance regression. That also gives voters confidence that if the RFC passes the code will actually get completed by the people that proposed it, rather than just expecting someone else to magically do it for them.
--Larry Garfield
--
To unsubscribe, visit: https://www.php.net/unsub.php
Hi Josh,
I'd recommend looking at https://github.com/php/php-src/pull/5156 (linked to in your RFC's reference) when implementing this PR.
Additionally, I didn't see if this was brought up - PHP already has a class which is falsifiable - SimpleXMLElement.
You can see the source of ext/simplexml/simplexml.c for _IS_BOOL
static int sxe_object_cast_ex(zend_object *readobj, zval *writeobj, int type)
{
php_sxe_object *sxe;
xmlChar *contents = NULL;
xmlNodePtr node;
int rv;
sxe = php_sxe_fetch_object(readobj);
if (type == _IS_BOOL) {
node = php_sxe_get_first_node(sxe, NULL);
if (node) {
ZVAL_TRUE(writeobj);
} else {
ZVAL_BOOL(writeobj, !sxe_prop_is_empty(readobj));
}
return SUCCESS;
}
static php_sxe_object* php_sxe_object_new(zend_class_entry *ce, zend_function *fptr_count)
intern->zo.handlers = &sxe_object_handlers;
// ... elsewhere in ext/simplexml/simplexml.c
sxe_object_handlers.cast_object = sxe_object_cast;
The default handlers would be overridden, so this would probably need to fall back to the original handlers for types other than bool.
Also, this might need to copy all of the handlers from the parent class entry's zend_object_handlers,
to meet the expected behavior of other cast types and other magic behaviors from SimpleXMLElement and other classes continuing to work.
From the perspective of static analysis, a few things to note:
-
Static analyzers such as psalm/phan currently deliberately don't bother handling the possibility that
object
can be false even after it was checked for falsiness.
There's hundreds of other things that could be implemented, and adding the special casing and performance overhead checking for FFI objects and SimpleXMLElement, subclasses of those is currently low priority compared to those things.Code using SimpleXMLElement/FFI is generally limited to a few files in practice.
Definitely possible for analyzers to support this for known base classes, though, and the priority would increase if the RFC passed.
I am a maintainer of Phan.
-
For BC reasons, internal data structures such as ArrayObject probably wouldn't get changed in any php release
(e.g. could break code using falsiness check instead of null check).
So this might lead to inconsistencies with newer extensions if half of the data structures treat emptiness as false and half don't. -
This feature may end up getting adopted in cases where it's convenient but turns out prone to accidental bugs and later is deprecated and removed.
For example, https://stackoverflow.com/questions/25031236/if-elem-vs-elem-is-not-none was seen in python(e.g.
if (!$this->resultSetObject) { $this->resultSetObject = slow_db_operation(); }
would not behave the way people would previously expect for most objects (executed repeatedly instead of once))
<?php
function test(SimpleXMLElement $e) {
// False positive RedundantCondition in psalm
if ($e) {
}
}
And I don’t know of a way to add an interface to new stdClass() - but thought this might be a valid use case:
stdClass
isn't a final class. I assume they meant this
class EmptyStdClass extends stdClass implements Falsifiable {
public function __toBool() { return false; }
}
function example(stdClass $s) {
if (!$s) { throw new Exception("Previously impossible"); }
}
Thanks,
- Tyson
Hey Tyson,
This is great! Thank you so much, sincerely.
Still slow goings, which is fine, we have at least a year. lol
Static analyzers seem to be the biggest concern to date.
Haven’t been able to get one running locally - though I’ve only spent a few minutes here and there; definitely on the list.
A use case for this sort of thing is also a concern or question. I came across Pipeline from the PHP League and saw their interruptible processor and was wondering if something like this would be helpful there - for pipeline patterns in general: https://github.com/thephpleague/pipeline/blob/master/src/InterruptibleProcessor.php https://github.com/thephpleague/pipeline/blob/master/src/InterruptibleProcessor.php
While working on another project, saw this line from the PHP array_filter docs:
"If no callback is supplied, all entries of array equal to `FALSE` (see converting to boolean) will be removed."
I’m still field testing the latest iteration of my base project, but wanted to put a working (non-internals) implementation out there (note this covers emptiness and falseness for the purposes of the project):
Tests - https://github.com/8fold/php-shoop/blob/master/tests/RfcObjectCanBeDeclaredFalsifiableTest.php https://github.com/8fold/php-shoop/blob/master/tests/RfcObjectCanBeDeclaredFalsifiableTest.php
Interface - https://github.com/8fold/php-shoop/blob/master/src/FilterContracts/Interfaces/Falsifiable.php https://github.com/8fold/php-shoop/blob/master/src/FilterContracts/Interfaces/Falsifiable.php - for our purposes the efToBool would be __toBool
Default implementation - https://github.com/8fold/php-shoop/blob/master/src/Shooped.php#L216 https://github.com/8fold/php-shoop/blob/master/src/Shooped.php#L216
“Type system” implementation - https://github.com/8fold/php-shoop/blob/master/src/Filter/TypeAsBoolean.php https://github.com/8fold/php-shoop/blob/master/src/Filter/TypeAsBoolean.php
Cheers,
Josh
Hi Josh,
I'd recommend looking at https://github.com/php/php-src/pull/5156 (linked to in your RFC's reference) when implementing this PR.
Additionally, I didn't see if this was brought up - PHP already has a class which is falsifiable - SimpleXMLElement.You can see the source of ext/simplexml/simplexml.c for
_IS_BOOL
static int sxe_object_cast_ex(zend_object *readobj, zval *writeobj, int type) { php_sxe_object *sxe; xmlChar *contents = NULL; xmlNodePtr node; int rv; sxe = php_sxe_fetch_object(readobj); if (type == _IS_BOOL) { node = php_sxe_get_first_node(sxe, NULL); if (node) { ZVAL_TRUE(writeobj); } else { ZVAL_BOOL(writeobj, !sxe_prop_is_empty(readobj)); } return SUCCESS; }
static php_sxe_object* php_sxe_object_new(zend_class_entry *ce, zend_function *fptr_count) intern->zo.handlers = &sxe_object_handlers; // ... elsewhere in ext/simplexml/simplexml.c sxe_object_handlers.cast_object = sxe_object_cast;
The default handlers would be overridden, so this would probably need to fall back to the original handlers for types other than bool.
Also, this might need to copy all of the handlers from the parent class entry's zend_object_handlers,
to meet the expected behavior of other cast types and other magic behaviors from SimpleXMLElement and other classes continuing to work.From the perspective of static analysis, a few things to note:
- Static analyzers such as psalm/phan currently deliberately don't bother handling the possibility that
object
can be false even after it was checked for falsiness.
There's hundreds of other things that could be implemented, and adding the special casing and performance overhead checking for FFI objects and SimpleXMLElement, subclasses of those is currently low priority compared to those things.Code using SimpleXMLElement/FFI is generally limited to a few files in practice.
Definitely possible for analyzers to support this for known base classes, though, and the priority would increase if the RFC passed.
I am a maintainer of Phan.
- For BC reasons, internal data structures such as ArrayObject probably wouldn't get changed in any php release
(e.g. could break code using falsiness check instead of null check).
So this might lead to inconsistencies with newer extensions if half of the data structures treat emptiness as false and half don't.- This feature may end up getting adopted in cases where it's convenient but turns out prone to accidental bugs and later is deprecated and removed.
For example, https://stackoverflow.com/questions/25031236/if-elem-vs-elem-is-not-none was seen in python(e.g.
if (!$this->resultSetObject) { $this->resultSetObject = slow_db_operation(); }
would not behave the way people would previously expect for most objects (executed repeatedly instead of once))<?php function test(SimpleXMLElement $e) { // False positive RedundantCondition in psalm if ($e) { } }
And I don’t know of a way to add an interface to new stdClass() - but thought this might be a valid use case:
stdClass
isn't a final class. I assume they meant thisclass EmptyStdClass extends stdClass implements Falsifiable { public function __toBool() { return false; } } function example(stdClass $s) { if (!$s) { throw new Exception("Previously impossible"); } }
Thanks,
- Tyson
--To unsubscribe, visit: https://www.php.net/unsub.php
Hey Internals,
So, I received a message that said of the mail I should have received from internals bounced; so, this is part test and part update.
I’ve started mapping out call paths in the project that brought this concept to light.
The project, Shoop, never uses is_bool()
- instead it always uses empty, then reverses that for:
- Booleans
- Numbers
- Arrays
- Strings
- stdClass or data-only classes
- Json strings, which it treats as #5
I essentially get standard SPL behavior when I do this.
For the object definition I can define two interfaces:
- Falsifiable
- Emptiable
The checks go like this:
- if the object implements Falsifiable: the __toBool method would be called and would return the result. (This check would be inside
is_bool()
and could use the empty() implementation) - if the object implements Emptiable: the __isempty method would be called and return the result. (This check would be inside empty())
- else: standard output from
is_bool()
or empty() would be used when passing an instance to either of those SPL functions, depending on which the user is using.
Because the concepts of emptiness and falsiness are so tightly coupled, I’m wondering if it would be better to implement both with this RFC??
Otherwise, Emptiable would be a future enhancement consideration.
I’d like to hear what the rest of Internals thinks.
Next for me: I’m going to finish solidifying the Shoop project and make sure my other projects can use latest and then continue going through the tutorials from Nikita and others on doing development Internals.
Cheers,
Josh
Hey Tyson,
This is great! Thank you so much, sincerely.
Still slow goings, which is fine, we have at least a year. lol
Static analyzers seem to be the biggest concern to date.
Haven’t been able to get one running locally - though I’ve only spent a few minutes here and there; definitely on the list.
A use case for this sort of thing is also a concern or question. I came across Pipeline from the PHP League and saw their interruptible processor and was wondering if something like this would be helpful there - for pipeline patterns in general: https://github.com/thephpleague/pipeline/blob/master/src/InterruptibleProcessor.php https://github.com/thephpleague/pipeline/blob/master/src/InterruptibleProcessor.php
While working on another project, saw this line from the PHP array_filter docs:
"If no callback is supplied, all entries of array equal to `FALSE` (see converting to boolean) will be removed."
I’m still field testing the latest iteration of my base project, but wanted to put a working (non-internals) implementation out there (note this covers emptiness and falseness for the purposes of the project):
Tests - https://github.com/8fold/php-shoop/blob/master/tests/RfcObjectCanBeDeclaredFalsifiableTest.php https://github.com/8fold/php-shoop/blob/master/tests/RfcObjectCanBeDeclaredFalsifiableTest.php
Interface - https://github.com/8fold/php-shoop/blob/master/src/FilterContracts/Interfaces/Falsifiable.php https://github.com/8fold/php-shoop/blob/master/src/FilterContracts/Interfaces/Falsifiable.php - for our purposes the efToBool would be __toBool
Default implementation - https://github.com/8fold/php-shoop/blob/master/src/Shooped.php#L216 https://github.com/8fold/php-shoop/blob/master/src/Shooped.php#L216
“Type system” implementation - https://github.com/8fold/php-shoop/blob/master/src/Filter/TypeAsBoolean.php https://github.com/8fold/php-shoop/blob/master/src/Filter/TypeAsBoolean.php
Cheers,
JoshHi Josh,
I'd recommend looking at https://github.com/php/php-src/pull/5156 https://github.com/php/php-src/pull/5156 (linked to in your RFC's reference) when implementing this PR.
Additionally, I didn't see if this was brought up - PHP already has a class which is falsifiable - SimpleXMLElement.You can see the source of ext/simplexml/simplexml.c for
_IS_BOOL
static int sxe_object_cast_ex(zend_object *readobj, zval *writeobj, int type) { php_sxe_object *sxe; xmlChar *contents = NULL; xmlNodePtr node; int rv; sxe = php_sxe_fetch_object(readobj); if (type == _IS_BOOL) { node = php_sxe_get_first_node(sxe, NULL); if (node) { ZVAL_TRUE(writeobj); } else { ZVAL_BOOL(writeobj, !sxe_prop_is_empty(readobj)); } return SUCCESS; }
static php_sxe_object* php_sxe_object_new(zend_class_entry *ce, zend_function *fptr_count) intern->zo.handlers = &sxe_object_handlers; // ... elsewhere in ext/simplexml/simplexml.c sxe_object_handlers.cast_object = sxe_object_cast;
The default handlers would be overridden, so this would probably need to fall back to the original handlers for types other than bool.
Also, this might need to copy all of the handlers from the parent class entry's zend_object_handlers,
to meet the expected behavior of other cast types and other magic behaviors from SimpleXMLElement and other classes continuing to work.From the perspective of static analysis, a few things to note:
- Static analyzers such as psalm/phan currently deliberately don't bother handling the possibility that
object
can be false even after it was checked for falsiness.
There's hundreds of other things that could be implemented, and adding the special casing and performance overhead checking for FFI objects and SimpleXMLElement, subclasses of those is currently low priority compared to those things.Code using SimpleXMLElement/FFI is generally limited to a few files in practice.
Definitely possible for analyzers to support this for known base classes, though, and the priority would increase if the RFC passed.
I am a maintainer of Phan.
- For BC reasons, internal data structures such as ArrayObject probably wouldn't get changed in any php release
(e.g. could break code using falsiness check instead of null check).
So this might lead to inconsistencies with newer extensions if half of the data structures treat emptiness as false and half don't.- This feature may end up getting adopted in cases where it's convenient but turns out prone to accidental bugs and later is deprecated and removed.
For example, https://stackoverflow.com/questions/25031236/if-elem-vs-elem-is-not-none https://stackoverflow.com/questions/25031236/if-elem-vs-elem-is-not-none was seen in python(e.g.
if (!$this->resultSetObject) { $this->resultSetObject = slow_db_operation(); }
would not behave the way people would previously expect for most objects (executed repeatedly instead of once))<?php function test(SimpleXMLElement $e) { // False positive RedundantCondition in psalm if ($e) { } }
And I don’t know of a way to add an interface to new stdClass() - but thought this might be a valid use case:
stdClass
isn't a final class. I assume they meant thisclass EmptyStdClass extends stdClass implements Falsifiable { public function __toBool() { return false; } } function example(stdClass $s) { if (!$s) { throw new Exception("Previously impossible"); } }
Thanks,
- Tyson
--To unsubscribe, visit: https://www.php.net/unsub.php <https://www.php.net/unsub.php