Type variants

php.internals

Михаил Востриков

9 years ago
Hello. I have an idea which seems rather useful. I would like to know your opinion, Let's say we have class Rectangle and need to have class Square which will be used in some operations. We don't need any rectangle, this must be only square. What if we could describe a type which is the same as another type but have some restrictions for properties? Something like this: class Rectangle { private $x; private $y; private $width; private $height; public function __construct($x, $y, $width, $height) { $this->x = $x; $this->y = $y; $this->width = $width; $this->height = $height; } } class Square variant of Rectangle { public function __match() { if ($this->width != $this->height) { return ['width' => 'Width and height have to be the same']; } return true; } } function drawSquare(Square $square) { // ... } $rectangle = new Rectagle(0, 0, 10, 10); drawSquare($rectangle); When drawSquare() is called, the check 'instanceof' is performed. It calls magic method __match(). If __match() does not return true, it throws an exception with the data returned: 'Order does not match OrderForCheckout. Reason: ...'. Technically, we don't have method __match() in $rectangle object. So it can be static, or a proxy object can be created here. The second variant can be extended. We may not to inherit implementation of methods, and describe another interface instead, for example new object can be read only. class Square variant of Rectangle { public function ___match() { // ... } public function getWidth() { return parent::getWidth(); } } With some changes in syntax it can be defined in this way: class Square variant of Rectangle { ... getWidth: base::getWidth; } This is like inheritance, but semantically differs a little. Inheritance has vertical direction, type variants have horisontal one. Also type variants have an access for all methods and properties of base class, even for private. Another example. Let's say we have an order checkout process which is splitted into 3 steps - cart for products, date page, delivery address page. This is not an input validation, this is a validation in business logic. class Order { public $products; public $date; public $address; } class OrderForCheckout variant of Order { public function __match() { $errors = []; if (empty($this->products)) { $errors['products'] = 'Product list is empty'; } if ($this->date === null) { $errors['date'] = 'Date is not selected'; } if (empty($this->products)) { $errors['address'] = 'Address is not selected'; } return (empty($errors) ? true : $errors); } } public function checkout(OrderForCheckout $order) { ... } $order = findCurrentOrder(); checkout($order); // if $order does not match requirements, the exception will be thrown

Михаил Востриков

9 years ago
> 'Order does not match OrderForCheckout. Reason: ...'.
Sorry, this is text for second example) I missed this moment. But I think the idea is clear. For manual call of __match() type casting can be used: $orderForCheckout = (OrderForCheckout)$order; Also this feature can be used for checking enum values or intervals. class Value { public $value; public function __construct($value) { $this->value = $value; } } class State variant of Value { const ACTIVE = 1; const INACTIVE = 2; public function __match() { return in_array($this->value, [self::ACTIVE, self::INACTIVE]); } } function setState(State $s) { ... } setState(new State(3)); In constructions like "new State($value)" __match() can be called after __construct(). If to add a possibility to use data types, class Value will not be needed: class State variant of integer { ... public function __match() { return in_array($this, [self::ACTIVE, self::INACTIVE]); } } setState(3);

Michał Brzuchalski

9 years ago
2017-03-11 17:24 GMT+01:00 Michael Vostrikov <michael.vostrikov@gmail.com>:
> > 'Order does not match OrderForCheckout. Reason: ...'. > > Sorry, this is text for second example) I missed this moment. But I think > the idea is clear. > > > For manual call of __match() type casting can be used: > $orderForCheckout = (OrderForCheckout)$order; > > > Also this feature can be used for checking enum values or intervals. > > class Value > { > public $value; > > public function __construct($value) > { > $this->value = $value; > } > } > > class State variant of Value > { > const ACTIVE = 1; > const INACTIVE = 2; > > public function __match() > { > return in_array($this->value, [self::ACTIVE, self::INACTIVE]); > } > } > > function setState(State $s) > { > ... > } > > setState(new State(3)); > > In constructions like "new State($value)" __match() can be called after > __construct(). > > > If to add a possibility to use data types, class Value will not be needed: > > class State variant of integer > { > ... > public function __match() > { > return in_array($this, [self::ACTIVE, self::INACTIVE]); > } > } > > setState(3); >
IMHO the real life implementations always differ, in another case, they would be the same. Example with Rectangle and Square IMO is an example of bad naming in this case. I rather want to call it Quadrangle and have one implementation or event do the simplest inheritation. But I may be wrong.
-- regards / pozdrawiam, -- Michał Brzuchalski about.me/brzuchal brzuchalski.com

Sherif Ramadan

9 years ago
PHP already has horizontal inheritance. They're called traits. How is this an improvement in anyway? On Sat, Mar 11, 2017 at 1:36 AM, Michael Vostrikov < michael.vostrikov@gmail.com> wrote:

Михаил Востриков

9 years ago
> Example with Rectangle and Square IMO is an example of bad naming in this
case. I used this example because this is known problem of inheritance. Type variants solve this problem. Square is a particular case (variant) of Rectangle.
> PHP already has horizontal inheritance. They're called traits. How is
this an improvement in anyway? Trait is just reusable implementation. I suggest type matching. This is extension of type hinting. It can be used in all cases where we need to check if an object matches some criterias before continue operation. It is a kind of defensive programming and a replacement for many 'if-throw' constructions.

Sherif Ramadan

9 years ago
Yea, but how's that an improvement over using an interface? On Sun, Mar 12, 2017 at 1:38 AM, Michael Vostrikov < michael.vostrikov@gmail.com> wrote:

Михаил Востриков

9 years ago
2017-03-12 11:41 GMT+05:00 Sherif Ramadan <theanomaly.is@gmail.com>:
> Yea, but how's that an improvement over using an interface? > >
How can I check if an order is ready for checkout with interface? The difference is in property values, not in presence of methods.

Sherif Ramadan

9 years ago
You could have an abstract classes implement the interface and extend from the abstract classes. On Sun, Mar 12, 2017 at 1:57 AM, Michael Vostrikov < michael.vostrikov@gmail.com> wrote:

Михаил Востриков

9 years ago
2017-03-12 12:03 GMT+05:00 Sherif Ramadan <theanomaly.is@gmail.com>:
> You could have an abstract classes implement the interface and extend from > the abstract classes. > >
Ok, could you give an example, how to check described requirements with interface?

Sherif Ramadan

9 years ago
Which requirements are those exactly? On Sun, Mar 12, 2017 at 3:15 AM, Michael Vostrikov < michael.vostrikov@gmail.com> wrote:

Михаил Востриков

9 years ago
2017-03-12 12:19 GMT+05:00 Sherif Ramadan <theanomaly.is@gmail.com>:
> Which requirements are those exactly? >
An order should have count of products > 0 and date and address filled before checkout procedure.

Sherif Ramadan

9 years ago
I'm not sure what that has to do with inheritance exactly or how it pertains to this discussion. This is an implementation detail. On Sun, Mar 12, 2017 at 3:47 AM, Michael Vostrikov < michael.vostrikov@gmail.com> wrote:

Lester Caine

9 years ago
On 11/03/17 06:36, Michael Vostrikov wrote:
> $rectangle = new Rectangle(0, 0, 10, 10); > drawSquare($rectangle); > > When drawSquare() is called, the check 'instanceof' is performed. It calls > magic method __match(). > If __match() does not return true, it throws an exception with the data > returned:
My problem here is with the reason for needing a different class for 'Square' when $rectangle->is_square() is a simple additional check on a single Rectangle class. drawSquare() seems redundant when $rectangle->draw() does the same job ... a lot of duplicate code for little gain? My problem I think is 'instanceof' is being used incorrectly here.
> 'Order does not match OrderForCheckout. Reason: ...'.
The class is 'Order' which in my book will be a variable list of 'LineItem' and each line item will have a status such as is_instock(). The Order status of is_shipable() flags an order that is ready for shipment which will produce an answer of yes, partial or no. Just as you don't need to change Rectangle->Square just because you want to draw a square with a set of numbers which started as a Rectangle, you don't want to change the class from Order to OrderForCheckout. The base order simple has a set of constraints which must be met before is_ready can apply and is_billable will flag if you only charge for a shipment, or take a full payment with order. LineItem has a check on 'backorder' to validate if it's allowed to add items which are out of stock. While I think I can see the logic behind describing these as different types of object I think it is the same mistake as strictly enforcing 'int' when the raw data you have is a string of numbers. And just because you know you have a number for the quantity to buy you still need to check that against the stock level and handle it differently if there is not sufficient stock. Trying to create 'type's for all the variants of the object is just wrong? int_instock, int_minorder, int_backorder, int_special_production and so on as the types of LineItem so 'instanceof' will work for you?
-- Lester Caine - G8HFL ----------------------------- Contact - http://lsces.co.uk/wiki/?page=contact L.S.Caine Electronic Services - http://lsces.co.uk EnquirySolve - http://enquirysolve.com/ Model Engineers Digital Workshop - http://medw.co.uk Rainbow Digital Media - http://rainbowdigitalmedia.co.uk

Михаил Востриков

9 years ago
2017-03-12 14:14 GMT+05:00 Lester Caine <lester@lsces.co.uk>:
> My problem here is with the reason for needing a different class for
'Square' when $rectangle->is_square() is a simple additional check on a single Rectangle class. Yes. And we have to call it in all operations with Square manually, and throw exceptions/return errors manually.
> drawSquare() seems redundant when $rectangle->draw() does the same job
This is just example of external function with type hinting. And I'm not sure that models should know about device contexts, canvases, pixels and other details of presentation layer.
> Trying to create 'type's for all the variants of the object is just
wrong? int_instock, int_minorder, int_backorder, int_special_production and so on as the types of LineItem so 'instanceof' will work for you? I don't need to create a separate type for every characteristic. I need to create a type for some important operations, which will contain all required restictions of object state and which I could just write in all functions that work with this state.

Yasuo Ohgaki

9 years ago
Hi Michael, On Sat, Mar 11, 2017 at 3:36 PM, Michael Vostrikov < michael.vostrikov@gmail.com> wrote:
> > class Square variant of Rectangle > { > public function __match() > { > if ($this->width != $this->height) { > return ['width' => 'Width and height have to be the same']; > } > > return true; > } > } >
DbC allows this kind of validation at development time. I suppose you would like to check requirement at runtime always. It would be nice to have feature, but I think something like __requrie() is better name for the method. IMO, C# like getter/setter is better though. http://stackoverflow.com/questions/11159438/looking-for-a-short-simple-example-of-getters-setters-in-c-sharp Regards,
-- Yasuo Ohgaki yohgaki@ohgaki.net

Fleshgrinder

9 years ago
On 3/12/2017 11:33 AM, Yasuo Ohgaki wrote:
> DbC allows this kind of validation at development time. > I suppose you would like to check requirement at runtime always. > It would be nice to have feature, but I think > > something like __requrie() > > is better name for the method. > IMO, C# like getter/setter is better though. > > http://stackoverflow.com/questions/11159438/looking-for-a-short-simple-example-of-getters-setters-in-c-sharp >
Hey Yasuo! :) Design by Contract is a nice thing, and you know that I would love to see it as part of the language. However, it is not really the solution to the problem that is being described here, since it is a valid state for an order to be empty. The stated Square-Rectangle example is a different beast, and there are many solutions to it. I probably would argue that Rectangle needs to extend Square and not the other way. Square has an extended functionality compared to Rectangle while being fully compatible. DbC would still be nice and would allow us to add real invariant, pre-, and post-conditions. I, as you might remember, would not go for a magic method but rather an extended block, like we see it for if-else or try-catch: class C {} invariant {} // one assert per line function f() {} requires {} // pre-conditions ensures {} // post-conditions
-- Richard "Fleshgrinder" Fussenegger

Михаил Востриков

9 years ago
2017-03-12 15:33 GMT+05:00 Yasuo Ohgaki <yohgaki@ohgaki.net>:
> DbC allows this kind of validation at development time. > I suppose you would like to check requirement at runtime always.
Yes, DbC from here https://wiki.php.net/rfc/dbc2 describes similar things. This is good idea, but I tell more about checks which cannot be removed from code. Types which meet some requirements looks more closer to business logic terms than constructions like if-throw or require($order->isReadyForSomething()). 2017-03-12 15:35 GMT+05:00 Fleshgrinder <php@fleshgrinder.com>:
> `IncompleteOrder` is the one without date, address, and > probably even products. You directly named it as such. Then you have > `ProcessableOrder` where this is impossible
Yes, this is Order and OrderForCheckout in my example. But they should not be descendants of base class or each other. Because allowed class states are not descendants of that class.
> **BAM** all problems solved.
Yeah, now we have: base Order IncompleteOrder extends Order ProcessableOrder extends Order PaidOrder extends Order // or ProcessableOrder? and later could get: WholesaleOrder extends Order with a question how to extend classes in new branch. Also the problem is how to use this tree in relations. There is class OrderItem with $order property. There is class Customer with $orders property. What classes should have these properties? Every time different? This is a part of another problem that we need to decide what exact class should be created here or there before calling a function which uses it. With type variants (hm, or invariants) it could be: Order ProcessableOrder variant of Order PaidOrder variant of Order WholesaleOrder variant of Order And everywhere we can create and use objects with class Order, and only in some functions they will be considered as processable or not.

Fleshgrinder

9 years ago
On 3/12/2017 2:49 PM, Michael Vostrikov wrote:
> 2017-03-12 15:35 GMT+05:00 Fleshgrinder <php@fleshgrinder.com>: >> `IncompleteOrder` is the one without date, address, and >> probably even products. You directly named it as such. Then you have >> `ProcessableOrder` where this is impossible > > Yes, this is Order and OrderForCheckout in my example. But they should not > be descendants of base class or each other. Because allowed class states > are not descendants of that class. > >> **BAM** all problems solved. > > Yeah, now we have: > base Order > IncompleteOrder extends Order > ProcessableOrder extends Order > PaidOrder extends Order // or ProcessableOrder? > > and later could get: > WholesaleOrder extends Order > > with a question how to extend classes in new branch. > > > Also the problem is how to use this tree in relations. > > There is class OrderItem with $order property. > There is class Customer with $orders property. > What classes should have these properties? Every time different? > > This is a part of another problem that we need to decide what exact class > should be created here or there before calling a function which uses it. > > > With type variants (hm, or invariants) it could be: > Order > ProcessableOrder variant of Order > PaidOrder variant of Order > WholesaleOrder variant of Order > > And everywhere we can create and use objects with class Order, and only in > some functions they will be considered as processable or not. >
Post this on Stackoverflow and I'll give you some answers and possible approaches. ;) You are clearly struggling with architecture and design, but you are definitely not missing language constructs here.
-- Richard "Fleshgrinder" Fussenegger

Михаил Востриков

9 years ago
2017-03-12 18:54 GMT+05:00 Fleshgrinder <php@fleshgrinder.com>:
> > Post this on Stackoverflow and I'll give you some answers and possible > approaches. ;) You are clearly struggling with architecture and design, > but you are definitely not missing language constructs here. > > -- > Richard "Fleshgrinder" Fussenegger >
No, thanks) I don't have real problem. I know some problems with usual inheritance and try to suggest the tool to solve them. Problems which are mostly kinds of Rectangle-Square problem, and where is some restrictions in derived types.

Fleshgrinder

9 years ago
On 3/12/2017 6:16 PM, Michael Vostrikov wrote:
> No, thanks) I don't have real problem. I know some problems with usual > inheritance and try to suggest the tool to solve them. Problems which are > mostly kinds of Rectangle-Square problem, and where is some restrictions in > derived types. >
As I said already, there is no problem if you just change the direction of the inheritance. class Square { private $x; private $y; private $w; public function __construct(int $x, int $y, int $w) { $this->x = $x; $this->y = $y; $this->w = $w; } public function draw(Canvas $canvas): void { } } class Rectangle extends Square { private $h; public function __construct(int $x, int $y, int $w, int $h) { parent::__construct($x, $y, $w); $this->height = $h; } public function draw(Canvas $canvas): void { } } If you want to ensure that you always get a Square if width and height match, no problem either. class Square { private $x; private $y; private $w; private $h; final protected function __construct( int $x, int $y, int $w, int $h ) { $this->x = $x; $this->y = $y; $this->w = $w; $this->h = $h; } public static function new(int $x, int $y, int $w): self { return new self($x, $y, $w, $w); } public function draw(Canvas $canvas): void { } } class Rectangle extends Square { public static function new( int $x, int $y, int $w, ?int $h = null ): parent { if ($h === null || $w === $h) { return new parent($x, $y, $w, $w); } return new self($x, $y, $w, $h); } } Added bonus here is the fact that we disabled the possibility for multiple constructor invocations, and are enforcing constructor argument invariants (which PHP does not).
-- Richard "Fleshgrinder" Fussenegger

Yasuo Ohgaki

9 years ago
Hi Michael, On Sun, Mar 12, 2017 at 7:33 PM, Yasuo Ohgaki <yohgaki@ohgaki.net> wrote:
> IMO, C# like getter/setter is better though. > > http://stackoverflow.com/questions/11159438/looking- > for-a-short-simple-example-of-getters-setters-in-c-sharp >
I pasted wrong URL. This URL has better example code. https://msdn.microsoft.com/en-us/library/w86s7x04.aspx While DbC is nice, but checks are only performed on dev environment for maximum performance and acceptable safety. Therefore, developers must implement mandatory validations at trust boundaries to ensure software safety. i.e. Code correctness and security. Your proposal is useful for this. Please note that, with DbC, caller has responsibility to make correct calls that follow contracts. e.g. Caller must use sane parameters for callee. With this design, basic objects/functions, that may execute validation code repetitively, can safely omit validations for maximum performance as long as contracts are maintained. I'm not against to have method/feature that validates object, but it would be better to implement DbC support, then runtime object validation support. Otherwise, users would design poor performance objects that execute validation code repetitively. Regards,
-- Yasuo Ohgaki yohgaki@ohgaki.net

Михаил Востриков

9 years ago
2017-03-12 23:01 GMT+05:00 Fleshgrinder <php@fleshgrinder.com>:
> As I said already, there is no problem if you just change the direction > of the inheritance. >
Ok, how to inherit Rectangle, Square, Rhomb, all of which are Parallelogram? In your example Rectangle knows restrictions of Square and any Rectangle is instance of Square. It does not match domain model. 2017-03-13 1:40 GMT+05:00 Yasuo Ohgaki <yohgaki@ohgaki.net>:
> I pasted wrong URL. This URL has better example code. > > https://msdn.microsoft.com/en-us/library/w86s7x04.aspx > > Getters/setters is a good feature, I'd like them to be in PHP, but they
should be placed in some classes. What are these classes? We still cannot use all squares as rectangles and some rectangles as squares without questions. And if we already have one $rectangle with w == h, we cannot just use it as square, we must create new object based on $rectangle data.