Home > Perl6 > Signals and Slots like the pros

Signals and Slots like the pros

The Qt is quite famous for good reason. With it’s convenient signal and slot implementation it sneaks late binding into c++ in addition to some fine callback features.

We can do the same in Perl6.

Lets say Peter has witten some GUI elements that may look like the following.

use Signal;

class Input {
  has Str $.text is rw;
  our method sig_changed(Str $s) is signal {};
  our method sig_keyup is signal {};

  # To make this example do something I have to simulate the event loop 
  # of a GUI toolkit. 
  method fake_event_handler() {
    $.text = (1..10).pick(5).join;
    $.sig_changed($.text); # Input calls it's own signal to 
                           # indicate that the text has changed
  }
}

class Button {
  has Str $.text is rw;
  our method sig_pressed is signal {};
  our method sig_lowered is signal {};
  our method sig_raised is signal {};

  # We simulate that the OK button was clicked
  method fake_event_handler() { self.sig_pressed } 
}

Paul made us a class that can handle Persons.

class Person {
  has Str $.name is rw;
  has Int $.age is rw;
  has Int $.sexiness is rw;

  our method set_name($name){ $.name = $name }
  our method set_age($age){ $.age = $age.Int }
  our method set_sexiness($sexiness) { $.sexiness = $sexiness.Int }
  our method foo(){};
}

Now Marry comes along and is asked to stick the two together without subclassing or changing any of code. Luckily Peter was foresightful and added use Signal; right at the top of his code. That allowed him to add is signal to an empty method (that does nothing indeed).

Lets see what Marry does with that.

class PersonDialog {
  has Person $.person;
  has Input $.name;
  has Input $.age;
  has Input $.sexiness;
  has Button $.ok;

  method new(){
    my Person $person .= new,
    my Input $name .= new,
    my Input $age .= new,
    my Input $sexiness .= new,
    my Button $ok .= new(:text('OK'));

    my $self = self.bless(*, person => $person, name => $name, age => $age, 
                             sexineess => $sexiness, ok => $ok);

Nothing special so far. Marry sticks some GUI elements together and sets a name of a button.

    connect($name, &Input::sig_changed, $person, &Person::set_name);
    connect($age, &Input::sig_changed, $person, &Person::set_age);
    connect($sexiness, &Input::sig_changed, $person, &Person::set_sexiness);
    connect($ok, &Button::sig_pressed, $self, &PersonDialog::finished);

Oi! Magic!

    return $self;
  }

  our method finished() {
    # this is getting called when $ok is pressed, validation of input goes here
    die "ohh no!" unless $.person.name && $.person.age && $.person.sexiness;
      say "Person name: {$.person.name}, age: {$.person.age}, sexiness: {$.person.sexiness}";
  }

Even more magic! See below.

}

my PersonDialog $pd .= new;

# lets call all those fake event handlers to simulate frenetic typing and clicking
.fake_event_handler for $pd.name, $pd.age, $pd.sexiness, $pd.ok; # Perl 6 is so cool. :)

Marry quickly connects some empty methods of Peter’s classes with Paul’s Person class. How does that work? Let’s have a look at some simplified code (the real thing is linked at the bottom). It all starts with a role.

role Signal {
  has @.slots; # a nice list of callbacks

  multi method connect(Routine $r){
    @.slots.push([Mu, self, Mu, $r]);
  }

  method disconnect(Routine $method){
    my $offset = 0;
    for @.slots -> $slot {
      @.slots.splice($offset, 1) if $slot == [Mu, self, Mu, $method]
      $offset++;
    }
  }
}

Nothing to fancy here. We have a list to hold a full row of callbacks and two methods to add and remove callbacks from that list.

multi trait_mod:<is>(Routine $_signal, :$signal!){
  $_signal does Signal;

  $_signal.slots; # WORKAROUND RT112666

  $_signal.wrap(sub (*@args) {
    for $_signal.slots -> [$sender, $signal, $receiver, $slot] {
      $slot(|@args);
    }
  });
}

And here is the magic. What we do is to take a Routine (sub or pointy block) and apply a trait to it, like so.

sub my_signal is signal() { #` this wont be called` }

That calls traid_mod:<is>, which does a mixin of the Routine with Signal and creates a wrapper around my_signal that loops over all connected slots and calls them one after another. If there are arguments to the signal call, they are passed on. Thanks to the mixin we can connect our signal now to as many subs as we want.

sub some_sub_a() { say "a" };
sub some_sub_b() { say "b" };

&f.connect(&some_sub_a);
&f.connect(&some_sub_b);

f();

&f.disconnct(&some_sub_b);

The last line will call the two subs one after another. There can be as many callbacks per signal as rakudo can manage and there is no need to fiddle with handles when it comes to remove a callback from a signal. Any signal can be made fully introspective and as soon as &f.signature ~~ &some_sub_a.signature works type checks can be done when a signal is connected.

So why is this cool. Have a look back into Peter’s code in the fake_event_handler. Peter is calling a signal in the last line of that method. That doesn’t do anything until Marry comes along and connects it to Paul’s Person class. Our three friends don’t have to talk to each other to make that happen, thanks to the foresighted Paul who decided to leave some methods empty. There is no subclassing required (quite in contrast to the Qt) to make that work in Perl 6 what makes interfaces a little less fragile.

The whole thing can be found here and will end up on github as a module after I added return value handling and some more testing. With some Perl 6 magic that is NYI the syntax should be able to be simplified to something like.

connect(&$input.sig_changed, &$data.set_age);

So one does not has to type the same stuff twice.

Advertisements
Categories: Perl6
  1. No comments yet.
  1. No trackbacks yet.

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: