Child pages
  • return-false-if-something-goes-wrong approach
Skip to end of metadata
Go to start of metadata

 

Personally, I do not like when a function return multiple types. Yet, PHP built-in functions do return multiple types as well (yikes!) and some people were hoping TSPHP does allow a return-false-if-something-goes-wrong approach as well somehow.
Just as a side notice, there exist PHP built-in functions which return bool/int (strpos), bool/string (strstr), bool/resource (fopen) and bool/DateTime (date_create) and most probably further combinations. The important part here, this approach is not restricted to scalar types.

At the moment, one can return multiple types by specifying mixed as return type but it implies that one has to apply casts every time one wants to work with the return value. Although, that does not sound convenient, TSPHP is designed this way on purpose to steer the user in the right direction: do not return several types which have nothing in common. Therefore, I do not intend to support other types than bool as additional type. Everything else hampers the readability too much and goes against the goal readability of TSPHP (see Vision of TSPHP). Hence, the following code is not under discussion to simplify somehow.

Not under discussion
class Bar {}
class Baz {}
function mixed foo(bool $a){
    if ($a) {
        return new Bar();
    }
    return new Baz();
}
mixed $foo = foo(true);
Bar $bar = (Bar) $foo;
Baz $baz = (Baz) $foo; // would cause an error during runtime

If you code this way and you do not think about to change your style then type safety is maybe not what you are looking for.

 

Following an example of a return-false-if-something-goes-wrong approach which is part of this discussion (in contras to the example above).

class Bar {
    function void hello() {
        echo 'hello world!';
    }
}
function mixed foo(bool $a) {
    if ($a) {
        return new Bar();
    }
    return false;
}
mixed $foo = foo(true);
if ($foo !== false) {
    Bar $bar = (Bar) $bar;
    $bar->hello();
}

Relations

This topic relates to scalar type hierarchy

Ideas

Following some thoughts on how to support the return-false-if-something-goes-wrong approach in TSPHP:

  1. TSPHP could specify that each type can hold bool. The code would look as follows:

    function Bar foo(bool $a) {
        if ($a) {
            return new Bar();
        }
        return false;
    }
    Bar $bar = foo(true);
    if ($bar !== false) {
        $bar->hello();
    }
  2. TSPHP could introduce a modifier which defines that a type can also hold false (is falseable). Could look as follows:

    function Bar! foo(bool $a) {
        if ($a) {
            return new Bar();
        }
        return false;
    }
    Bar! $bar = foo(true);
    if ($bar !== false) {
        $bar->hello();
    }
  3. TSPHP could introduce that one can restrict mixed somehow. Could look as follows

    function mixed<Bar, bool> foo(){
        if ($a) {
            return new Bar();
        }
        // return new Baz(); -> would cause a compiler error, only bool and Bar allowed
        return false;
    }
    mixed<Bar, bool> $foo = foo(true);
    if ($foo !== false) {
        Bar $bar = (Bar) $bar;
        $bar->hello();
    }
  4. TSPHP does not support this approach and the user has to create a wrapper for this purpose (could use generics later on). With generics it could look like this

    class Falseable<T> {
        private T value;
        private bool isNotFalse;
        public __construct(mixed $value){
            $this->isNotFalse = $value !== false;
            if($this->isNotFalse) {
                $this->value= (T) $value;
            }
        }
        public function bool isNotFalse(){
            return $this->isNotFalse;
        }
        public function T Value(){
            return $this->value;
        }
    } 

    If TSPHP should support overload for __construct then it could be simplified a little bit.
    The code where Falseable would be used would look as follows

    function Falseable<Bar> foo(bool $a) {
        if ($a) {
            return new Falseable<Bar>(new Bar());
        }
        return new Falseable<Bar>(false);
    }
    Falseable<Bar> $bar = foo(true);
    if ($bar->isNotFalse()) {
        $bar->Value()->hello();
    }

Last but not least, I want to point out that everyone could do the same job by returning null instead of false which has more or less the same effect (not if null is a valid value of course). Or they could throw an exception if something goes wrong (that works always). Therefore it might well be that I am not going to change anything (especially if no one submits a feature request explicitly).

Pros and cons

  1. Every type can hold bool
    + user does not have to think about it (more the PHP way)
    - Goes somewhat against type safety (yet, one can argue mixed as such goes against type safety as well and if mixed is ok then this approach would be ok as well)
    - Cannot be regulated with code conventions (the usage of the other constructs could be forbidden in code conventions)

  2. Falseable modifier
    + Pretty much like Nullable which defines that a certain type can hold null in addition.
    + Can easily be forbidden in code conventions
    - Might be too comfortable, user do not see that it is rather bad design to return two things (yet, others might see this point different and it is restricted to false only and finally the modifier ! somehow screams to watch out (smile))

  3. mixed can be restricted to certain types
    + Looks rather ugly, people might reconsider before returning two types
    + Can easily be forbidden in code conventions
    - Is like a bad syntax choice for point 2 but
         + Is still better than only mixed (safer in a manner of type safety) could be extended to arbitrary types and number of types
         - somehow a conflict between type safety and readability. It favours type safety because it is safer than mixed but it also encourage users to return multiple types which is against type safety
         - complex to implement especially with an arbitrary number of types -> thus 
    only allow it for return types. That means variables can only be of type mixed (not mixed<x,y>) and complexity would still be fairly simple.

  4. User has to create a wrapper
    + no additional complexity in TSPHP core
    + As you can see the code looks rather ugly and one does consider twice before using such a construct
    - only for people who do OOP, functional programming would not have such a construct
    - most probably, there will be several different implementations in user land (unless they stick to the implementation here or this implementation is provided in a standard TSPHP library)

Decision

TSPHP wil go for variant 2 since it enhances readability - one of the goals of TSPHP (see Vision of TSPHP). Furthermore it enables the decision in scalar type hierarchy. See  TSPHP-820 - Getting issue details... STATUS  for more information

  • No labels