JavaScript bind() for Perl
JavaScript's bind()
doesn't exist in Perl. But it wouldn't be Perl if it wasn't possible to implement a perlish equivalent to JavaScript's bind()
in a completely transparent and idiomatic fashion with just a few lines of code.
In a previous blog post I had described --- for both JavaScript and Perl - how you can use closures as glue code if a callback API and the callback function or method do not fit together. The closure can be used to translate the function signatures. The same technique can be used to make a callback API call an instance method of an object instead of calling a plain old function.
The Perl solution looked like this:
#! /usr/bin/env perl
package Logger;
use strict;
sub new {
my ($class, $prefix) = @_;
bless \$prefix, $class;
}
sub logMessage {
my ($self, $msg) = @_;
my $now = localtime;
print STDERR "[$now][$$self]: $msg\n";
}
package main;
use strict;
sub run_service {
my ($logger) = @_;
$logger->("starting service");
# Do something.
$logger->("stopping service");
}
foreach my $service ("cruncher", "crasher") {
my $prefix = $service;
my $logger = Logger->new($prefix);
my $callback = sub {
my ($msg) = @_;
$logger->logMessage($msg);
};
run_service $callback;
}
The function run_service()
in line 24 is a stub microservice that is invoked with a logger function as a callback. That callback is invoked with just one argument, the message to log as a string.
But we want to use a Logger
object instead and have its logMessage()
method being called. The solution in lines 39-45 consists of passing a closure as the callback function and inject the logger object into the invocation context.
So far, so good. But JavaScript has the bind()
method defined for every function (object) allowing for a more idiomatic solution:
runService(logger.logMessage.bind(logger));
The method bind()
creates a new function with the JavaScript this
keyword set to the provided first argument with the given sequence of arguments preceding any provided when the new function is called.
How can the same be achieved in Perl? It is ridiculously simple:
package Logger;
use strict;
sub new {
my ($class, $prefix) = @_;
bless \$prefix, $class;
}
sub logMessage {
my ($self, $msg) = @_;
my $now = localtime;
print STDERR "[$now][$$self]: $msg\n";
}
package main;
use strict;
sub run_service {
my ($logger) = @_;
$logger->("starting service");
# Do something.
$logger->("stopping service");
}
foreach my $service ("cruncher", "crasher") {
my $prefix = $service;
my $logger = Logger->new($prefix);
run_service(Logger->bind(logMessage => $logger));
}
package UNIVERSAL;
sub bind {
my ($self, $method, $this, @args) = @_;
my $coderef = $self->can($method);
return sub {
my (@more_args) = @_;
return $coderef->($this, @args, @more_args);
}
}
The first line that has changed compared to the previous version is line 37:
run_service(Logger->bind(logMessage => $logger));
This is an idiomatic translation of the JavaScript version to Perl:
runService(logger.logMessage.bind(logger));
But how is bind()
magically defined in Perl now? Starting in line 40, the class UNIVERSAL
--- the mother of all Perl classes --- gets extended by a method bind()
.
Its first argument is the name of the method that should be bound to another object. In line 45 a reference to the method of that name is created (read the documentation of UNIVERSAL->can
in perldoc UNIVERSAL
if you don't understand why that works). By the way, this Perl bind()
can be invoked both as a class and an instance method because UNIVERSAL->can
can be invoked both as a class and an instance method. And you can put the code in any file you want. You should just not name it UNIVERSAL.pm
.
The rest is easy to understand with a minimal knowledge of closures:
The Perl bind()
--- just as its JavaScript counterpart --- creates and returns a new function, a closure where the instance --- called $self
in Perl and this
in Javascript --- is replaced with whatever you chose. And then the additional arguments from bind()
plus the arguments from the actual invocation are merged together and the function is invoked.
But this Perl bind()
is not exactly the same as the bind()
method in JavaScript. In JavaScript, every function is an object and you can invoke methods on it. In Perl, functions are references but you cannot invoke methods on plain references. You have to bless()
them first.
However, in JavaScript, this
is a keyword, when Perl's equivalent $self
is not. It is just the first argument that a method is called with and it can be named as you will. $self
is just a conventional name.
That being said, calling bind()
on regular Perl functions would not make a lot of sense. Well, does bind()
for Perl make any sense? No idea. Probably it is just code golf. Leave a comment if you have found a useful application for it.
Leave a comment