On Mon, Apr 18, 2016 at 9:28 PM, Stanislav Malyshev <smalys...@gmail.com>
wrote:

> Hi!
>
> > It's about the perception of consistency. "Oh, I can do this! Neat:"
> >
> > function neat(Foo | Bar $a) { ... }
>
> You shouldn't be able to do this, because it makes no sense - why would
> a function accept two random types and only them? That's probably a bad
> design - it should be one type or two functions.
>
> > "But I can't do this? WTF?"
> >
> > catch (FooException | BarException $ex) { ... }
>
> But this makes total sense - you routinely catch more than one type of
> exceptions, just not you write it catch(Foo) ... catch(Bar...). You very
> rarely have functions that do exactly the same with two types, and only
> those. The usage is different.
>
> I do not think approach "it should look the same, no matter what the
> usage is" is right.
>
> > And vice-versa. The perception revolves around the fact that both appear
> > to be signatured, regardless of how they're implemented in the engine.
>
> But that's not what they do. Function says "I accept only parameter of
> this type, and it's an error if it's not". Catch says "I will process
> exception of this type, but if it's not, other catch clause may process
> it". You don't chain functions like that, but you do chain catch
> clauses. Again, different usage.
>

// concrete example
// My application uses two implementation of collection pattern:
// one from eloquent and one from haldayne. I have a log method that
// logs value of either collection implementation and catches the same
// kind of invalid collection regardless of implementation
//
// in this example, both union types and multi-catch are available

namespace Eloquent;
class Collection implements Contract\Arrayable {
    public static function toArray() {
        if (! (is_array($this->data) || is_object($this->data))) {
            throw new Exception\UnexpectedValue;
        }
        // ....
     }
}

namespace Haldayne;
class Map implements ToArray {
    public static function toArray() {
        if (! (is_array($this->data) || is_object($this->data))) {
            throw new \UnexpectedValueException;
        }
        // ....
     }
}

namespace Application;
function log(Eloquent\Collection | Haldayne\Map $entity) {
    $logger->log($entity->toArray());
}

try {
    log(Config::find());
} catch (Eloquent\Exception\UnexpectedValue | \UnexpectedValueException
$ex) {
    die('Configuration is neither array nor object');
}

There is a pleasant symmetry in all of this. As a developer, I handle both
library's concrete collection implementations similarly for both happy and
unhappy paths, all using a compact expression (Class1 | Class2). The engine
helps me out here.

Now, imagine multi-catch passes (as I expect), but union types does not. I
would change my log function to this:

function log($entity) {
    if ($entity instanceof Eloquent\Collection | $entity instanceof
Haldayne\Map) {
        $logger->log($entity->toArray());
    } else {
        throw new \InvalidArgumentException;
    }
}

"Ugh, this is much easier in multi-catch. If only functions took multiple
types, this would be much simpler to write."

Now imagine union types pass, but multi-catch doesn't. Then I'd change my
catch:

try {
    log(Config::find());
} catch (Eloquent\Exception\UnexpectedValue $ex) {
    die('Configuration is neither array nor object');
} catch (\UnexpectedValueException $ex) {
    die('Configuration is neither array nor object');
}

"ugh, this is much easier in function types. If only I could catch multiple
exception types in one block."

So when I'm talking about "user confusion", I'm referring to a perception
of inconsistency by userland developers. And, I think we'd be well-wise to
avoid designing inconsistencies. So even though I consider both union types
and multi-catch to be on the rare side of utility, I consider having one
and not the other a worse situation than having neither.

Reply via email to