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.