2020/12/25

2020 Perl Advent Calendar - Day 25

<< First | < Prev

Bonus Day!

Over this blog post series we have built up to the post on day 24, which explains that all of what we've seen this series is available and working in Perl, right now. It is the Perl we can write in 2020. All of this has been possible because of the custom keyword feature which was first introduced to Perl in version 5.14.

When Perl gained the ability to support custom keywords provided by modules it started down the path that CPAN modules would experiment with new language ideas. Already a number of such modules exist, and it is likely this idea will continue to develop. What new ideas might turn up in the next few years, and will any of them evolve to become parts of the actual core language?

Here's a collection of some thoughts of mine. Some of these can be implemented in CPAN modules, in the same way as the four modules we've already seen this series. Other ideas however go beyond what would be possible via keywords alone, and stray into the realm of ideas that really do need core Perl support.

Match/case Syntax

Perl 5.10 added the smartmatch operator, ~~. I think we can mostly agree it has not been the success many had been hoping for. Its rules are complex and subtle, and there's far too many of them to remember. Furthermore, it still doesn't express the most basic question of whether basic scalar comparisons for equallity are performed as string or number tests. For example, is the expression 5 ~~ "5.0" true or false? I honestly don't know and the fact I'd have to look it up in a big table of behavior suggests that the thing has failed to achieve its goal.

Yet still we are left without a useful syntax to express control-flow dispatch based on comparing a given value to several example choices - a task for which many languages use keywords switch and case. I have already written to Perl5 Porters with my thoughts on a design I have nicknamed "dumb match", in response to this. The basic idea of dumb match is to make the programmer write down their choice of operator to be used to compare the given value with the various alternatives.

match($var : eq) {
    case("abc") { ... }
    case("def") { ... }
    
    case("ij", "kl", "mno") { ... }  # any of these will match
}

Here the programmer has specifically requested the eq operator, so we know these are stringy comparisons. Alternatively they could have requested any of

match($var : ==) {
    # numerical comparisons
    case(123) { ... }
    case(456) { ... }
}

match($string : =~) {
    # regexp matches
    case(m/^pattern/)    { ... }
    case(m/morestring$/) { ... }  # only the first match wins
}

match($obj : isa) {
    # object class matches
    case(IO::Handle) { ... }
}

Type Assertions

Various people have in various times written about or designed all sorts of variations on a theme of a "type system" for Perl. I have written reactions to some of those ideas before.

The idea I have in mind here is less a feature in itself, and more a piece of common ground for several of the other ideas, though it may have applications to existing pieces of Perl syntax. Common to several ideas is the need to be able to ask, at runtime, whether a given value satisfies some classification criteria. People often bring up thoughts of assertions like "this is a string" or "this is an integer" at the start of these discussions, but that isn't really within the nature or spirit of what Perl's value system can answer. Instead, I think any workable solution would be written in terms of the existing kinds of comparisons.

Perl 5.32 added the isa operator - a real infix operator that asks if its first operand is an object derived from the class given by its second.

if($arg isa IO::Handle) {
    ...
}

This is certainly one kind of type assertion. I could imagine a new keyword, for the sake of argument for now lets call it is*, which can answer similar yes/no questions on a broader category of criteria. It is likely that the righthand side argument would have to be some sort of expression giving a "type constraint", though exactly what that is I admit I don't have a neat design for currently.

*: Yes, I'm aware this operator choice would interfere probably with Test::More::is. Likely a solution can be found somehow, either by a better naming choice, better parser disambiguation, or a lexical feature guard.

It may be the case that generic type constraints can be constructed with an arbitrary Perl expression to explain how to test if a value meets the constraint:

type PositiveNumber is Numeric where { $_ > 0 };

While in general that would be the most powerful system, it may not lead to a very good performance for several of the other ideas here, so I am still somewhat on the fence about this sort of detail. Because I don't have a firm design on this yet, for the rest of this post I'm just going to give examples using the isa operator instead. But any of the examples or ideas would definitely apply to a more generalised type constraint operator or system, whenever one came to exist.

In any case, once a generic is operator exists for testing type constraints, it feels natural to allow that in match/case syntax too:

match($value : is) {
    case(PositiveNumber) { ... }
    case(NegativeNumber) { ... }
}

In addition it would be wanted in function and method signatures:

method exec($code is Callable)
{
    ...
}

And also object slot variables:

class Caption
{
    has $text is Textual;
    ...
}

Multiple Dispatch

Another idea that comes once you have assertions is the idea of hooking that into function dispatch itself. Some languages give you the ability to define the same-named function multiple times, with different kinds of assertion on its arguments, and at runtime the one that best matches the given arguments will be chosen. There are usually many rules and subtleties to this idea, so it may not ultimately be very suitable for Perl, but if a constraint system did exist then it would be relatively simple to write a CPAN module providing a multi keyword to allow these.

multi sub speak($animal isa Cow) { say "Moo" }

multi sub speak($animal isa Sheep) { say "Baaah" }

Naturally this syntax ought to be implemented in a way that means it still works with method and async as well, allowing us to just as easily

async multi method speak_to($animal isa Goose)
{
    await $self->say("Boo", to => $animal);
}

Signature-like List Assignment

Perl 5.20 introduced signatures, which can be imagined as a neatening up of the familiar syntax of unpacking the @_ list into variables. In some ways the following two functions could be considered identical:

sub add_a
{
    my ($x, $y) = @_;
    return $x + $y;
}

sub add_b($x, $y)
{
    return $x + $y;
}

This does however brush over a few more subtle details of signatures. Firstly, signatures are more strict on the number of values they receive vs. how many they were expecting. While this is a useful feature, it seems odd that Perl now lacks any syntax for performing a list unpack and checking that it has exactly the right number of elements in any situation other than the arguments from function entry.

For that task, I could imagine an operator maybe spelled := which acts exactly the same as a signature on a function:

my ($x, $y) := (1, 2);

my ($x, $y) := (1, 2, 3);  # complains about too many values
my ($x, $y) := (1);        # complains about not enough values

Of course, there's more to signatures than simply counting the elements. Signatures permit a default value to be used if the caller did not specify it; we could allow that too:

my ($one, $two, $three = 3) := (1, 2);

If signatures gain features like type assertions then it seems natural to apply them to the signature-like list assignment operator as well, allowing that to check also:

my ($item isa Item, $group isa Group) := @itemgroup;

If key/value unpacking of named arguments arrives then that too would be useful for unpacking a hash:

my (:$height, :$width) := %params;

Twigils

The slot variables introduced by Object::Pad are written the same as regular lexical variables. I have for a while wished them to be distinct from regular lexicals, so they stand out better visually. The $: syntax can easily be made available, allowing them to be written with that instead:

class Point
{
    has $:x = 0;
    has $:y = 0;
    
    method describe($name) {
        say "Hello $name, this point is at ($:x, $:y)";
    }
}

I accept this is a much more subjective idea than most of the other features. Personally I find it helps to visually distinguish object slots, now that they don't have such notation as $self->{...} to remind you.

True Core Implementations

As earlier mentioned, some of these ideas can be implemented as CPAN modules (those introduced by new keywords), but others (such as the := operator) would require core Perl support. It would also be nice to see some of the more established and stable CPAN keyword modules implemented in core Perl as true syntax as well.

It would be great if, in 2025, we could simply

use v5.40;    # or maybe it will be use v7.x by then

try { ... }
catch ($e) { ... }

class Calculator {
    method add($x, $y) { ... }
}

Having these available to the core language would hopefully mean that a lot more code would more quickly adopt them as features. While these things are all available as CPAN modules, and work even on historic Perl versions as far back as 5.16 from 2012, it seems that some people don't want to make use of such syntax features unless they are provided by the core language itself. Moving the implementation into core may help for other reasons too, such as efficiency of operation, or allowing them to do yet more abilities not available to them while they are third-party modules.

All in all, it's something we can hope for over the next five years...

<< First | < Prev

3 comments:

  1. Of all the 2020 advent calendars, this was the best and most interesting. Thank you, and well done.

    ReplyDelete
  2. Perl should do less by pushing more stuff into CPAN while becoming more orthoganal so that more can be achieved with fewer concepts. The concepts that you have so ably delineated are all doable in the existing language and so add to the amount that has to be learned to be a "Perl" programmer, but do not seem to create any new capability that distinguishes Perl from any other other languages - rather the opposite - pretty soon Perl will be just like Java and as annoying to code in.

    ReplyDelete