Recently I have been adding more and more functionality into emacs using comint. And as there are more and better perl libraries for hooking into other systems (such as DBI, SOAP, HTTP…), I often start with a script that reads commands from stdin and writes the result to stdout. That is the comint way.
use strict; use warnings; use 5.010; init(); # -- some initialization that takes a while # ... while (defined(my $command = <STDIN>)) { chomp $command; given (lc($command)) { when (/^\s*exit\s*$/) { exit 0; } when (/^\s*send\s*$/) { say 'send' } when (/^\s*receive\s*$/) { say 'receive' } default { say "Error: unrecognized command $command"; } } }
If initialisation takes a long time, it is a big saving to only do it once for many commands.
As emacs has full control over the process, I can control what gets sent and filter what is received before display. However, I prefer to be a little bit flexible with what the perl process receives for when I am testing it from the command line. That is why I allow surrounding spaces.
when (/^\s*exit\s*$/) { ... } when (/^\s*send\s*$/) { ... } when (/^\s*receive\s*$/) { ... }
Those matching regexes look pretty similar. I almost feel like I’m violating DRY.
Unfortunately, the subroutine references only take a single parameter.
sub cexit { return $_[0] =~ /^\s*exit\s*$/ } sub csend { return $_[0] =~ /^\s*send\s*$/ } sub creceive { return $_[0] =~ /^\s*receive\s*$/ } # ... when (\&cexit) { exit 0; } when (\&csend) { say 'send' } when (\&creceive) { say 'receive' }
But there is a way of storing data along with a subroutine – a closure. However, the following strangely doesn’t work.
sub match_command { my $command = shift; return sub { my $input = shift; say "Input is [$input]"; return $input =~ /^\s*$command\s*$/; } } while (defined(my $command = <STDIN>)) { chomp $command; given (lc($command)) { when (match_command('send')) { say 'send' } when (match_command('exit')) { exit 0; } when (match_command('receive')) { say 'receive' } default { say "Error: unrecognized command $command"; } } }
Whereas storing the closures in a variable before smart matching does work. Unfortunately it looks like smart matching isn’t smart enough for my needs.
my $send = match_command('send'); my $exit = match_command('exit'); my $receive = match_command('receive'); # ... when ($send) { say 'send' } when ($exit) { exit 0; } when ($receive) { say 'receive' }
And the result:
c:\home\juang>perl t.pl send Input is [send] send exit Input is [exit]
I’ve never been quite happy with ~~ and given/when in 5.10. Until then, everything in Perl was operator-centric – the operator picked the types, the domain of operation; e.g. == vs eq; and the values were inspected under whatever domain was deemed appropriate for that operator.
Suddenly, ~~ inspects the values of its arguments for their supposed-”type”, which is a rather loosely defined concept, because until 5.10, it wasn’t really necessary.
This isn’t due to something lacking in smart matching, but because of the behavior of the when statement.
If you explicitly do a smart match, such as
when ($_ ~~ match_command(‘send’)) { say ‘send’ }
it will work how you wanted.
The documentation for when states a number of cases where its parameter will not be used for a smart match.
Hi Paul,
I’ve been quite happy with my smart matching outside given/when. I often use $var ~~ [qw(x1 x2 x3 ...)] to match a number of possibilities where before I would have used a regex $var =~ /^x1|x2|x3$/
I don’t like its type checking though.
—-
Hi Graham,
Thanks for the work around. I read through the documentation again but I still couldn’t see where it said that a closure wouldn’t work correctly in a case statement.
In particular, this section confused me:
although I now see that $foo isn’t meant to stand in for anything (including closures, lists, etc.) but represents a simple scalar.
The smart matching or given/when of Perl 5.10 was considered defective and changed for 5.12. Have you tried using 5.12 instead and has that improved things for you?
Speaking more generally, I think that 5.10.x should be avoided entirely and people should just use either 5.8.x or 5.12.x.
Jared:
The issue is that the subroutine call is treated like it is equivalent to:
if (match_command(‘send’)) {
rather than:
if ($_ ~~ match_command(‘send’)) {
Immediately after the section you quoted it lists the cases that are treated differently and not processed as smart matches, with subroutine calls being the first one.
You can also force it to use smart matching by making the when statement have the actual closure reference in it instead of just the sub call:
when (\) {
Darren:
This part of the behavior didn’t change between 5.10 and 5.12.
I’d do a trim first.
trim($str);
.....
when ('exit') { ... }
when ('send') { ... }
when ('receive') { ... }
@Darren – 5.12 is still a bit too new for me (I prefer to avoid .0 releases), not to mention the fact that it hasn’t been approved for use at my firm. And 5.10 gives me a lot that I like over 5.8. Are there reasons other than the given/when syntax to avoid it?
@Graham – yes, I saw that subroutines were listed as different but for some reason I didn’t equate that with closures. Thanks again for the advice on fixing this.
and…
@Alexandr – man oh man. Yes, trim first. That is, I think, the way I should have been doing it. Great suggestion!