Skip to content

Instantly share code, notes, and snippets.

@auroraeosrose
Created October 5, 2015 12:13
Show Gist options
  • Save auroraeosrose/df3976dfdd95e81a4cef to your computer and use it in GitHub Desktop.
Save auroraeosrose/df3976dfdd95e81a4cef to your computer and use it in GitHub Desktop.
<?php
use Eos\Datastructures\Immutable;
use Eos\Datastructures\Struct;
class Rectangle extends Struct implements Immutable {
public $x;
public $y;
public $height;
public $width;
}
$rect = new Rectangle(['x' => 5, 'y' => 6, 'height' => 56, 'width' => 19]);
$rect->x = 5; // will throw ImmutableException
@Crell
Copy link

Crell commented Oct 5, 2015

Counter-thought, as requested:

struct Rectangle implements Immutable {
  public $x;
  public $y;
  public $height;
  public $width;

  // Must not modify a property. Syntax error if so.
  public function area() {
    return $this->height * $this->width;
}

$rect = new Rectangle(['x' => 5, 'y' => 6, 'height' => 56, 'width' => 19]);

// With() is a virtual method on all structs. The first param MUST be a valid property; syntax error if not. Maybe even make the quotes optional, a la Hack's XHR.  Second param is new value.
$r2 = $rect->with('x', '10');
 print_r($r2);
// x=10, y=6, height=56, width=19

// Chainable (probably for free now in PHP 7):
$r3 = $rect->with('x', 10)->with('y', '12');

Benefits:

  • Clearly defined structure. Lots of places for automatic static validation.
  • Very easy to make "one almost like this but with this change" instances.
  • Because of the immutability, there's probably places we can take shortcuts and make optimizations. CoW already handles memory but we could potentially short-cut some CPU time. (Or maybe I'm talking out of my arse.)
  • Can still attach read-only intrinsic behavioral methods, like area(). Forcing that out into separate functions seems unhelpful to me.
  • If you want to do something fancier, you can still add methods that return new objects or whatever else as long as the immutability of the properties is preserved. So you could do:
public function doubleSized() {
  return $this->with('height', $this->height * 2)->with('width', $this->width * 2);
}

Which would run as a normal method at normal method cost.

@auroraeosrose
Copy link
Author

The biggest issue with this is that currently structs disallow any method attachment at all - you'd have a "container" class which would manipulate the rectangle to figure things like area, clip, etc(in Cairo for example this is a Context class, where you pass a rectangle struct to a clip() method)

@Crell
Copy link

Crell commented Oct 5, 2015

Could the virtual method be find without the arbitrary method? (I don't know how difficult ant of this is to implement. I'm just thinking from a DX perspective.)

@Antnee
Copy link

Antnee commented Oct 8, 2015

I much prefer the idea of methodless structs. Just containers for data. However, I'd prefer to take the opportunity to enforce some type hinting on the properties:

struct Rectangle {
    int $x;
    int $y;
    float $height;
    float $width;
}

This is a completely new type, rather than being a sort of class, so class syntax isn't required. All properties are going to be public, especially if you can't add methods, so get rid of public and replace with the type.

Also, if this is going to be a new type, any need to use the new object syntax? So instead of new struct, can we have something like this?

$rect = Rectangle {
    'x' => 1,
    'y' => 3,
    'height' => 2,
    'width' => 5
};

Or, assuming all properties will be added in the correct order (as defined):

$rect = Rectangle { 1, 3, 2, 5 };

This would make me so happy! 😆

Of course, this syntax would mean that you couldn't have a struct called do, while, if, switch etc. Unless the interpreter can understand that because you're assigning a value (ie $foo = do {}) and that there are no arguments (ie $foo = if {}), but reserved words are an accepted norm, aren't they?

@gallna
Copy link

gallna commented Oct 8, 2015

👍 I would like to see in addition:

$rect = (Rectangle)[1, 3, 2, 5 ];

and

$rectObject = new stdClass([
    'x' => 1,
    'y' => 3,
    'diagonal' => 5.3852,
    'height' => 2,
    'width' => 5
]);
$rect = (Rectangle)$rectObject; // => Rectangle {'x' => 1, 'y' => 3,'height' => 2, 'width' => 5}

@auroraeosrose
Copy link
Author

I can't muck with syntax in an extension (yet) sadly - so version 1.0 will not be able to have syntatic sugar
just overloading objects to give you struct functionality

Depending on uptake on this we'll see about doing a core patch

As for typing - yes being able to type properties would be fabulous but that is not currently in 7.0 yet (it's on the shortlist for 7.1, 7.2 inclusion)

Until then typing will be done either with

  1. a second array passed into the constructor with type definitions
  2. a "magic" __types property with type definitions

Yes this is ugly - but until we get the RFC and patch in for nullable typehints and property typehints there's no other way to get it done

I'll add a ->with to the immutable version of the struct, and also do a "strictobject" which is what all the valueobject lovers REALLY want anyway

@Antnee
Copy link

Antnee commented Oct 8, 2015

So you're talking overloading objects, rather than extending? Basically, just to create very lightweight value objects? In which case yeah, I'd be happy enough for that. Progress is progress :) Thanks

On the with() method on the immutable structs, would that always return a new struct? If you're chaining these then you'd be returning a new struct just to throw them away? Would they be built by reference, or would they be new and independent?

@Crell
Copy link

Crell commented Oct 8, 2015

Presumably each with() call would return a new struct instance, but with PHP's copy-on-write the memory impact should be minimal. That's how PSR-7 works, and the initial benchmarks said it was cheap enough to do.

@Crell
Copy link

Crell commented Oct 8, 2015

And I guess now I see the point of nullables, since without them a struct would only sort of function. Unless we allowed a property to have a default value defined, so even $r = Rectangle{}; would give a valid set of values. Honestly I think I prefer that to nullable, as nulls are nothing but a source of pain and suffering.

That is, you'd have:

struct Rectangle {
  int $x = 0;
  int $y = 0;
  float $width = 0;
  float $height = 0;
}

$r = Rectangle{};
print $r->x; // prints 0.

That way there's still never a null.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment