Re: [RFC] Safe Casting Functions
| From: | Theodore Brown | Date: | Tue, 21 Oct 2014 02:59:36 +0000 |
| Subject: | Re: [RFC] Safe Casting Functions | ||
| Groups: | php.internals | ||
| Request: | Send a blank email to internals+get-78172@lists.php.net to get a copy of this message | ||
On Oct 20 2014, at 8:07 PM, Josh Watzman <jwatzman@fb.com> wrote:
> I think this is pretty cool, but I'm really worried about some of its typing implications.
> In particular:
>
>> The functions return FALSE on failure instead of NULL because:
>
> Throwing an exception or even returning NULL seems so much better than returning
> "false" -- "false" is a boolean, not an error, and despite some historical cases
> of PHP using "false" as a poor person's error code, it really isn't. If you want
> error codes, use error codes, or use exceptions, but having two kinds of failure, null vs false, is
> really confusing.
>
> Addressing your arguments one by one:
>
>> • If strict type hinting were added, they would fail for a nullable typehint
>
> Throwing an exception also addresses this, in a much cleaner way. If you're worried about
> strict typing, then returning "false" is even worse, since the return type of, for
> example, to_int is now "int | bool" as opposed to "nullable-int" or
> "int" (if you throw).
>
>> • FALSE is a traditional error value in PHP
>
> Since this is a new function, one that doesn't interoperate in any complicated way with
> the existing library or affect BC, this doesn't seem that important. IMO a language should have
> one failure/absense-of-value, in most cases "null", and having a weird second case seems,
> well, weird. If you have more interesting failure cases, just throw an exception, or return null if
> you want, don't continue propagating a weird second kind of null (that isn't actually
> null, it's a boolean false).
>
> Also, that we *couldn't* introduce a meaningful to_bool function, even if we decided we
> wanted it, indicates to me that returning false is the wrong thing to do.
>
>> • NULL is used to signify the absence of a value - but what we want to signify is an
>> invalid value (similar to 0 vs NaN)
>
> I might argue that failure is indeed absense-of-value, but an exception seems like a much
> better way to convey this.
>
> It's also interesting to look at how other languages handle failures of this kind. Most
> imperative languages I know of either throw or return null; in particular, Python, which has fairly
> similar type system to PHP in a lot of ways, throws. Functional languages do more interesting
> things; Haskell does something complicated but morally equivalent to returning null. But I'm
> not aware of any language which specifically returns false.
I'm going to second Josh on this and add some of my own thoughts as a userland developer.
I believe it is important to have a simple, safe, and consistent way to cast values in PHP, and I
would be supportive of these functions regardless of whether they throw an exception or return an
error value. With that said, I believe that exceptions would be the best way to handle cast failures
for the following reasons:
1. Exceptions can provide more information about the cast failure (e.g. overflow vs. invalid type
vs. missing
__toString method)
2. Throwing an exception is safer. For example, strpos($haystack, $needle,
to_int($offset)) would thrown an exception rather than silently converting $offset to 0 if it
can't be safely cast.
3. There *is* precedent for this in other languages. Josh already mentioned Python, and I'll
add C#. Convert.ToInt64() in C# throws a FormatException if the string
does not have valid syntax, or an OverflowException if the value is less than
Int64.MinValue or greater than Int64.MaxValue.
4. Mixed return types are a bad pattern. If we get scalar return type declarations in the future it
would be nice to be able to use them with these functions.
On Oct 20 2014, at 8:17 PM, Andrea Faulds <ajf@ajf.me> wrote:
> Exceptions make chaining more difficult.
How so? Chaining is worse with error values, in my experience! For example:
$result = do_something_with(to_int(to_float($value)));
if ($result === false) {
// I have no idea which function returned false or why
}
vs.
try {
do_something_with(to_int(to_float($value)));
} catch (Exception $e) {
// I know exactly which function caused the error and why it occurred
}
My userland polyfill includes a branch which throws exceptions: https://github.com/theodorejb/PolyCast/tree/use-exceptions.
Theodore Brown
http://theodorejb.me