Beefy Boxes and Bandwidth Generously Provided by pair Networks
There's more than one way to do things
 
PerlMonks  

Poor Man's Perl6 Exegesis (you get what you pay for)

by Ovid (Cardinal)
on Jan 24, 2002 at 06:21 UTC ( [id://141113]=perlmeditation: print w/replies, xml ) Need Help??

I've been trying to keep up with the latest news on Perl6 and I though it would be a good idea to go through some of the code and try to figure it out. The following is similar to TheDamian's Exegeses. The major difference is that he is much smarter than I and he's kept up with Perl6. Further, I don't have all the answers and I have a heck of a lot of questions.

The following is BrentDax's first run at creating the Perl6 Exporter module. He states that he typed the code directly into his mail program, so no fair holding anything against him! Also, this is being posted in meditations rather than SOPW because this really isn't about Perl (yet).

What follows is merely my attempt at understanding what's happening with the new Perl. Read the link above for the original Perl6 Exporter code. This node was getting long enough without my posting that code here.

module Exporter;

From Apocalypse 1, if Perl6 sees a module or class declaration, it knows that it is Perl6 code. A package declaration means that we're dealing with Perl5 code.

use strict; use warnings; use warnings::register; #assuming this continues to exist...

This is pretty standard. Interestingly, the use strict might be superfluous here. According to the discussion regarding "lexical variables as default" in Apocalypse 4, use strict 'vars' will be the default in modules and classes. With Exporter, we're going to be turning off strict refs anyway, and the major effect of strict 'subs' is to inhibit Perl poetry ;) (please note that a semi-colon followed by a right paren is a 'wink'. That means it's a joke. Don't flame me ;) <-- see, that's another one ;)

sub import($pkg : ARRAY $export=undef, ARRAY $ok=undef, HASH $tags=und +ef) {

Hoo boy, here's where I get bogged down. It looks to me like BrentDax is saying that we have an argument, $pkg, that has three adverbs. The first argument is the package of the module that inherits from Exporter, but what are the rest of the arugments? These are scalars that are references to particular types of data structures. Thus, when we see this:

ARRAY $export=undef

It should mean that we have a scalar, that contains and array reference. Now, I'm not sure what the undef if for. I think this is to establish a default, which means that this is (I think) a typo. If so, it should read:

ARRAY $export //= undef

I don't think that interpretation is correct, however, as he properly uses the default operator in other places and thus understands its use.

Now, as for how this all fits together, I'm not sure. The colon operator appears to be explained in Exegesis 3, page 6, under the heading "The ∑ of all our fears". I read and read and reread that section, but couldn't figure out what that meant in relation to the arg list in import(). Could someone please enlighten me?

my $from_pkg=caller.package;

Okay, that's pretty nifty. package is an attribute of caller (or is it a method that returns a value?). This is much easier to read then caller(0). I wonder, though, if we can adjust this to allow us to export to a lexical scope. There's been a fair amount of talk about a MY pseudo-package and having this accessible to caller. This could allow us to export something to a limited scope and not screw with the caller's symbol table! The following code is completely speculative, of course.

my $from_pag = caller.MY.package;

I have no idea what the actual syntax will be, but I wonder if some mechanism will be in place for a particular scope to protect the MY pseudo-package. It wouldn't be a lot of fun to call a poorly written function and have it, uh, barf all over your lexicals.

pkg_alias($from_pkg, "Exporter::From"); &Exporter::From::import := &myimport;

This is moderately straightforward. The pkg_alias() function takes two packages and makes the first and alais of the second. The next line is interesting, though. We are now aliasing (explained below) the myimport() function to the import() function, which is therefore aliased to whatever the calling packages import() function would be (I think). Since we have chained all of these together, doesn't this overwrite the calling packages import function, if any? On the other hand, why would we have an import function and then subclass Exporter? I suppose we could do this for some "special cases" that we might want to trap and then call Exporter::import (and assuming we can use something like goto to trick the call stack).

The only thing I'm wondering about is whether or not beginning a function name with an ampersand and leaving off the parens will result in @_ being passed through (and aliased).

Here's how the aliasing is accomplished. Let's jump ahead, though, and take a look at the pkg_alias function:

sub pkg_alias($original, $new) { #Any sufficiently encapsulated hack... no strict 'refs'; %{"${original}::"} := %{"${new}::"}; #XXX will this actually wo +rk? }

This looks fairly straightforward: alias one symbol table to another. Any subsequent changes made to one will affect the other. Aack! That looks like it's begging for trouble. Of course, now that we have lexical subs, we can have nice, tidy namespaces and this may not be as much of a concern.

:= This is the binding operator. It creates an alias from the rvalue to the lvalue. It's now used where we would have used typeglobs. The example above appears to be intended to alias the symbol table.

Now, back to our regularly scheduled program. Rather than finish &import, we need to see what's happening in &myimport, or else the rest of the import function will not make any sense.

sub myimport($exp_from : *@symbols=() : *@options=()) {

Aack! What the heck is that? The asterisk flattens the the arguments to a list and allows the array to slurp up the rest of the arguments, but I don't get this. Once again, any explanation would be helpful.

my $exp_to=caller.package; #is this how caller is used? pkg_alias($exp_from, "Exporter::From"); pkg_alias($exp_to, "Exporter::To"); #defaults @symbols //= @Exporter::From::EXPORT;

Nothing really interesting there, except for the default operator. As you might recall, Perl5 programmers would do something like this:

$foo ||= 'bar';

That has problems, though, if $foo has a valid value that evaluates as false (such as zero). Perl6's default operator says "if this value ain't defined", define with the rvalue. You can even chain these values:

$foo //= $bar // $baz // 'nothing to see here, folks';

Now things start to get strange again.

#expand tags to their values @symbols=map { /^:/ ?? @{%Exporter::From::EXPORT_TAGS}{$_}} :: $_ } @symbols;

The single colon now has a lot of work to do, so the ternary operator (?:) is ambiguous. This will be corrected by changing the ternary operator to ??::. TheDamian argues that this is actually a better fit as the ternary operator short-circuits, just like && and ||. I understand what he's saying, but this just looks weird to me. Of course, a lot of Perl6 looks weird to me, so I should bite my tongue.

Other than that, this is fairly clear. We map the @symbols array with the original value, if it doesn't start with a colon, or we grab the array of tags from the appropraite EXPORT_TAGS list.

for(@symbols) { #handle version numbers if(/^[\d.]+$/) { $exp_from.require_version($_); next; }

Perl6 is supposed to allow for different module versions to be supported in the same program. So, if one of our symbols is just a number, then we know the module version that we're exporting from.

#handle renamed exports my($to, $from)=$_.ref eq 'PAIR' ? ($_.left, $_.right) : ($_, $ +_);

I think we have a typo here. The ternary operator should be ??::.

At this point, since we're iterating over the symbols, we check to see if this symbol is a pair. If so, $to and $from are set the the left and right values (the key and the value?) of the pair, respectively. Otherwise, they are set to the current symbol.

for($to, $from) { #make sure it has some sort of sigil $_='&' _ $_ unless m'^[$@%&]'; }

If it's a bareword, they wanted to export a function, go prepend an ampersand. (?)

warnings::warnif("$from exported from $exp_from conflicts with + existing $to at $(caller.file) line $(caller.line).\n") if defined %Exporter::To::{$to};

This is nice. We get a very clear error message if we're about to export over something that already exists. Also, note the interpolation of the object and it's method (attribute?): $(caller.file). This sort of DWIM interpolation should make life simpler in the future.

<ocde> die("$exp_from doesn't export $from at $(caller.file) line $(caller.line).\n") unless grep {/^$from$/} @Exporter::From::EXPORT, @Exporter::From::EXPORT_OK; </code>

Whoops! We tried to ask for something that isn't exported.

%Exporter::To::{$to}=%Exporter::From::{$from}; }

This is the actual export. Note that we're still in the symbol for loop.

for(@options) { my($sign, $name, $value)=/[+-]([[\w]&&[^\d]]\w+)(?:=(.*))?/s; my $targ := ${$Exporter::From::{$name}}; given($sign) { when('+'): { if(defined $value) { $targ=$value; } else { $targ is true; } } when('-') { if($targ.props{true}) { $targ is false; } else { undef $targ; } } } } }

I'll confess, I don't know what's going on here. I need to get home (well, actually, head to the pool league), so I need to cut this short. Rather than puzzle through this, I'll be lazy and say "If you understand this, please tell me".

And to finish off the &import function:

if(defined $export) { @Exporter::From::EXPORT=@$export; } if(defined $ok) { @Exporter::From::EXPORT_OK=@$ok; } if(defined $tags) { %Exporter::From::EXPORT_TAGS=%$tags; } }

Now, as I understand it, dereferencing will be a DWIM sort of thing. Thus, the type sigils on the right are not necessary.

if(defined $export) { @Exporter::From::EXPORT = $export; } if(defined $ok) { @Exporter::From::EXPORT_OK = $ok; } if(defined $tags) { %Exporter::From::EXPORT_TAGS = $tags; } }

There's no need to dereference anymore (except in unusual cases where the code is ambiguous) as Perl6 will do it for us.

Well, that's about it. I need to run for the evening and I can't dig into this further, but I look forward to seeing any comments or thoughts people have. I'll also let you know how I did on my first night on a pool league :)

Cheers,
Ovid

Update: I won the match :)

Join the Perlmonks Setiathome Group or just click on the the link and check out our stats.

Replies are listed 'Best First'.
Re: Poor Man's Perl6 Exegesis (you get what you pay for)
by theorbtwo (Prior) on Jan 24, 2002 at 07:12 UTC

    Warning: I am not TheDamian, nor lwall.

    As to the parameter list on the sub import(...) { line, that's from RFC 128 (nice number, no?), with some Larifications. ($pkg : ARRAY $export=undef, ARRAY $ok=undef, HASH $tags=undef) gets a bit confusing. I read it like this. First, we've got $pkg, which is untyped. I think the : is a typo for ;, meaning "end of required parameters". Then follows $export, an arrayref which defaults to being undef, $ok, another arrayref with the same default, and $tags, a hash that still defaults to undef. If I'm worng, and that : really is a :, then that means that $pkg does indeed have three attributes... but those would be parameter list attributes, and we know what all of those are, and they don't look like that. Perhaps with Larry's adverbial :, those are suposted to be definitions of adverbial variables.

    Next, we've got my $from_pkg=caller.package;, and you're wondering if that's an attribute of the value that caller returns, or if it's a method on the object that it returns. Well, it doesn't really matter is the answer. An attribute looks an awful lot like a method call. In this case, it's probably an attribute, but the difference in calling convention is nil. An attribute is essentily an lvalueable sub that you never had to write, or a hashref that's magicly a part of a value.

    OK, time for me to catch my bus. I'll proabably finish this later.

    TACCTGTTTGAGTGTAACAATCATTCGCTCGGTGTATCCATCTTTG ACACAATGAATCTTTGACTCGAACAATCGTTCGGTCGCTCCGACGC
Re: Poor Man's Perl6 Exegesis (you get what you pay for)
by TheDamian (Vicar) on Jan 27, 2002 at 18:00 UTC
    A few meta-comments:
    sub import($pkg : ARRAY $export=undef, ARRAY $ok=undef, HASH $tags=und +ef) {
    Now, I'm not sure what the undef if for. I think this is to establish a default, which means that this is (I think) a typo. If so, it should read:
    ARRAY $export //= undef
    That's right (as of Larry's last communication with me on the subject).
    Now, as for how this all fits together, I'm not sure. The colon operator appears to be explained in Exegesis 3, page 6, under the heading "The ∑ of all our fears". I read and read and reread that section, but couldn't figure out what that meant in relation to the arg list in import(). Could someone please enlighten me?
    No. But I'll try and answer your question. ;-)

    A colon in the parameter list of a subroutine means that a colon should appear in the same spot in the argument list with which the sub is invoked.

    Typically the colon will be after the first argument, which will then be treated as the "invocant" of the subroutine. But it may be possible to put a colon (or even several of them) at other points in the param list as well. In such cases, arguments appearing after the colon will be thought of as adverbs modifying the call, though it's not yet clear whether there will be language level consequences of that convention.

    my $from_pkg=caller.package;
    Okay, that's pretty nifty. package is an attribute of caller (or is it a method that returns a value?).
    Yes. ;-)

    In Perl 6 there is much less difference between an attribute and a method. Most attributes like this will have lvalue public accessor methods.

    This is much easier to read then caller(0). I wonder, though, if we can adjust this to allow us to export to a lexical scope. There's been a fair amount of talk about a MY pseudo-package and having this accessible to caller. This could allow us to export something to a limited scope and not screw with the caller's symbol table! The following code is completely speculative, of course.
    my $from_pag = caller.MY.package;
    Something like this is quite likely. Though it would only work at compile-time.
    The only thing I'm wondering about is whether or not beginning a function name with an ampersand and leaving off the parens will result in @_ being passed through (and aliased).
    No. That behaviour is gone from Perl 6.

    &subname is simply the fully-sigilled name of that subroutine. As an lvalue of a binding operator, it specifies the subroutine to be bound. As a scalar rvalue, it produces a reference to the subroutine in question.

    sub myimport($exp_from : *@symbols=() : *@options=()) {
    Aack! What the heck is that? The asterisk flattens the the arguments to a list and allows the array to slurp up the rest of the arguments, but I don't get this. Once again, any explanation would be helpful.
    This is an example of the multi-colon syntax I mentioned above. The code means:
    sub myimport($exp_from # expect a scalar as arg 1 : # then expect a colon *@symbols=() # then flatten any args : # until another colon *@options=() # then flatten any more args ) {
    BTW, the default values again need the operator (//=) here, rather than =. They're also redundant here (except as self-documentation, of course).
    Now, as I understand it, dereferencing will be a DWIM sort of thing. Thus, the type sigils on the right are not necessary.
    if(defined $export) { @Exporter::From::EXPORT = $export; } if(defined $ok) { @Exporter::From::EXPORT_OK = $ok; } if(defined $tags) { %Exporter::From::EXPORT_TAGS = $tags; } }
    There's no need to dereference anymore (except in unusual cases where the code is ambiguous) as Perl6 will do it for us.
    That's correct. The only issue is whether the first two cases above are ambiguous, since they already have a meaning ("set the rvalue array to a single element which is a copy of the lvalue") in Perl 5.

    Personally, I think that assignment of a scalar to an array will dwimically dereference an lvalue that's an array reference, but Larry will have to rule on that.

Re: Poor Man's Perl6 Exegesis (you get what you pay for)
by mikfire (Deacon) on Jan 24, 2002 at 21:09 UTC
    I will take a pass at this for loop:
    for(@options) { my($sign, $name, $value)=/[+-]([[\w]&&[^\d]]\w+)(?:=(.*))?/s; # this looks like a typo - there should be parens around # the [+-]? my $targ := ${$Exporter::From::{$name}}; given($sign) { when('+'): { if(defined $value) { $targ=$value; } else { $targ is true; } } when('-') { if($targ.props{true}) { $targ is false; } else { undef $targ; } } } }
    If I have understood what I have read ( which is a mighty large conditional ), the first two lines are pretty much identical to any perl5 code. The third line creates an alias between the $targ and the variable in Exporter::From symbol table ( which looks to be a really, really neat feature ).

    We then switch on the sign. If the sign is a '+' and a value is given, we simply assign the provided value to $targ. If no value is given, we assume it is a boolean flag and set $targ to be true. This is being done by an actual property associated with $targ, not by assigning some non-zero value. This is done, I assume, so we can easily distinguish those annoying "0 but true" cases.

    Similarly, if the sign is '-' we check the 'true' property. If that is set, we assume this is a boolean flag and set it false. Otherwise, we simply undef the value.

    Pretty straight forward, I think. But I really like the way perl6 is shaping up.

    Thanks for the write up Ovid.

    Mik
    mikfire

Re: Poor Man's Perl6 Exegesis (you get what you pay for)
by mikfire (Deacon) on Jan 24, 2002 at 23:45 UTC
    I am uncertain if I should update my previous node or not. As I am attacking a seperate portion of this, I believe I am correct in making a new node.

    sub myimport($exp_from : *@symbols=() : *@options=())
    What does this do? I have been over the related Apocalypse and Exegesis ( part 3 ) and I think the idea here is to allow myimport to be called with lists instead of arrays.

    According to TheDamian in Exegesis 3, page 1, the @ will cause perl6 to expect either an array or an array reference as the parameter. If we were to declare a perl6 sub like this:

    sub foo( @bar )
    and called it like this:
    &foo( "a", "b" );
    we would generate an error ( at compile time? ).

    If we really want foo to be called with a list, we need to put the flattening star in the sub definition:

    sub foo( *@bar )

    But, later on in the same tome, we are told that this will have the standard perl5 affect of slurping the remainder of the parameters.

    I am guessing this to be an error on BrentDax's part. I believe BrentDax was attempting to allow myimport to be called with lists, but forgot that the flattening star would behave exactly as we expect from perl5. If I have missed something, I would really appreciate being corrected.

    update: Changed a bit of wording in the last paragraph. Mik
    mikfire

      sub foo( @bar ){...} &foo( "a", "b" );

      As I read things, yes, this is an error. The flattening star will be necessary to slurp up the rest of the goodies. I suspect that this is going to be a source of bugs for many people switching to Perl6. Also, I suspect the following will trip people up:

      @*ARGS; # was @ARGV

      The star between the sigil and the variable name, I believe, is how we will now be referring to special globals. Thus, @*ARGS != @ARGS.

      Cheers,
      Ovid

      Join the Perlmonks Setiathome Group or just click on the the link and check out our stats.

        Not true. @*ARGS and @ARGS are the same thing, unless you define a new @ARGS in your scope. If that happens, you can still access the global @ARGS as @*ARGS.

        =cut
        --Brent Dax
        There is no sig.

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlmeditation [id://141113]
Approved by root
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others romping around the Monastery: (8)
As of 2024-03-28 09:53 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found