Archive
Callbacks and Perl 6
use v6; #source http://gist.github.com/544153
It’s quite tempting to make assumptions about Perl 6 because it resembles constructs from other languages. Most of those assumptions are wrong and as a result spawn anger and mischief.
There are mind traps one might step in. One of those traps are the assumption that a class and the resulting object are the same thing. That might be true for simple low level languages like c++ and Java (kekeke :), for Perl 6 it isn’t. That’s not much of a problem until you try to do something that’s supposed to be quite simple in a dynamic language.
A Callback
class A { our method m1(){} # our is needed to see ::m1 outside of the class } my $o = A.new(); say (&A::m1).WHAT; #>Method()
Perl6 is a dynamic language and there is no reason why there shouldn’t be another object system (or better a way to create P6 Objects) that does not use classes. We need to ask the object or it’s meta object for a reference to a method (kind of, I will explain later).
#say $o.^can('m1').WHAT; #rakudo does not support .WHAT in P6Invocation, if it would it would say #>P6Invocation() #say 'yes' if &A::m1 ~~ $o.can('m1'); #rakudo does not support .ACCEPTS in P6Invocation
Nontheless it wont be the same anyways because .can
will return an iterator over all methods that match that name. On top of that there are multi methods and a few other tarpits.
But what would work?
class Sender { has @.callbacks; # takes 3 items at a time: Any $obj, Str $name, Code $func multi method add_callback($o, Str $name){ @.callbacks.push($o, $name, $o.can($name)); return self; } method run_callbacks(){ # This fellow will make a difference of (object, method) paires and simple # functions. We call them with different parameters to make sure we can tell # the difference and to show off MMD. for @.callbacks -> $o, $name, $f { if $o { $o.$f("foo"); } else { $f(4, 2); } } return self; } multi method add_callback(Code $f){ @.callbacks.push(Any, Any, $f); } }
Now the mean part. We have an object that wants to be called back and got two functions that might be up for the task, depending on their parameters.
class Rcpt { has $.name = die 'I need a name.'; multi method callback1($p1){ say "callback1 of $.name has to say: $p1"; } multi method callback1(Int $i, Int $j){ say '$i * $j = ' ~ $i * $j; } } my $sender = Sender.new; my $rcpt1 = Rcpt.new(:name('rcpt1')); $sender.add_callback($rcpt1, 'callback1');
Another trap comes from somebody coming along and starting to poke around in Rcpt
. Extending classes is NYI in Rakudo, so we are not really doing it right here.
use MONKEY_TYPING; #supersede class Rcpt { # method callback1($p1){ # say "callback1 of $.name would have to say: $p1"; # } #} # #$sender.run_callbacks();
The question is, what function would be called as a method of $rcpt1
. The old one or the new one. Should we call .^can
before we call the function? It might even be a case of “Just don’t do it.”.
#use AllYourClassesAreBelongToMe;
Did something happen to Rcpt while loading that package? Maybe.
We can get around .^can
alltogether by forming a closure, what most dynamic savy folk would have done anyways. Making sure the right arguments land in the right spot.
$sender.add_callback(-> *@args {$rcpt1.callback1(|@args)}); # There are now two callbacks registered. One as a object/function pair and # another as a simple function with no bound object. The binding happends # in a closure that passes it's arguments on as they come in. $sender.run_callbacks(); #>callback1 of rcpt1 has to say: foo #>$i * $j = 8 #adding a multi NYI in Rakudo #augment class Rcpt { # multi method callback1(Str $s1, Str $s2){ # what_ever($s1, $s2); # } #}
Even after fooling around with Rcpt
and adding another candidate for callback1
, Rakudo would call the right method if supplied with two strings. Late binding and signatures working hand in hand to do what we mean. Perl 6 is very perlish if you ask me. :)
An Iterator that knows it’s previous and next value
We basicly need a prefetching iterator. Building any iterator is quite easy with gather/take.
use v6; my @a = 1..20; sub prev_iterator_next (@list){ my ($prev, $current, $next); my @foo = @list; $current = @foo.shift; $next = @foo.shift; gather loop { take ($prev, $current, $next); $prev = $current; $current = $next; $next = @foo.shift; take ($prev, $next, Any) if !@foo; last if !@foo; } } for prev_iterator_next(@a) -> $prev, $curr, $next { say ($prev, $curr, $next).perl; }
Rakudo doesn’t implement proper iterators yet, so we have to retreat to build a copy of the list and shift one element at a time of the beginning. It’s not overly fast that way ofc.