Greetings to you all,
I will like to submit an RFC on introducing type alias to php.
Even though Generics won't or never be introduced to PHP, personally i will
like php to add the type alias feature.
Type aliases provide a mechanism to create more descriptive and readable
type hints in PHP code. This enhancement will improve code readability and
maintainability and also to promote type reusability.
Motivation:
The motivation behind introducing type aliases is to:
- Enhance code readability by allowing developers to use meaningful type
hint names. - Improve code maintainability by reducing the likelihood of type hint
updates throughout the codebase. - Encourage the adoption of best practices for type hinting.
Proposal:
Syntax:
Type aliases will be declared using the type
or typealias
keyword,
followed by the alias name, an equal sign (=
), and the type it is aliased
to.
Sample:
type MyType = string;
// or
typealias MyType = string;
// Advance
type MyType = string|null;
// or
type MyType = [string, null];
Usage:
Type aliases can be used in parameter and return type hints as follows:
function greetings(MyType $message): string {
// Implementation
}
greetings(1); // TypeError
Since this is just a basic feature, Type aliases Inheritance (compound
type) may not be added, since we are not digging deeper into generics :).
References:
https://docs.python.org/3/glossary.html#term-type-alias
https://doc.rust-lang.org/beta/reference/items/type-aliases.html
https://www.typescriptlang.org/docs/handbook/typescript-in-5-minutes-func.html#type-aliases
So, What do you think?
On Thu, Oct 26, 2023 at 8:37 AM Oladoyinbo Vincent
oladoyinbov@gmail.com wrote:
Greetings to you all,
I will like to submit an RFC on introducing type alias to php.
Even though Generics won't or never be introduced to PHP, personally i will
like php to add the type alias feature.Type aliases provide a mechanism to create more descriptive and readable
type hints in PHP code. This enhancement will improve code readability and
maintainability and also to promote type reusability.Motivation:
The motivation behind introducing type aliases is to:
- Enhance code readability by allowing developers to use meaningful type
hint names.- Improve code maintainability by reducing the likelihood of type hint
updates throughout the codebase.- Encourage the adoption of best practices for type hinting.
Proposal:
Syntax:
Type aliases will be declared using the
type
ortypealias
keyword,
followed by the alias name, an equal sign (=
), and the type it is aliased
to.Sample:
type MyType = string; // or typealias MyType = string; // Advance type MyType = string|null; // or type MyType = [string, null];
Usage:
Type aliases can be used in parameter and return type hints as follows:
function greetings(MyType $message): string { // Implementation } greetings(1); // TypeError
Since this is just a basic feature, Type aliases Inheritance (compound
type) may not be added, since we are not digging deeper into generics :).References:
https://docs.python.org/3/glossary.html#term-type-alias
https://doc.rust-lang.org/beta/reference/items/type-aliases.html
https://www.typescriptlang.org/docs/handbook/typescript-in-5-minutes-func.html#type-aliases
So, What do you think?
Please make these aliases either per-file (easiest to implement with
the current design of types, in my humble opinion) or able to be
namespaced.
If I have an alias called Currency and a class somewhere else called
Currency, what will happen if one file is included before the other?
How will I specify that I want an alias or class of the same name?
Will I need to alias the class, or can I alias the type alias?
How will autoloading work with composer? Will a file called
"Currency.php" be needed to discover the type alias?
Robert Landers
Software Engineer
Utrecht NL
Hi,
It seems to me that there is more to lose than gain. I'm afraid that when we look at the signature, we'll be confused as to whether it's a class or a type.
Even if these problems were successfully resolved, I feel that it would only reduce readability. I've never seen a union type complex enough to require an alias.
Regards.
Saki
Hi Saki,
I will have to disagree as the convenience doesn't come from the
complexity of union types, rather how many times its used. Its easier to
use ConstantNode in 10 functions vs NodeA|NodeB|NodeC.
Hi,
It seems to me that there is more to lose than gain. I'm afraid that when we look at the signature, we'll be confused as to whether it's a class or a type.
Even if these problems were successfully resolved, I feel that it would only reduce readability. I've never seen a union type complex enough to require an alias.
Regards.
Saki
I would like to voice my support for this and i'm willing to help with
the implementation. As for differentiating between type aliases and
classnames, only one of them can be instantiated.
Lanre
Greetings to you all,
I will like to submit an RFC on introducing type alias to php.
Even though Generics won't or never be introduced to PHP, personally i will
like php to add the type alias feature.Type aliases provide a mechanism to create more descriptive and readable
type hints in PHP code. This enhancement will improve code readability and
maintainability and also to promote type reusability.Motivation:
The motivation behind introducing type aliases is to:
- Enhance code readability by allowing developers to use meaningful type
hint names.- Improve code maintainability by reducing the likelihood of type hint
updates throughout the codebase.- Encourage the adoption of best practices for type hinting.
Proposal:
Syntax:
Type aliases will be declared using the
type
ortypealias
keyword,
followed by the alias name, an equal sign (=
), and the type it is aliased
to.Sample:
type MyType = string; // or typealias MyType = string; // Advance type MyType = string|null; // or type MyType = [string, null];
Usage:
Type aliases can be used in parameter and return type hints as follows:
function greetings(MyType $message): string { // Implementation } greetings(1); // TypeError
Since this is just a basic feature, Type aliases Inheritance (compound
type) may not be added, since we are not digging deeper into generics :).References:
https://docs.python.org/3/glossary.html#term-type-alias
https://doc.rust-lang.org/beta/reference/items/type-aliases.html
https://www.typescriptlang.org/docs/handbook/typescript-in-5-minutes-func.html#type-aliases
So, What do you think?
type MyType = string|null;
function greetings(MyType $message): string {
// Implementation
}greetings(1); // TypeError
When writing RFC you'd have to decide/clarify whether you want this to
be an error in strict_types mode only or not. For example, here int
could be cast to string in non-strict types mode.
--
Aleksander Machniak
Kolab Groupware Developer [https://kolab.org]
Roundcube Webmail Developer [https://roundcube.net]
PGP: 19359DC1 # Blog: https://kolabian.wordpress.com
Hi,
Greetings to you all,
I will like to submit an RFC on introducing type alias to php.
Even though Generics won't or never be introduced to PHP, personally i will
like php to add the type alias feature.Type aliases provide a mechanism to create more descriptive and readable
type hints in PHP code. This enhancement will improve code readability and
maintainability and also to promote type reusability.Motivation:
The motivation behind introducing type aliases is to:
- Enhance code readability by allowing developers to use meaningful type
hint names.- Improve code maintainability by reducing the likelihood of type hint
updates throughout the codebase.- Encourage the adoption of best practices for type hinting.
Proposal:
Syntax:
Type aliases will be declared using the
type
ortypealias
keyword,
followed by the alias name, an equal sign (=
), and the type it is aliased
to.Sample:
type MyType = string; // or typealias MyType = string; // Advance type MyType = string|null; // or type MyType = [string, null];
Usage:
Type aliases can be used in parameter and return type hints as follows:
function greetings(MyType $message): string { // Implementation } greetings(1); // TypeError
Since this is just a basic feature, Type aliases Inheritance (compound
type) may not be added, since we are not digging deeper into generics :).References:
https://docs.python.org/3/glossary.html#term-type-alias
https://doc.rust-lang.org/beta/reference/items/type-aliases.html
https://www.typescriptlang.org/docs/handbook/typescript-in-5-minutes-func.html#type-aliases
So, What do you think?
I would love to see type aliases added to PHP but I think it would be
very less useful if it's scoped per file only.
Like type UserId = string;
in it's self already improves readability
but only if reusable by different files and because of that a big
question about autoloading/performance comes in here as well.
About the syntax: I think this should be consistent with how we declare
types right now ...
type UserId: string;
instead of type UserId = string;
Another question is how the type annotations will behave if the aliased
type is hinted:
type UserId: string;
function test(UserId $userId): string {
return $userId;
}
test("its-me");
Also, would it be possible to handle types as specialized classes
including namespaces, helper functions and usable with reflection?
type UserId: string;
type OtherId: UserId;
UserId::base // "string"
OtherId::base // "string"
UserId::nullable // false
UserId::isValid(mixed $var): bool
$refl = ReflectionClass(UserId::class);
$refl = ReflectionType::from(UserId::class);
...
Best,
Marc
Greetings to you all,
I will like to submit an RFC on introducing type alias to php.
Even though Generics won't or never be introduced to PHP, personally i will
like php to add the type alias feature.Type aliases provide a mechanism to create more descriptive and readable
type hints in PHP code. This enhancement will improve code readability and
maintainability and also to promote type reusability.Motivation:
The motivation behind introducing type aliases is to:
- Enhance code readability by allowing developers to use meaningful type
hint names.- Improve code maintainability by reducing the likelihood of type hint
updates throughout the codebase.- Encourage the adoption of best practices for type hinting.
Proposal:
Syntax:
Type aliases will be declared using the
type
ortypealias
keyword,
followed by the alias name, an equal sign (=
), and the type it is aliased
to.Sample:
type MyType = string; // or typealias MyType = string; // Advance type MyType = string|null; // or type MyType = [string, null];
What would this one even do?
Type aliases have been discussed several times in the past, and a lot of people are strongly in favor, myself included. They are arguably a prerequisite for callable types. (Or rather, the people who want to work on callable types refuse to until we get type aliases. :-) )
The complication is, as some other replies noted, their scope.
File local type aliases would be pretty easy:
use type int|float as numeric;
function add(numeric $a, numeric $b): numeric {}
However, that's also not all that useful. A library that uses some complex type (such as a callable type) could not provide a reusable and standardized alias for it, which means everyone would have to re-declare it themselves in every file that uses it, and would probably all use different names (numeric, number, num, etc.), making it all very confusing.
App-wide aliases run into the autoloading problem. If the engine runs across the add() function above, and "numeric" isn't defined, what can it do? Currently, all it can do is trigger autoloading, which only works for class-ish constructs (class, interface, trait, enum). Making type aliases a full class-like construct seems... weird, and over-engineered. But if not, how do we autoload them? And if they do autoload somehow, does that mean we end up with a bunch of files with a single type
line in them? That seems not-good. You also then have to ask how they interact with namespaces.
So far no one seems to like my idea of "composer has a files block, PHP has a preloader, they're small, so who the hell cares about autoloading, just greedily load them and move on with life", even though I think that's the best answer. :-)
So any type alias proposal would need to sort out the above "definition problem" in a way that's generally acceptable. That's been the hold up for 3 years, I think.
I'm also not sure why you're mentioning generics at all. Type aliases would be helpful with generics, but they're also helpful without generics. They're orthogonal and not especially related. I agree that there's no reason to wait for generics to add type aliases.
--Larry Garfield
On Thu, Oct 26, 2023 at 4:23 PM Larry Garfield larry@garfieldtech.com
wrote:
App-wide aliases run into the autoloading problem. If the engine runs
across the add() function above, and "numeric" isn't defined, what can it
do? Currently, all it can do is trigger autoloading, which only works for
class-ish constructs (class, interface, trait, enum). Making type aliases
a full class-like construct seems... weird, and over-engineered. But if
not, how do we autoload them? And if they do autoload somehow, does that
mean we end up with a bunch of files with a singletype
line in them?
That seems not-good. You also then have to ask how they interact with
namespaces.So far no one seems to like my idea of "composer has a files block, PHP
has a preloader, they're small, so who the hell cares about autoloading,
just greedily load them and move on with life", even though I think that's
the best answer. :-)So any type alias proposal would need to sort out the above "definition
problem" in a way that's generally acceptable. That's been the hold up for
3 years, I think.I'm also not sure why you're mentioning generics at all. Type aliases
would be helpful with generics, but they're also helpful without generics.
They're orthogonal and not especially related. I agree that there's no
reason to wait for generics to add type aliases.--Larry Garfield
Unless someone else has good points to raise, it sounds like there's a
clear sign that file-based type aliases are not useful for PHP.
With that in mind, the discussion seems to point straight to autoloading. I
think this shows a limitation with PHP Namespaces and Autoloading
capabilities. I really don't want to judge/belittle/speak ill of something
that was first released 14 years ago, which means discussion and planning
had to have happened even earlier than that. Everything was completely
different back then.
What I see today is that PHP Namespaces seem to have succeeded thanks to
Composer and PSR-4. Working without Composer in 2023 doesn't seem like a
reality to me. Maybe I'm biased/young and if this premise is "too wrong"
then everything else I say after this might be discarded.
PHP Namespaces, Composer and PSR-4 can't really recreate the experience
that ESM provides because 1) Namespaces itself aren't tied to file paths
and 2) PHP doesn't have a build process (path aliases, Typescript types).
What Composer and PSR-4 offers is a static build tool that runs once
(composer dump-autoload) and creates namespace-to-folder mapping that can
be used to discover symbols without a continuous build process or a
continuous composer dump-autoload --watch
.
Now my question is: Why PHP Namespace limitation SHOULD be a deciding
factor when talking about Type aliases? The problem is there and exists and
is dealt with as best as it can by the union of PHP Namespaces + Composer +
PSR-4. PHP already has a few symbols that require discovery: Class,
Interface, Enum, Traits and they're all tied to autoloading in case they
haven't been discovered yet. If a Type Alias triggers autoloading,
developers are free to:
1- Implement Type Aliases using 1-file-per-type-alias (PSR-4 compatible).
2- Create a single type-to-rule-them-all.php and rely on Composer File
autoload (greedy).
3- Write a custom autoloading function that deals with the problem however
they want.
4- Manually require_once
type-aliases files where they know they need to
be used.
5- Not use Type Alias.
To build up on my question, suppose someone one day comes up with a
solution to PHP Namespace problems (that already affects interfaces,
classes, enums and traits). Could Type Alias make it worse enough that such
a hypothetical solution would be rendered impossible because Type Aliases
were introduced in PHP? Does preventing PHP from having Type Aliases that
comply with the current reality of the project benefit the project in any
meaningful way that it will be easier for something in the future to be
made better?
I know it's hard to talk about hypothetical solutions when we don't know
what they are, but I think we can ballpark that interfaces, classes, enums
and traits are important enough that an enhancement to PHP Namespace system
needs to bring enough benefit without a BC break to be voted in. And such
improvement could just as easily take into consideration type aliases as
one extra thing.
Maybe my lack of knowledge on PHP Source code makes me think that Type
Aliases aren't a big deal as they could be and some insight into that could
shed some light into why this is too simplistic thinking. The simplistic
thinking being: Take Namespaces as they were designed 15 years ago and add
Type Aliases into the mix. Back then a solution decoupled from the
filesystem was introduced and a userland autoloading function was the slim
layer that made PHP Namespaces what they are. We can build on top of that
mindset with type aliases. We need a syntax to declare them and an
autoloading userland function to find where they are declared. Everything
else can fall into place as it did before.
I would love it if we could bring up a discussion on proposing changes to
the PHP Namespace system to allow for something more like ESM while
considering we don't have a build system and a hot-reload system, but I'm
just not sure if PHP Type Aliases is where that discussion MUST to happen.
If this line of thought has any merit, a better question would be whether
Type Alias needs a separate autoload function from class/interfaces or a
single autoload is better. I think that's the kind of discussion that would
help Composer/PSR to decide how to expand and improve the PHP Ecosystem to
include handling of Type Aliases.
--
Marco Deleu
I'll use it a lot if they'll be namespaced. It avoids name-collision using
the existing ecosystem and we already use lots of "uses" in our codes.
When Enums came to the party, they came namespaced as traits, classes and
interfaces. The last 2 represent types, concrete and abstract.
User defined types are nothing more than types, so I think the way to gois
to keep it namespaced as well.
About the type compatibility, covariance and contravariance, let's say I
define some type inside my package:
// File: C1.php => My package code
namespace N1;
typedef MyString = string|Stringable|\Symfony\Component\String\UnicodeString
;
typedef MyBool = null|bool;
typedef BigNumeric = int|float|\Brick\Math\BigNumber;
class C1
{
protected function myQuestionableFunction(MyString $str1): MyBool
{
//...
}
protected function myQuestionableFunction2(null|C1 $str1): ScalarNumeric
{
//...
}
}
// File: C2.php => On the project of those who are using my package
namespace N2;
typedef ScalarNumeric = int|float;
typedef Falsy = null|false;
class C1 extends C2
{
protected function
myQuestionableFunction(string|Stringable|\Symfony\Component\String\UnicodeString
$str1): Falsy
{
//...
}
protected function myQuestionableFunction2(Falsy|C1 $str1):
ScalarNumeric
{
//...
}
}
I propose that this code should be valid code, because, in the end, these
typedef's, are just synonyms.
Note: By the way, I propose to use the C/C++ keyword, which is typedef and
dropping the syntax* "type MyType = [string, null];",*
which differs too from how typehints are used and do not add any benefits
.
--
Erick
Since there may be need for reusability, I feel it can have this kind of
structure:
// File: A.php
namespace A;
typedef TypeLists {
type Msg = string|null;
type Numeric = int|float;
type MyClass = \Package\Test|\Package\Db;
}
// file: B.php
declare(strict_types=1);
use A\TypeLists;
class Test1 {
use TypeLists;
public function greet(Msg $message): Msg {
// ...
}
}
$init = new Test1;
// it triggers TypeError when strict types is declared
$init->greet(1); // TypeError
And for local usage only:
typedef T = int|string|null;
// or
type T = string|int|null;
$msg = fn (T $message): T => $message;
echo $msg;
On Fri, 27 Oct 2023, 12:51 am Erick de Azevedo Lima, <
ericklima.comp@gmail.com> wrote:
I'll use it a lot if they'll be namespaced. It avoids name-collision using
the existing ecosystem and we already use lots of "uses" in our codes.
When Enums came to the party, they came namespaced as traits, classes and
interfaces. The last 2 represent types, concrete and abstract.
User defined types are nothing more than types, so I think the way to gois
to keep it namespaced as well.About the type compatibility, covariance and contravariance, let's say I
define some type inside my package:// File: C1.php => My package code
namespace N1;
typedef MyString =
string|Stringable|\Symfony\Component\String\UnicodeString
;
typedef MyBool = null|bool;
typedef BigNumeric = int|float|\Brick\Math\BigNumber;class C1
{
protected function myQuestionableFunction(MyString $str1): MyBool
{
//...
}protected function myQuestionableFunction2(null|C1 $str1):
ScalarNumeric
{
//...
}
}// File: C2.php => On the project of those who are using my package
namespace N2;
typedef ScalarNumeric = int|float;
typedef Falsy = null|false;class C1 extends C2
{
protected functionmyQuestionableFunction(string|Stringable|\Symfony\Component\String\UnicodeString
$str1): Falsy
{
//...
}protected function myQuestionableFunction2(Falsy|C1 $str1):
ScalarNumeric
{
//...
}
}I propose that this code should be valid code, because, in the end, these
typedef's, are just synonyms.Note: By the way, I propose to use the C/C++ keyword, which is typedef
and
dropping the syntax* "type MyType = [string, null];",*
which differs too from how typehints are used and do not add any benefits
.--
Erick
I would love it if we could bring up a discussion on proposing changes to
the PHP Namespace system to allow for something more like ESM while
considering we don't have a build system and a hot-reload system, but I'm
just not sure if PHP Type Aliases is where that discussion MUST to happen.
For those of us not familiar, can you provide a two or three line summary of ESM, and what you think it provides that PHP currently lacks?
As I understand it, the debate has generally not been about how namespaces work, but about how autoloading works. The relevant parts being a) the ingrained culture of one definition per file (which is not a technical limitation, but makes certain coding patterns feel alien); and b) the lack of a universal build stage that can resolve symbols in advance (making it hard to find a middle way between eager loading all possible definitions, and postponing loading to runtime).
Luckily with a new concept like type aliases, we don't have the additional problem that function and constant autoloading faces, of the dynamic fallback to global scope. So as Mike Schinkel points out, it would be perfectly possible to have autoloaders that loaded one file per namespace rather than one per type name, with no changes to the core namespace or autoloading system at all.
In fact, it seems like a namespaced (rather than per-file) type alias would have to use the exact same mechanism as classes etc, because the run-time won't know it's a type alias until after it's loaded. For example, given function foo(\Acme\MyType $bar) { ... }
, the runtime knows only that \Acme\MyType
is something that can describe a type, not whether that is a class, interface, enum, or type alias.
Coming back to the lack of a build stage, that also means type aliases would be opaque to compile-time optimisations, which are mostly per-file. I'm not sure what impact that would currently have, but it's worth thinking about.
Regards,
Rowan Tommins
[IMSoP]
Le 26 oct. 2023 à 21:23, Larry Garfield larry@garfieldtech.com a écrit :
App-wide aliases run into the autoloading problem. If the engine runs across the add() function above, and "numeric" isn't defined, what can it do? Currently, all it can do is trigger autoloading, which only works for class-ish constructs (class, interface, trait, enum). Making type aliases a full class-like construct seems... weird, and over-engineered. But if not, how do we autoload them? And if they do autoload somehow, does that mean we end up with a bunch of files with a single
type
line in them? That seems not-good. You also then have to ask how they interact with namespaces.So far no one seems to like my idea of "composer has a files block, PHP has a preloader, they're small, so who the hell cares about autoloading, just greedily load them and move on with life", even though I think that's the best answer. :-)
Hi,
“Autoloading” does not mean (and has never meant) “one file per class-like construct”; even if it is the most customary strategy (and the one consecrated by PSR), it is by no means the only one.
Proof of concept: With the following autoloader, at the first time the compiler needs the definition of some class-like symbol under the FooBits\Acme\Type namespace, all symbols under that namespace are loaded together:
spl_autoload_register(function ($class) {
if (str_starts_with($class, 'FooBits\\Acme\\Type\\')) {
// we have conventionally put all types defined by our library under this namespace,
// and there are all defined together in the following file:
require 'vendor/foobits/acme/src/types.php';
}
elseif (str_starts_with($class, 'FooBits\\Acme\\')) {
// for other symbols we use the boring psr-4 convention
require 'vendor/foobits/acme/src/' . str_replace('\\', '/', substr($class, 13)) . '.php';
}
});
I think that treating the type aliases as class-like names for the purpose of naming and autoloading (without giving my opinion whether they are class-like constructs for some definition of “are”) is a reasonable approach, and autoloading/preloading/whatever-loading-strategy ought to be treated as an orthogonal (non-)issue.
—Claude
Why are we reinventing alias syntax? We already have "use" to alias per file:
use My\Namespace\MyFancyType as MyNewFancyType;
use string|int|bool as MyFancyType;
Further, in PHP we specify a type using a colon, not an equal sign,
thus this feels more idiomatic to PHP (and less ambiguous):
type MyFancyType: string|int|bool;
or using existing functionality for creating aliases for types:
class_alias('string|int|bool', 'My\Namespace\MyFancyType');
However,
type MyFancyType = string|int|bool;
immediately makes me think of the consts "string", "int", and "bool"
being bitwise-OR'ed together, and not a type. The only hint that we're
dealing with types is the type
keyword.
Robert Landers
Software Engineer
Utrecht NL
App-wide aliases run into the autoloading problem. If the engine runs across the add() function above, and "numeric" isn't defined, what can it do? Currently, all it can do is trigger autoloading, which only works for class-ish constructs (class, interface, trait, enum). Making type aliases a full class-like construct seems... weird, and over-engineered.
Curious, how do you define a "full class-like construct" in this context? Anything more than autoloading?
And would autoloading actually require something to be a "full class-like construct," or could it not be something lighter weight (assuming full class-like constructs are not than just autoloading?)
But if not, how do we autoload them? And if they do autoload somehow, does that mean we end up with a bunch of files with a single
type
line in them? That seems not-good. You also then have to ask how they interact with namespaces.
Straw man proposal:
When PHP recognizes a symbol in the context it would expect a type yet PHP does not recognize that type, PHP could use the existing autoload mechanism to determine files and directories in which those types might be located. This would address namespaces, I believe
Then, following PSR 4 is could attempt to autoload via a file of the same name. If there are types and classes, interfaces, traits and/or enums with that same name they would all need to be contained in that same named file, much as it currently works.
Of course that would require one .PHP file per type which as you mention would not be ideal.
So instead, a new PSR could be created to extend PSR 4 to add type-specific considerations. For example, when the type passed in is "Foo" it could first look for /~types.php
and load it but it that did not work then it could look for /~types/Foo.php
. I picked the tilde (~) since it cannot be part of a valid namespace of class name so no backward compatibility concerns.
If the type name passed to the autoloader is Foo/Bar
then it could look in /~types.php
first, if not there then /Foo/~types.php
and if not there, then /~types/Foo/Bar.php
. I picked the tilde (~) since it cannot be part of a valid namespace of class name so no backward compatibility concerns.
Having a single ~types.php
per namespace would let developers put all their types for that namespace there, if that is what they prefer. Or they could pull all types in the root's /~types.php
. Or they could create a file for each, whichever their preferences.
I am envisioning a new type_exists()
function would be required.
Even better would be to add an additional optional parameter $type to my_custom_autoloader_new() which could either pass in a numeric for some new predefined constants like PHP_AUTOLOAD_CLASS=1, PHP_AUTOLOAD_INTERFACE=2, etc., or just a string like "class", "interface", etc.
Based on that idea, such an autoloader might look like this:
function my_custom_autoloader_new( $name, $type ) {
$dir = DIR . '/includes';
switch ($type) {
case PHP_AUTOLOAD_TYPE:
$file = sprintf("%s/~types.php", $dir);
if ( file_exists( $file ) ) {
require_once $file;
}
if (type_exists($name)) {
return;
}
$file = sprintf( "%s/~types/%s.php", $dir, $name );
default:
$file = sprintf("%s/%s.php", $dir, $name);
}
if ( file_exists( $file ) ) {
require_once $file;
}
}
So any type alias proposal would need to sort out the above "definition problem" in a way that's generally acceptable. That's been the hold up for 3 years, I think.
The above is potentially one way to skin that cat.
Anyway, I'm sure there will be lots of opinions on this, so bikeshed away.
-Mike
So, we have to discuss and conclude on the best syntax to use for the Type
Alias:
Syntax 1:
type T = int|string|null;
Syntax 2:
type T: int|string|null;
Syntax 3:
use int|string|null as T;
And also, we need a function to check of type exist, maybe type_exists()
will be a great choice :).
And also, in order to group and namespace the Type Alias, i suggest we use
typedef
, like i specify in my last message, it will be like this:
File A.php:
namespace Package;
typedef MyTypes {
type Numeric: int|float|null;
type Chars: string|null;
type Result: bool|null;
}
File B.php:
declare(strict_types=1);
use Package\MyTypes;
class Hello {
use MyTypes;
public function A(Numeric $id, Chars $message): Result
{
// get the value
echo Numeric::value; // int|float|null
}
}
Well, it's just the round up of every features listed in this discussion,
how is it? I will like to see your thought on this :).
Best Regards
O. Vincent.
On Oct 26, 2023, at 3:23 PM, Larry Garfield larry@garfieldtech.com
wrote:App-wide aliases run into the autoloading problem. If the engine runs
across the add() function above, and "numeric" isn't defined, what can it
do? Currently, all it can do is trigger autoloading, which only works for
class-ish constructs (class, interface, trait, enum). Making type aliases
a full class-like construct seems... weird, and over-engineered.Curious, how do you define a "full class-like construct" in this context?
Anything more than autoloading?And would autoloading actually require something to be a "full class-like
construct," or could it not be something lighter weight (assuming full
class-like constructs are not than just autoloading?)But if not, how do we autoload them? And if they do autoload somehow,
does that mean we end up with a bunch of files with a singletype
line in
them? That seems not-good. You also then have to ask how they interact
with namespaces.Straw man proposal:
When PHP recognizes a symbol in the context it would expect a type yet PHP
does not recognize that type, PHP could use the existing autoload mechanism
to determine files and directories in which those types might be located.
This would address namespaces, I believeThen, following PSR 4 is could attempt to autoload via a file of the same
name. If there are types and classes, interfaces, traits and/or enums with
that same name they would all need to be contained in that same named file,
much as it currently works.Of course that would require one .PHP file per type which as you mention
would not be ideal.So instead, a new PSR could be created to extend PSR 4 to add
type-specific considerations. For example, when the type passed in is "Foo"
it could first look for/~types.php
and load it but it that did not work
then it could look for/~types/Foo.php
. I picked the tilde (~) since it
cannot be part of a valid namespace of class name so no backward
compatibility concerns.If the type name passed to the autoloader is
Foo/Bar
then it could look
in/~types.php
first, if not there then/Foo/~types.php
and if not
there, then/~types/Foo/Bar.php
. I picked the tilde (~) since it cannot
be part of a valid namespace of class name so no backward compatibility
concerns.Having a single
~types.php
per namespace would let developers put all
their types for that namespace there, if that is what they prefer. Or they
could pull all types in the root's/~types.php
. Or they could create a
file for each, whichever their preferences.I am envisioning a new
type_exists()
function would be required.Even better would be to add an additional optional parameter $type to
my_custom_autoloader_new() which could either pass in a numeric for some
new predefined constants like PHP_AUTOLOAD_CLASS=1,
PHP_AUTOLOAD_INTERFACE=2, etc., or just a string like "class", "interface",
etc.Based on that idea, such an autoloader might look like this:
function my_custom_autoloader_new( $name, $type ) {
$dir = DIR . '/includes';
switch ($type) {
case PHP_AUTOLOAD_TYPE:
$file = sprintf("%s/~types.php", $dir);
if ( file_exists( $file ) ) {
require_once $file;
}
if (type_exists($name)) {
return;
}
$file = sprintf( "%s/~types/%s.php", $dir, $name );
default:
$file = sprintf("%s/%s.php", $dir, $name);
}
if ( file_exists( $file ) ) {
require_once $file;
}
}So any type alias proposal would need to sort out the above "definition
problem" in a way that's generally acceptable. That's been the hold up for
3 years, I think.The above is potentially one way to skin that cat.
Anyway, I'm sure there will be lots of opinions on this, so bikeshed away.
-Mike
namespace Package; typedef MyTypes { type Numeric: int|float|null; type Chars: string|null; type Result: bool|null; }
use Package\MyTypes; class Hello { use MyTypes; public function A(Numeric $id, Chars $message): Result { } }
Your typedef is essentially just a trait. One new keyword less. What
would be missing is direct access to type.
If we indeed allow type inside a class/trait, then natural syntax
becomes the one used for const/var.
I think your syntax 2 is the worst anyway. Syntax 3 (use-as) will not
gonna fly as we already have use-as in a conflicting use for classes,
unless you change it to "use type string|null as NullableString".
I think Syntax 1 makes most sense, especially considering a case of long
list of "subtypes" that could be class names with namespace.
Types should also support visibility (public|protected|private) when in
class/trait context.
--
Aleksander Machniak
Kolab Groupware Developer [https://kolab.org]
Roundcube Webmail Developer [https://roundcube.net]
PGP: 19359DC1 # Blog: https://kolabian.wordpress.com
Hey,
So actually while reading this:
On Fri, Oct 27, 2023 at 11:42 PM Oladoyinbo Vincent oladoyinbov@gmail.com
wrote:
And also, in order to group and namespace the Type Alias, i suggest we use
typedef
, like i specify in my last message, it will be like this:File A.php:
namespace Package; typedef MyTypes { type Numeric: int|float|null; type Chars: string|null; type Result: bool|null; }
I came up with an idea:
In php we don't have inner/nested classes, like in Java.
But maybe at some point we could have. An inner class would help with
avoiding some extra files and autoloading, especially when that class would
be needed as private.
Coming back to types, what if a type would be allowed to be defined on the
class level?
It would solve the autoloading problem as it would be loaded together with
the class.
And I guess that in real-life, complex types are usually related to some
code using them, so I expect that identifying a class where the type to be
placed would not be hard.
And even if it would be difficult, There can always be a class named Types
that would have only type definitions.
An example would look like this:
namespace My\App;
class Types {
public type Numeric: int|float;
}
And it could be used with:
use My\App\Types.Numeric;
function castNumericToFloat(Numeric $number): float {
return (float)$number;
}
or
use My\App\Types;
function castNumericToFloat(Types.Numeric $number): float {
return (float)$number;
}
or by using an import alias, of course.
Maybe we can use this construct, along with allowing a type to be defined
in a normal way, a way that would not permit autoloading.
However, a way that would better fit code that is procedural and it can be
loaded in the same functions.php that some projects use.
Allowing inner "types" on classes might be a bit more complex that I can
ever evaluate.
But it might open the door for inner classes and there are nice constructs
that can be built using this.
Regards,
Alex
So, who is going to help with the implementation :)
On Saturday, October 28, 2023, Alexandru Pătrănescu drealecs@gmail.com
wrote:
Hey,
So actually while reading this:
On Fri, Oct 27, 2023 at 11:42 PM Oladoyinbo Vincent oladoyinbov@gmail.com
wrote:And also, in order to group and namespace the Type Alias, i suggest we use
typedef
, like i specify in my last message, it will be like this:File A.php:
namespace Package; typedef MyTypes { type Numeric: int|float|null; type Chars: string|null; type Result: bool|null; }
I came up with an idea:
In php we don't have inner/nested classes, like in Java.
But maybe at some point we could have. An inner class would help with
avoiding some extra files and autoloading, especially when that class would
be needed as private.Coming back to types, what if a type would be allowed to be defined on the
class level?
It would solve the autoloading problem as it would be loaded together with
the class.
And I guess that in real-life, complex types are usually related to some
code using them, so I expect that identifying a class where the type to be
placed would not be hard.
And even if it would be difficult, There can always be a class named Types
that would have only type definitions.An example would look like this:
namespace My\App;
class Types {
public type Numeric: int|float;
}And it could be used with:
use My\App\Types.Numeric;
function castNumericToFloat(Numeric $number): float {
return (float)$number;
}or
use My\App\Types;
function castNumericToFloat(Types.Numeric $number): float {
return (float)$number;
}or by using an import alias, of course.
Maybe we can use this construct, along with allowing a type to be defined
in a normal way, a way that would not permit autoloading.
However, a way that would better fit code that is procedural and it can be
loaded in the same functions.php that some projects use.Allowing inner "types" on classes might be a bit more complex that I can
ever evaluate.
But it might open the door for inner classes and there are nice constructs
that can be built using this.Regards,
Alex
On Sat, Oct 28, 2023 at 10:29 PM Alexandru Pătrănescu
drealecs@gmail.com wrote:
Hey,
So actually while reading this:
On Fri, Oct 27, 2023 at 11:42 PM Oladoyinbo Vincent oladoyinbov@gmail.com
wrote:And also, in order to group and namespace the Type Alias, i suggest we use
typedef
, like i specify in my last message, it will be like this:File A.php:
namespace Package; typedef MyTypes { type Numeric: int|float|null; type Chars: string|null; type Result: bool|null; }
I came up with an idea:
In php we don't have inner/nested classes, like in Java.
But maybe at some point we could have. An inner class would help with
avoiding some extra files and autoloading, especially when that class would
be needed as private.Coming back to types, what if a type would be allowed to be defined on the
class level?
It would solve the autoloading problem as it would be loaded together with
the class.
And I guess that in real-life, complex types are usually related to some
code using them, so I expect that identifying a class where the type to be
placed would not be hard.
And even if it would be difficult, There can always be a class named Types
that would have only type definitions.An example would look like this:
namespace My\App;
class Types {
public type Numeric: int|float;
}And it could be used with:
use My\App\Types.Numeric;
function castNumericToFloat(Numeric $number): float {
return (float)$number;
}or
use My\App\Types;
function castNumericToFloat(Types.Numeric $number): float {
return (float)$number;
}or by using an import alias, of course.
Maybe we can use this construct, along with allowing a type to be defined
in a normal way, a way that would not permit autoloading.
However, a way that would better fit code that is procedural and it can be
loaded in the same functions.php that some projects use.Allowing inner "types" on classes might be a bit more complex that I can
ever evaluate.
But it might open the door for inner classes and there are nice constructs
that can be built using this.Regards,
Alex
My personal opinion is that file-based type aliases would be the best.
It solves an immediate problem while introducing a problem most people
will never have (needing to reuse the type aliases elsewhere). It
allows them to be used in functions and classes, while also making
code more readable but not opaque outside the file. For example, if
there is a type called BigNumber that was an alias of
GMP|int|float|string, outside the file, knowing the actual types in my
IDE is more useful than "BigNumber" and doesn't require me to dig into
the definition or even agree with the other file's definition of
"BigNumber." I just need to know that I need to pass it one of those
types from my own code. However, reading that file's code, I don't
need to know the intimate details of the aliases to make it more
readable.
If we find ourselves constantly writing the same type aliases in a
bunch of files, we only need to wait less than a year to implement
"global" (as in, available once include'ed) in the very next version
of PHP.
Robert Landers
Software Engineer
Utrecht NL
On Sun, 29 Oct 2023 at 20:36, Robert Landers landers.robert@gmail.com
wrote:
My personal opinion is that file-based type aliases would be the best.
It solves an immediate problem while introducing a problem most people
will never have (needing to reuse the type aliases elsewhere). It
allows them to be used in functions and classes, while also making
code more readable but not opaque outside the file. For example, if
there is a type called BigNumber that was an alias of
GMP|int|float|string, outside the file, knowing the actual types in my
IDE is more useful than "BigNumber" and doesn't require me to dig into
the definition or even agree with the other file's definition of
"BigNumber." I just need to know that I need to pass it one of those
types from my own code. However, reading that file's code, I don't
need to know the intimate details of the aliases to make it more
readable.If we find ourselves constantly writing the same type aliases in a
bunch of files, we only need to wait less than a year to implement
"global" (as in, available once include'ed) in the very next version
of PHP.
No, this is actually ridiculous. File based type alias are dumb and I will
vote against this.
My main motivation to work on function autoloading in the first place was
to create a mechanism so that adding a new type autoloader is very easy.
As I already find the semantics of one interface per file dumb, let's not
make it even more ridiculous of having a single line in a file, especially
as a library/project will likely want to declare more than one type.
An IDE should very much have no struggle to resolve what the type alias
actually resolves too, they already do this with classes and interfaces.
And needing to declare every alias in every single file of a project is a
ludicrous idea.
Best,
Gina/George P. Banyard
File-based are the worst.
Let the types be attached to namespaces. As we have autoloaders, everyone
is free to load them however they want (PSR-4, file, custom autoloader,
whatever).
Best regards,
Erick
Em seg., 30 de out. de 2023 às 08:34, G. P. B. george.banyard@gmail.com
escreveu:
On Sun, 29 Oct 2023 at 20:36, Robert Landers landers.robert@gmail.com
wrote:My personal opinion is that file-based type aliases would be the best.
It solves an immediate problem while introducing a problem most people
will never have (needing to reuse the type aliases elsewhere). It
allows them to be used in functions and classes, while also making
code more readable but not opaque outside the file. For example, if
there is a type called BigNumber that was an alias of
GMP|int|float|string, outside the file, knowing the actual types in my
IDE is more useful than "BigNumber" and doesn't require me to dig into
the definition or even agree with the other file's definition of
"BigNumber." I just need to know that I need to pass it one of those
types from my own code. However, reading that file's code, I don't
need to know the intimate details of the aliases to make it more
readable.If we find ourselves constantly writing the same type aliases in a
bunch of files, we only need to wait less than a year to implement
"global" (as in, available once include'ed) in the very next version
of PHP.No, this is actually ridiculous. File based type alias are dumb and I will
vote against this.
My main motivation to work on function autoloading in the first place was
to create a mechanism so that adding a new type autoloader is very easy.
As I already find the semantics of one interface per file dumb, let's not
make it even more ridiculous of having a single line in a file, especially
as a library/project will likely want to declare more than one type.An IDE should very much have no struggle to resolve what the type alias
actually resolves too, they already do this with classes and interfaces.
And needing to declare every alias in every single file of a project is a
ludicrous idea.Best,
Gina/George P. Banyard
I really like the idea and I would love to see it in PHP. I'm wondering
however, what would be the scope of the feature and how complex would be
designed type system. Examples I saw in this thread could be easily
replaced with union and intersection types (i.e. numeric as int|float). In
my opinion, there is a little benefit implementing in this shape making the
PHP core more complex.
The two use cases of user defined types in PHP which would benefit a lot
IMO, would be:
- Typed arrays similar to Typescript.
- Semantic types which would increase the security of systems. Example:
type UserId = int;
function setUserId_1(int $userId){}
function setUserId_2(UserId $userId){}
setUserId_1(5); // OK
setUserId_2(5); // TypeError
setUserId_1(UserId(5)); // OK
setUserId_2(UserId(5)); // OK
Kind regards,
Jorg
I really like the idea and I would love to see it in PHP. I'm wondering
however, what would be the scope of the feature and how complex would be
designed type system. Examples I saw in this thread could be easily
replaced with union and intersection types (i.e. numeric as int|float). In
my opinion, there is a little benefit implementing in this shape making the
PHP core more complex.The two use cases of user defined types in PHP which would benefit a lot
IMO, would be:
- Typed arrays similar to Typescript.
- Semantic types which would increase the security of systems. Example:
type UserId = int;function setUserId_1(int $userId){}
function setUserId_2(UserId $userId){}
setUserId_1(5); // OK
setUserId_2(5); // TypeErrorsetUserId_1(UserId(5)); // OK
setUserId_2(UserId(5)); // OKKind regards,
Jorg
Simple unions are the easiest to talk about in quick examples, but the real benefit of type aliases is in other cases, some of which they would enable.
Example:
I have a real parameter defined in my attributes library like this:
\ReflectionProperty|\ReflectionMethod|\ReflectionClassConstant $subject
And it appears several times, I believe. That would definitely be nicer if simplified to an alias.
Example:
There's general consensus that callable types would be beneficial: callable(RequestInterface, string): string or similar. But inline, that gets long and complicated fast. Type aliases would allow simplifying that to
type callable(RequestInterface, string): string as PropertyRetriever
function foo(PropertyRetriever $c) { ... }
Example:
As in the above example, type aliases serve as documentation for callables or complex types, explaining what that complex ruleset actually means, semantically.
Example:
If generics or typed arrays ever happen, they'd probably be useful here, too.
Note that using type aliases in a required-fashion is a different matter, and potentially not feasible. Free standing variables are untyped, which means setUserId(UserId $id) could not require a UserId, because there's no way to define a variable as being a UserId, not an int. While I can definitely see a value in that kind of restriction, in practice I don't think PHP is capable of it without vastly larger changes.
--Larry Garfield
I have been so busy lately, which gave me no time to work on the type alias
implementation. but I will be happy if anyone can volunteer/help in
implementing this feature so that it can be introduced in PHP 8.4 or 9.0,
as this will help optimise a lot of people and codebase out there.
I really like the idea and I would love to see it in PHP. I'm wondering
however, what would be the scope of the feature and how complex would be
designed type system. Examples I saw in this thread could be easily
replaced with union and intersection types (i.e. numeric as int|float).
In
my opinion, there is a little benefit implementing in this shape making
the
PHP core more complex.The two use cases of user defined types in PHP which would benefit a lot
IMO, would be:
- Typed arrays similar to Typescript.
- Semantic types which would increase the security of systems. Example:
type UserId = int;function setUserId_1(int $userId){}
function setUserId_2(UserId $userId){}
setUserId_1(5); // OK
setUserId_2(5); // TypeErrorsetUserId_1(UserId(5)); // OK
setUserId_2(UserId(5)); // OKKind regards,
JorgSimple unions are the easiest to talk about in quick examples, but the
real benefit of type aliases is in other cases, some of which they would
enable.Example:
I have a real parameter defined in my attributes library like this:
\ReflectionProperty|\ReflectionMethod|\ReflectionClassConstant $subject
And it appears several times, I believe. That would definitely be nicer
if simplified to an alias.Example:
There's general consensus that callable types would be beneficial:
callable(RequestInterface, string): string or similar. But inline, that
gets long and complicated fast. Type aliases would allow simplifying that
totype callable(RequestInterface, string): string as PropertyRetriever
function foo(PropertyRetriever $c) { ... }
Example:
As in the above example, type aliases serve as documentation for callables
or complex types, explaining what that complex ruleset actually means,
semantically.Example:
If generics or typed arrays ever happen, they'd probably be useful here,
too.Note that using type aliases in a required-fashion is a different matter,
and potentially not feasible. Free standing variables are untyped, which
means setUserId(UserId $id) could not require a UserId, because there's no
way to define a variable as being a UserId, not an int. While I can
definitely see a value in that kind of restriction, in practice I don't
think PHP is capable of it without vastly larger changes.--Larry Garfield
--
Best Regards,
O. Vincent
Hello,
I took the time to read you. Very interesting discussion.
I'm also interested in the type definition.
Because these two functions are equivalent:
function foo(?int $baz) {}
function foo(null|int $baz) {}
We could imagine a personalized null-safety type.
typedef Refutable as null|false|NAN|INF; // I'm not sure that
NAN
andINF
are types
typedef UserId as Refutable ! int;// Refutable is the type of null-safety
function zoo(UserId $id) {} // $id is (int)
function noo(?UserId $id) {}// $id is (null|false|NAN|INF|int)
Do you plan to use enum cases as type ?
enum MyEnum{ case Foo = 1; case Zoo = 2;}
use enum MyEnum::Foo as MyFoo;// syntaxe error
typedef Mamamia MyFoo|MyEnum::Zoo