Friday, May 25, 2007

Perl 6: Round 2

Pugs revision: r16525

In the last article, I discussed some of the basic tenets of Perl 6. Continuing from here, we will go onto using subroutines and other dilly dallying.

Subroutines have taken a few good changes in Perl 6. There are also things that have been left alone, as we shall see. Let's go ahead and start:

[altair@stormwind diveintoperl6]$ pugs

pugs> sub abcd {
....> True;
....> }
pugs> if (abcd() == True) { say 'yes!'; }

Too trivial, but an example nonetheless (well, it at least shows that the last expression evaluated in a sub is the return by default; but I hope you caught that.)

Before going on, I'll take this time to point out a couple of different ways you can define subroutines. The first and most apparent, is a named subroutine:

pugs> sub cool ($a,$b,$c) {
....> $a ~ $b ~ $c;
....> }
pugs> (cool "ab","cd","ef").say;

You may also specify the return type in the parameter (remember the type system?) The above could also have been started with:

sub cool ($a,$b,$c --> String)

It's worth noting if you do not define parameters with the read-write trait explicitly (is rw attached to the particular parameter), they will be readonly aliases, i.e. immutable in the context of your sub. Also, if your sub declaration says it takes no parameters, but ones are passed anyway, they are passed in @_:
pugs> sub a {
....> @_.join(' ');
....> }
pugs> a("hi","there","guy")
"hi there guy"
You may also define sub's anonymously, by simply saying 'sub' without a name:
pugs> sub {
....> say "hi";
....> }
pugs> my $tea = sub ($a,$b) { $a ~ $b; }
pugs> $tea("a","b")

The third way is to use 'pointy-blocks.' These are almost synonymous with an anonymous sub declaration, but you can't have traits, and you don't need parenthesis for a parameter list:

pugs> my $s = -> $v { $v**2; }
pugs> $s(10)
pugs> my $t = -> $d,$e,$f { $f ~ $e ~ $d; }
pugs> $t('x','y','z')

Now that that is out of the way, let's continue (tip: the ~ operator is Perl 6's concatenation operator. Yes, that's really it.)

Perl 6 subroutines can, of course, take parameters. The way they take them ideally is pretty similar to that of perl 5, i.e.:
pugs> sub a($a,$b) {
....> ($a,$b).join(' ').say;
....> }
pugs> a("asdf","aoeu");
asdf aoeu
pugs> sub b {
....> @_.join(' ').say;
....> }
pugs> b("a","b","c","d");
a b c d

And so on and so on. Now, let's continue with some more interesting parts of parameters in Perl 6.

Optional parameters
Generally you will pass your arguments in order to the function; after these ordered, required arguments you may specify several other types of parameters, optional parameters are one of these. Optional parameters simply appear after all required arguments and are post-fixed with the ? symbol. Example:

pugs> sub opt($a,$b,$c?,$d?) {
....> say "a & b are:\t"~ ($a,$b).join(' ');
....> say "c & d are:\t"~ ($c,$d).join(' ');
....> }
pugs> opt("my","name","is","austin");
a & b are: my name
c & d are: is austin
pugs> opt("my","name","is");
a & b are: my name
c & d are: is
pugs> opt("my","name");
a & b are: my name
c & d are:
pugs> opt("my");
*** No compatible multi variant found: "&opt"
at line 1, column 1-10

You may also give these optional parameters default values, such as:

pugs> sub opt2($a,$b? = "seipp") {
....> }
pugs> opt2("austin","seipp").say;
austin seipp
pugs> opt2("austin").say;
austin seipp

Named Parameters
You can also explicitly associate your parameters with a name, by prefixing it with :. This forces the parameter you mark in question to be passed with a name, rather than being passed by position. Example:
pugs> sub named($a,:$b) {
....> say $a;
....> say $b;
....> }
pugs> named "a", b => "str";
pugs> named b => "str","a";
Named parameters are always optional, and they must come after optional parameters marked with ?.
Note: you may also do a key-value pair in the form of :key(value); such as:

pugs> named :b('str'),"a";
This option syntax is purely for pleasure, of course.

Variadic Parameters
A variadic parameter in a subroutine declaration allows the sub to take [extra] arguments and roll them up into an array. Using variadic parameters, this Perl 6 subroutine stub:

sub test(Int $a,Int $b,*@rest) { ... }

And this C prototype:

int test(int a, int b, ...);

Are the same semantically; the extra parameters are rolled into @rest in this case. Example:

pugs> sub test($a,$b,*@rest) {
....> say "a & b:\t" ~ ($a,$b).join(' ');
....> say "rest:\t" ~ @rest.join(' ');
....> }
pugs> test("austin","brian","chris","david");
a & b: austin brian
rest: chris david
pugs> test("austin","brian","chris");
a & b: austin brian
rest: chris
pugs> test("austin","brian");
a & b: austin brian

On the note of variadic parameters, you can also do the inverse by flattening an array and passing it to a function, element for parameter, ex:

pugs> sub test($a,$b) { $a ~ " " ~ $b; }
pugs> my @a = <cool refrigerator>;
pugs> test(|@a).say;
cool refrigerator

Multi-subs and signatures
In Perl 6, the parameters an argument accepts defines what is called the subroutine signature. Signature's are your function differentiator when you call a sub that is defined twice, over two different sets of parameters. This difference is based on the type annotations you provide in the signature. Multi-subs allow you to basically overload functions in the classic sense; you use the multi keyword on a sub to state it will be defined more than once with different signatures, and when you call it, the appropriate sub will be called based on signatures.

That's a lot of talk, here's an example of what I mean:

pugs> multi multiples (Int $i) {
....> say "int:\t"~$i;
....> }
pugs> multi multiples (String $s) {
....> say "str:\t"~$s;
....> }
pugs> my Int $a = 1;
pugs> my String $b = "asdf";
pugs> multiples($a);
int: 1
pugs> multiples($b);
str: asdf
pugs> multiples(1);
int: 1
pugs> multiples("asdf");
str: asdf
Currying a function allows you to take a function with multiple parameters and reduce it to a function with one parameter (essentially.) This allows you to curry multi-argument subs and use them in operations like map, where the function is expected to take one argument. Example:

pugs> sub curryer($one,$two) {
....> $one**$two;
....> }
pugs> curryer(3,2)
pugs> my $c = &curryer.assuming(two => 2);
pugs> $c(5)
In this case, $c would be a function that simply took numbers to the power of 2.
Currying is pretty useful as it allows us to define a sub that may take multiple parameters, but map it over a list of values sequentially as if it only took one, i.e.:

pugs> sub tester($first,$second,$third) {
....> say "first = " ~ $first;
....> say "second = " ~ $second;
....> say "third = " ~ $third;
....> }
pugs> map &tester.assuming(second => 5, third => "asdf"), 1..5;
first = 1
second = 5
third = asdf
first = 2
second = 5
third = asdf
first = 3
second = 5
third = asdf
first = 4
second = 5
third = asdf
first = 5
second = 5
third = asdf
Nifty, I know.

Wrapped Subroutines
Aside from currying, it is possible to have fun with subroutines in another manner: by defining a function wrapper that is executed when sub is called. Wrappers basically allow you to write fairly polymorphic/generic functions, and wrap them with a specific functionality using full Perl 6. It's important to note though, that this wrap actually modifies the function itself; it doesn't return you an anonymous function in a Scalar or somesuch (we'll get to this in a sec.)

All Routine's have a .wrap method. It expects one argument of type Code (a block of code, essentially.) This parameter may have the special expressions callsame,callwith, nextsame and nextwith within it. These all call the original Routine, but you may specify different parameters, etc..

pugs> sub wrapexample($a) {
....> say 'in middle; value passed == '~$a;
....> }
pugs> wrapexample "asdf";
in middle; value passed == asdf
pugs> $h = &wrapexample.wrap({say 'beginning'; callwith(42); say 'end';});
pugs> wrapexample "asdf";
in middle; value passed == 42
The id returned by a .wrap method can later be used to .unwrap the method. A call to .wrap replaces the previous routine definition with what you supply as your Code block, and you may have multiple wraps. Therefore, it's wise to get the return value of .wrap so you may unwrap it later:
pugs> &wrapexample.unwrap($h);
pugs> wrapexample "asdf";
in middle; value passed == asdf
(addendum: so far, .wrap doesn't work like above on pugs r16525; ergo, the above is fabricated, but accurate. It is however documented in S06 and will most likely be implemented sometime in the near future [hopefully.])

Well, in this article I roughly covered subroutines in Perl 6. There're quite a few changes, so you need to adjust to them; none are too daring to accept, however. Next round, I think we'll cover Object-orientation in Perl 6, before moving to Grammars and rules.


Dave Rolsky said...

It's tenets, not tenants. Perl 6 is not a building full of people.

riffraff said...

I think a clarification is needed in the text: "if you do not define parameters explicitly, they will be given as read-only (just copies, not a reference) in @_:" shouuld be "if you don't define them as read-write". See S06.

In current pugs though, it will work partly:
sub f($a) {$a++} # raise an error
sub f($a is rw) {$a++} #works as expected
sub f(@a) {@a++} # does side effect but should not, I believe

Ben said...

Should definitely think about covering macros too at some point. I played around with them on Pugs a few months ago and they were working then so I'm sure they're suitable material for this series.

austin seipp said...

@ben: It's on the to-do list, so don't worry. :)

Right now I'm thinking of an interlude however, to look at more functional examples of Perl 6. But Macro's are going to be covered at one point or another.

Anonymous said...

He гuns succesѕful tеchnology company Οutѕouгcery
buѕіnеss which sold part of its ѕtratеgic hardware.

Visіt my ωebpage -