I am an XP whore and if I reply multiple times, I'll get more XP. So have more thoughts.
Point Class
Your Perl version of the Point class can be simplified a lot using the features of existing modern versions of Perl.
use v5.20;
use experimental 'signatures';
package Point {
sub new ($class, $x, $y) {
bless [$x, $y] => $class;
}
sub x ($self) { $self->[0] }
sub y ($self) { $self->[1] }
sub inverted ($self) {
ref($self)->new($self->y, $self->x);
}
}
Yeah, I cheated by making x and y read only, but dammit they should be read only. If they change, it's a different point, so you should make a different object.
Incidentally, making them read only makes the implementation simpler in Perl, but harder in the other languages you gave examples for.
Type Checking in Core
As for type checking in core, there's already parser support for:
my Foo $foo = Foo->new(...);
However, other than checking that a package "Foo" exists, core Perl doesn't do anything with that.
I think the best route for adding decent type support to Perl would be:
Provide an API which allows a type library module to define types such as my Str $x where Str isn't a package name and $x would be a plain old string not a blessed reference. These should be scoped (either lexically or by package).
If you try to use a type which hasn't been declared for lexical scope, Perl assumes you meant it as a class name.
I'll provide an outline of a simple and perhaps workable API below.
Bundle a module with core which defines a bunch of predefined types. Perhaps automatically load it if a recent enough use VERSION line is given.
Including the following in the core set of types shouldn't be controversial because they conform roughly to the existing types Perl kinda exposes to end users already.
- All the standard ref types: "ArrayRef", "HashRef", "ScalarRef", "CodeRef", "GlobRef", "RegexpRef", etc.
- The types of value that can be stored in an SV: "Int", "UInt", "Num", "Str".
- "Defined" and "Blessed".
Having a type called "Undef" would probably be non-controversial, but it's also pretty useless to declare a variable which will be undef and which will always be undef.
There are loads of other types which are often handy, like "PositiveNum" but which seem likely to end up in bikeshedding. (For example, should "PositiveNum" include zero because that's more useful in most cases, even if zero isn't strictly positive. Should it be called "UNum" for consistency with "UInt", even though the former isn't a well-established name like the latter is, and "Unum" is Latin for "one" in the accusative form. Should it accept overloaded objects? Et cetera.)
To avoid such bikeshedding, plus to allow people to define their own useful application-specific type constraints, I think the core set of types should be kept small and non-controversial, with the API mentioned above being used to define other types.
Allow these types to also be used in sub signatures.
sub foo (Int $bar, Str $baz) {
...;
}
Would be roughly equivalent to:
sub foo {
my Int $bar = $_[0];
my Str $baz = $_[1];
die "..." if @_ > 2;
...;
}
Sketch of an API for Pluggable Types
If Perl encounters:
my Foo $x = $y;
It should interpret "Foo" much the same way it would in the case of Foo->new(). That is, if there's a Foo sub, Perl should treat it as Foo(). If there's no "Foo" sub, then "Foo" is treated as a package name (string). Perl would make this decision based on whether the Foo sub existed at compile time.
After that, any time a value is assigned to $x, then one of the following happens:
"Foo"->CHECK_SCALAR(\$newvalue);
Foo()->CHECK_SCALAR(\$newvalue);
I'm naming the method CHECK_SCALAR instead of just CHECK to allow for it to possibly be extended to arrays and hashes. Passing the value by reference for added efficiency, and because checking arrays and hashes will likely require passing a reference anyway.
This would be the basic API for creating user-defined types. There should probably also be an XS API which would allow a value to be checked via a single xsub call rather than all that method dispatch stuff. The basic type library bundled with core would make use of the XS API, because I'm pretty sure we don't want using the most basic type checks in signatures to slow down our code very much.
Oh yeah, and also:
sub UNIVERSAL::CHECK_SCALAR (Str $class, ScalarRef $sref) {
require Scalar::Util;
unless (Scalar::Util::blessed($$sref) && ${$sref}->isa($class)) {
require Carp;
Carp::croak("...");
}
}