[RFC][Discussion] Objects can be declared falsifiable

  111009
July 14, 2020 23:58 josh@joshbruce.dev (Josh Bruce)
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
  111010
July 15, 2020 04:48 matthewmatthew@gmail.com (Matthew Brown)
On Tue, 14 Jul 2020 at 19:59, Josh Bruce <josh@joshbruce.dev> wrote:

> 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.
  111019
July 15, 2020 13:39 larry@garfieldtech.com ("Larry Garfield")
On Tue, Jul 14, 2020, at 11:48 PM, Matthew Brown wrote:
> On Tue, 14 Jul 2020 at 19:59, Josh Bruce <josh@joshbruce.dev> wrote: > > > 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.
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
  111024
July 15, 2020 16:04 matthewmatthew@gmail.com (Matthew Brown)
> > > 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.
  111049
July 16, 2020 15:28 josh@joshbruce.dev (Josh Bruce)
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
  111051
July 16, 2020 15:31 josh@joshbruce.dev (Josh Bruce)
> On Jul 16, 2020, at 10:28 AM, Josh Bruce <josh@joshbruce.dev> wrote: > > 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
  111020
July 15, 2020 14:24 david.proweb@gmail.com (David Rodrigues)
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
  111023
July 15, 2020 15:31 larry@garfieldtech.com ("Larry Garfield")
On Wed, Jul 15, 2020, at 9:24 AM, David Rodrigues wrote:
> 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: 1) 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. 2) return false, which is just wrong on numerous levels and you should never do that. 3) 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
  111025
July 15, 2020 16:43 ocramius@gmail.com (Marco Pivetta)
Hey Larry,

On Wed, Jul 15, 2020 at 5:32 PM Larry Garfield <larry@garfieldtech.com>
wrote:

> 1) 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): ```hs (>>=) :: 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): ```hs (>>=) :: ?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 http://twitter.com/Ocramius http://ocramius.github.com/
  111026
July 15, 2020 16:54 larry@garfieldtech.com ("Larry Garfield")
On Wed, Jul 15, 2020, at 11:43 AM, Marco Pivetta wrote:
> Hey Larry, > > On Wed, Jul 15, 2020 at 5:32 PM Larry Garfield <larry@garfieldtech.com> > wrote: > > > 1) 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`
I disagree entirely. The value of a Maybe over just null is 1) You can bind to it even if the result is empty; the "if is null" check gets abstracted away from the main code. 2) 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
  111027
July 15, 2020 16:59 ocramius@gmail.com (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 > > 1) 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.
> 2) 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 http://twitter.com/Ocramius http://ocramius.github.com/
  111028
July 15, 2020 17:14 larry@garfieldtech.com ("Larry Garfield")
On Wed, Jul 15, 2020, at 11:59 AM, Marco Pivetta 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 > > > > 1) 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?
> > 2) 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
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
  111029
July 15, 2020 17:22 ocramius@gmail.com (Marco Pivetta)
<http://ocramius.github.com/>Hey Larry,


On Wed, Jul 15, 2020 at 7:15 PM Larry Garfield <larry@garfieldtech.com>
wrote:

> On Wed, Jul 15, 2020, at 11:59 AM, Marco Pivetta 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 > > > > > > 1) 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: ```hs do value <- someCall someInput someOperation <- someOtherCall value return value ``` The above would work with both `?T` and `Maybe T`, luckily.
> > > 2) 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. > > 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 * https://github.com/marcosh/lamphpda/blob/ebde827e78f0d5889da041fecfea353817890e35/src/Typeclass/Monad.php#L22 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 http://twitter.com/Ocramius http://ocramius.github.com/
  111030
July 15, 2020 19:16 internals@lists.php.net ("Levi Morrison via internals")
On Wed, Jul 15, 2020 at 10:43 AM Marco Pivetta <ocramius@gmail.com> wrote:
> > Hey Larry, > > On Wed, Jul 15, 2020 at 5:32 PM Larry Garfield <larry@garfieldtech.com> > wrote: > > > 1) 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): > > ```hs > (>>=) :: 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): > > ```hs > (>>=) :: ?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.
  111031
July 15, 2020 21:38 marcio.web2@gmail.com (Marcio Almada)
Hi!

Hey Larry,
> > wrote: > > > 1) 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. > > 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
  111032
July 15, 2020 21:42 marcio.web2@gmail.com (Marcio Almada)
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
  111096
July 21, 2020 15:34 josh@joshbruce.dev (Josh Bruce)
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
  111112
July 22, 2020 12:49 larry@garfieldtech.com ("Larry Garfield")
On Tue, Jul 21, 2020, at 10:34 AM, Josh Bruce wrote:
> 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
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
  111404
August 9, 2020 19:54 josh@joshbruce.dev (Josh Bruce)
Trying not to drown the list while some RFCs are getting worked out and preparing for 8.0.

Quick update:

1. 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>

2. I agree some type of working concept should be a thing.

3. Have some other projects taking precedent, any assistance on implementation would be greatly appreciated.

Cheers,
Josh

> On Jul 22, 2020, at 7:49 AM, Larry Garfield <larry@garfieldtech.com> wrote: > > On Tue, Jul 21, 2020, at 10:34 AM, Josh Bruce wrote: >> 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 > > 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 > > -- > PHP Internals - PHP Runtime Development Mailing List > To unsubscribe, visit: https://www.php.net/unsub.php >
  111405
August 9, 2020 20:59 tysonandre775@hotmail.com (tyson andre)
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))

```
> 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
  111720
August 30, 2020 14:32 josh@joshbruce.dev (Josh Bruce)
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

> On Aug 9, 2020, at 3:59 PM, tyson andre <tysonandre775@hotmail.com> wrote: > > 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)) > > ``` > 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 > -- > PHP Internals - PHP Runtime Development Mailing List > To unsubscribe, visit: https://www.php.net/unsub.php >