Holli
Holli

Reputation: 5072

how to create and export dynamic operators

I have some classes (and will need quite a few more) that look like this:

use Unit;

class Unit::Units::Ampere is Unit
{
  method TWEAK { with self {
    .si            = True;
                   #     m·  kg·   s·   A   ·K· mol·  cd
    .si-signature  = [   0,   0,   0,   1,   0,   0,   0 ];
    .singular-name = "ampere";
    .plural-name   = "ampere";
    .symbol        = "A";
  }}

  sub postfix:<A> ($value) returns Unit::Units::Ampere is looser(&prefix:<->) is export(:short) {
    return Unit::Units::Ampere.new( :$value );
  };

  sub postfix:<ampere> ($value) returns Unit::Units::Ampere is looser(&prefix:<->) is export(:long) {
    $value\A;
  };
}

I would like to be able to construct and export the custom operators dynamically at runtime. I know how to work with EXPORT, but how do I create a postfix operator on the fly?

Upvotes: 4

Views: 137

Answers (2)

Holli
Holli

Reputation: 5072

I ended up basically doing this:

sub EXPORT
{
    return %(
        "postfix:<A>" => sub is looser(&prefix:<->) {
            #do something
          }
    );
}

which is disturbingly simple.

Upvotes: 3

piojo
piojo

Reputation: 6723

For the first question, you can create dynamic subs by returning a sub from another. To accept only an Ampere parameter (where "Ampere" is chosen programmatically), use a type capture in the function signature:

sub make-combiner(Any:U ::Type $, &combine-logic) {
    return sub (Type $a, Type $b) {
        return combine-logic($a, $b);
    }
}

my &int-adder = make-combiner Int, {$^a + $^b};
say int-adder(1, 2);
my &list-adder = make-combiner List, {(|$^a, |$^b)};
say list-adder(<a b>, <c d>);
say list-adder(1, <c d>); # Constraint type check fails

Note that when I defined the inner sub, I had to put a space after the sub keyword, lest the compiler think I'm calling a function named "sub". (See the end of my answer for another way to do this.)

Now, on to the hard part: how to export one of these generated functions? The documentation for what is export really does is here: https://docs.perl6.org/language/modules.html#is_export

Half way down the page, they have an example of adding a function to the symbol table without being able to write is export at compile time. To get the above working, it needs to be in a separate file. To see an example of a programmatically determined name and programmatically determined logic, create the following MyModule.pm6:

unit module MyModule;

sub make-combiner(Any:U ::Type $, &combine-logic) {
    anon sub combiner(Type $a, Type $b) {
        return combine-logic($a, $b);
    }
}

my Str $name = 'int';
my $type = Int;
my package EXPORT::DEFAULT {
    OUR::{"&{$name}-eater"} := make-combiner $type, {$^a + $^b};
}

Invoke Perl 6:

perl6 -I. -MMyModule -e "say int-eater(4, 3);"

As hoped, the output is 7. Note that in this version, I used anon sub, which lets you name the "anonymous" generated function. I understand this is mainly useful for generating better stack traces.

All that said, I'm having trouble dynamically setting a postfix operator's precedence. I think you need to modify the Precedence role of the operator, or create it yourself instead of letting the compiler create it for you. This isn't documented.

Upvotes: 1

Related Questions