Reuse existing idea (Cor slots) and show how this new citizen can provide interesting advantages for developers (and language PR).
I think that perl5 should focus on future of programming and emerging trends. Personally I believe future lies in
-
domain driven development for providing domain specific terms to solve domain problems
-
context oriented programming for providing data driven scoping as a counterpart to code structure driven scoping
I'm using term slot as used by Cor
/ Object::Pad
- data-context
controlled named memory place (intentionally not using term variable).
(examples refers to Object::Pad
synopsis)
Although OOP is obvious to use them there are also other patterns where improved implementation of slots can provide another commonly used patterns (and possibly more).
Although anything described here can be achieved with variables as available right now, I'd let variables their current role being code-controlled.
With slots having different address syntax (as LeoNerd already started to contemplate about on #perl5) we can provide a mechanism to address any slot to provide alternative default value.
Example (used Object::Pad
's synopsis), syntax borrowed from xpath
${: / Point / x }
${: / Point / y }
${: / Point / move / dX }
${: / Point [ ${:x} == 0 ] / move / dX }
Same syntax can / should be used in catch expression as well
CATCH (X::Foo [ ${:name} =~ ... ] | X::Bar) ...
Capability to address slot allows to locally alter slot's behaviour.
Operator :=
used in examples should read as "extend meta of LHS"
Alter slot's default value
local ${: / Point / x } := 0;
local ${: / Point / x } := default => 0;
local ${: / Point / x } := default => sub { ${:y} };
Apply additional constraint
local ${: / Point / x } := constraint => Limit (min => 0, max => 10);
Apply additional trait (mimics jsonproperty used in java world)
local ${: / Point / x } := trait => JsonProperty ('point-x');
local ${: / Github / login / username } := trait => Disabled;
Sub arguments can be considered slots as well.
Motivation:
- consistency - context input parameters will be defined same way as classes
- slot allows more complex usage than signatures including optional dependencies
Imagine sub as some private class with single method so
# so method call like this
$point->move (1, 2);
# can be understand as
class Point::move isa Method {
has ($x, $y) :new :type(Int);
returns :type(Int);
}
Point::move->new (self := $point, x := 1, y := 2)->execute;
Modified example of method move
with slots (how it may look like):
method move {
returns :type (Int);
has $dX :type (Int);
has $dY :type (Int);
}
Using this mental model we can provide roles for methods
Getopt::Long (
'opt1=s' => sub does Getopt::Long::Callback { say "got ${:option} = ${:value}" },
);
Introducing new operator :=
for assigning default value to slot it's possible
to support mixed argument convention (how it may look like):
# every call will do the same
$point->move (1, 2);
$point->move (dX := 1, dY := 2);
$point->move (1, dY := 2);
$point->move (2, dX := 1);
$point->move (dY := 2, 1);
Although last two examples may look confusing such possibility will simplify parser as well as it may be handy for code generators.
Probably most valuable feature from language propagation perspective.
Example - create multiple 1D points
local ${: / Point / x } := 0;
Point->new (1); # x = 0; y = 1
Point->new (2); # x = 0; y = 2
Point->new (3); # x = 0; y = 3
Pattern can be applied also on methods (some kind of anonymous Curring)
local ${: / Point / &move / dX } := 1;
$point->move (2); # dX = 1, dY = 2
$point->move (3); # dX = 1, dY = 3
Curring is in fact dependency injection as described before.
Example:
method move_little_bit_left {
local ${: &move / dX } = -1;
goto &move;
}
move_little_bit_left
signature will contain onlydY
Delegation is special case of currying
Example: move method of polar vector is just move of its starting point
class Vector::Polar {
has $start => :type (Point);
has $radius;
has $angle;
method move extends ${:start}::move;
}
Example: move method of vector is move of both its point
class Vector::Polar {
has ($start, $end) => :type (Point);
# Variant - multiple "inheritance"
method move extends ${:start}::move, ${:end}::move;
# Variant - bind using slot constraint
method move {
has $dx :typeof (${: / Point / move / dx });
has $dy :typeof (${: / Point / move / dy });
${:start}->move;
${:end}->move;
}
}
There should be also possibility to define internal slots so developer can apply every declaration capability (traits, dependencies, multisub) on entity which is not publicly exposed.
Such slot still should be queried (makes sense at least in tests)
Example (how it may look like):
sub foo {
has $argument;
my has $internal;
}
Slot mechanism on subs can be used by any block as well, for example in foreach cycle it can be used to iterate over multiple items at once
Example: As far as block has two slots, iterate over two elements at once
foreach (@list) {
has $foo; # $list[0], $list[2], ...
has $bar; # $list[1], $list[3], ...
}
Example: Conditional slots (just mentioning possibility)
foreach (qw[ aaa --bbb ccc ) {
has $foo; # aaa (iteration 1) and --bbb (iteration 2)
if ($foo =~ m/^--) next;
has $bar; # ccc (iteration 2)
}
In functional programming thunk describes a variable which value is not computed until required.
Slot's default value can provide such mechanism if return SLOT
will return slot
by reference and not by value.
Example (how it may look like)
sub connection {
has $username;
has $password;
my has $connection
:default { Resource->connect (${:username}, ${:password}) }
;
return $connection;
}
my $connection = connection; # still not connected
random_sub ($connection); # still not connected - argument not used
$connection->request (...) # connects before calling method
Based on an expectation that every slot has unique name we can treat it as unique constraint as well (in context of multisubs).
All we need is a query to check whether slot is provided
- can be provided with
undef
- can be provided via thunk
Example (how it may look like):
when (has? $slot1 and has? $slot2) { ... }
Combined with default values (how it may look like):
my has $foo
:default
:when (has? slot1) { ... }
:when (has? slot2 and ${:slot2} > 0) { ... }
:otherwise { ... }
;
Single argument providing multiple internal slots (how it may look like):
has $foo
:when (isa Foo) :provides { $slot1 }
:when (isa Bar) :provides { $slot2 via ->build } :provides { $slot1 via 10 }
;
Availability of slots may depend on external conditions (how it may look like):
sub foo {
has $email
:when (${: / API / version } == 1) { :required }
:otherwise :provides { ${:username} }
;
has $username =>
when (${: / API / version } == 1) { :not-available }
otherwise { :required }
;
}
As your code base as well as used libraries evolve, some parts may become deprecated or security risks (hm, nice term possible "security injection").
For example, github API. Although it supports login / password you want to prohibit them and use only token based authentication.
Your code may then look like:
local ${: / Github / username } := :disabled;
local ${: / Github / password } := :disabled;
use Github;
# code will die
my $connection = Github->new (
username := ...,
password := ...,
);
Similary there can be a deprection mechanism:
class Github v3.0 {
${: / Github / username } := :deprecated (since => v2.0);
}
Based on fact that
- every information has its own type
- there are no two instances of same type in processing context
we can extend currying on class definitions as well.
Example (how it may look like):
class Country { }
class Country::City { has $country, isa => 'Country' }
my $CZ = Country->new ();
class CZ::City extends Country::City, $CZ { }
my $Prague = CZ::City->new;
Described extended usage of slots allows perl to provide decent support for context oriented programming.
Additional features:
- data frame - capability to specify new context
- including hierarchy
- write once - slot value can be populated only once
Limited COP is already provided:
- global / class / class instance - acts like data frames
- local - acts like creation of data frame