On Mon, Apr 18, 2016 at 9:28 PM, Stanislav Malyshev <[email protected]>
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.