Skip to content

Instantly share code, notes, and snippets.

@Danack
Last active June 10, 2019 21:45
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Danack/9e46dd0e7a31367d6dddbd92ba18eeab to your computer and use it in GitHub Desktop.
Save Danack/9e46dd0e7a31367d6dddbd92ba18eeab to your computer and use it in GitHub Desktop.
TypeDef.md

TypeDef all the things

I've had several conversations about ideas that all revolve about a one idea; allowing typedefs to define new types in PHP that aren't 'just' classes. Although classes are often great they just don't fit for all the things that we would like to use types for.

All of the ideas below are presented with placeholder syntax. It hasn't been thought through that carefully, but it seems to kind of work.

What I'd like to try to discuss is:

  • Introducing a new big piece of syntax into PHP is a big deal, so I'd like to get people's opinion on what syntax would fit the various things it could be used for.

  • How the heck could any of these work?

So, the various typedef usages could be:

Making standard combined exceptions

PHP 7.1 added the ability to catch multiple exception types in a single catch statment: https://wiki.php.net/rfc/multiple-catch

e.g.

try {
    foo();
}
catch(S3Exception|ImagickException|BadArgumentException $e) {
    // handle
}

However this has a downside. If the expected exceptions need to be caught in multiple places in code, the multiple exceptions need to be repeated everywhere they are used.

It would reduce duplication if we could define the mulitple exceptions in once place, and then just use that type in the catch block.

typedef ExpectedExceptions = S3Exception|ImagickException|BadArgumentException;

try {
    foo();
}
catch(ExpectedExceptions $bae) {
  ...
}

Array types aka very simple generics.

There have been several attempts at generics, with at least two RFCs being raised (but not implemented) this year. Although a 'full' generics RFC could be great, for me a large amount of benefit would come from just allowing people to define what types the keys and values of arrays should conform to.

typedef ArrayOfStrings =  array<string>; 

ArrayOfStrings will only accept strings as values, but can have either integers or strings as keys.

    $values = ArrayOfStrings[
        "foo",
        "bar"
    ];
typedef RulesByPriority = array<int, Rule>;

RulesByPriority will only accept objects of type Rule as values, and will only accept integer keys.

// This works
$validRules = RulesByPriority[
    new Rule(...),
    new Rule(...),
    1000 => new Rule(...),
];

// This gives an error as strings not allowed as keys.
$brokenRules = RulesByPriority[
    'foo' => new Rule(...)
];

Functional callable

There have been previous RFCs about being able to 'type' callables, to enforce the parameters and return types of callable parameters. A simpler syntax

typedef Reducer = callable(int, int): int;
typedef MapCallback = callable(ArrayKey $key, $value);

These could then be used as parameter or return types:

function process(array $items, Reducer $rd) {
   $sum = 0;
   foreach ($items as $item) {
       $sum = $rd($sum, $item);
   }
}

Strong types/Semantic Types

Although PHP 7 introduced scalar types, these are still 'weak' types in the sense that they don't carry much semantic meaning.

Allowing people to define scalar types with semantic meaning, would allow type checking for scalar values without the burden of having to wrap them up in classes

typedef Length = int;
typedef Width = int;
typedef Area = int;
typedef EmailAddress = string;
typedef UserName = string;

In use:

function calculateArea(Length $l, Width $w) : Area {
    return Area($l * w);
}

$length = Length(10);
$width = Width(5);

// This works
$area = calculateArea($length, $width);

// This fails the type check
$area = calculateArea($length, $length);

We will also want to be able to define a 'constructor style' rule for the strong types.

typedef EmailAddress extends string {
    if (strpos($this, "@") === false) { throw ...; }
}

Some people might also desire algebraic types. I think this would need to be a separate RFC, as the rules for casting algebraic types are probably going to be orders of magnitude more difficult than single types.

typedef Iterable = array|Traversable
typedef ArrayKey = int|string;
@kelunik
Copy link

kelunik commented Jan 26, 2017

"Strong Types" should probably be named "Semantic Types" and it should probably be $length = new Length(10); to differentiate between semantic types and function calls.

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