gregory-nisbet has asked for the wisdom of the Perl Monks concerning the following question:

I am, as an exercise, trying to write a function that mimics pattern matching in langauges like Haskell and OCaml in Perl. The following code runs (I'm using Perl 5.18) and produces the following result.
$ perl pattern_matching.pl Name "main::bob" used only once: possible typo at pattern_matching.pl +line 50. Use of uninitialized value $bob in print at pattern_matching.pl line 5 +0.
I think that's because the while loop
while (my ($k, $v) = each %$locals) { local ${$k} = $v; }
localizes each variable only within the body of the loop. I'm wondering what the right approach would be. Entire code below.
use strict; use warnings; sub same_keys { my ($left, $right) = @_; return unless ref $left eq 'HASH' and ref $right eq 'HASH'; return unless keys %$left == keys %$right; for my $key (keys %$left) { exists $right->{$key} or return; } return 1; } sub match ($$$) { no strict 'refs'; my ($lhs, $rhs, $fun) = @_; my $locals = {}; # populate locals match_inner($lhs, $rhs, $locals); while (my ($k, $v) = each %$locals) { local ${$k} = $v; } return $fun->(); } sub match_inner { my ($lhs, $rhs, $locals) = @_; if (ref $lhs eq '') { $locals->{$lhs} = $rhs; } elsif (ref $lhs eq 'ARRAY') { if (@$lhs == @$rhs) { for my $i (0 .. $#$lhs) { match_inner($lhs->[$i], $rhs->[$i], $locals); } } } elsif (ref $lhs eq 'HASH') { if (same_keys($lhs,$rhs)) { for my $k (keys %$lhs) { match_inner($lhs->{$k}, $rhs->{$k}, $locals); } } } } match "bob", 45, sub { no strict; print $bob; };

Replies are listed 'Best First'.
Re: rough approximation to pattern matching using local
by Athanasius (Archbishop) on Jan 25, 2015 at 06:51 UTC

    Hello gregory-nisbet,

    First, I don’t understand what you are trying to do. How does “pattern matching in languages like Haskell and OCaml” differ from Perl-style regular expressions work? Some explanation would be helpful here. At the very least, you should indicate the output you want to get from the call to match "bob", 45, sub { print $bob; }.

    Second, the warning messages are telling you that the package-global variable $bob is being used without having been first initialised or even declared. A search through your code shows that $bob occurs once only, in the print statement within the anonymous subroutine passed to sub match. There is also the string, "bob", passed in to that subroutine, so I’m guessing you expect the variable to be somehow created, from the passed-in string, within that sub? It isn’t, and I don’t know why you think the while loop localisation you identify is relevant here?

    Third, when you start sprinkling no strict 'refs' and no strict around your code to keep the compiler quiet, this is almost always a sign that your design is faulty.

    Fourth, the use of prototypes in sub match ($$$) is probably a bad idea. Please study

    Far More than Everything You've Ever Wanted to Know about Prototypes in Perl -- by Tom Christiansen

    and then restrict yourself to using prototypes in only those situations where the “implicit context coercion” (in Tom Christiansen’s phrase) which they provide is actually useful.

    Update: Amended sentence re “pattern matching”. Thanks to LanX, below.

    Hope that helps,

    Athanasius <°(((><contra mundum Iustus alius egestas vitae, eros Piratica,

      > How does “pattern matching in langauges like Haskell and OCaml” differ from Perl-style regular expressions?

      It's an unfortunate doubling of terminology.

      In Haskell you are free to "redefine“ a functions body multiple times for different argument "constellations"

      Those args have to "match a pattern", not (necessarily) a regular expressions!

      IIRC does Perl 6 have something very similar just named differently... ehm ...

      Multi-subs ? ? ?

      Cheers Rolf

      PS: Je suis Charlie!

      update

      yes it's called multi-subs in Perl 6, but from the examples I saw so far, I'd say that Haskell is more powerful.

      update

      see also http://en.wikibooks.org/wiki/Haskell/Pattern_matching

      Note

      Pattern matching on what?

      Some languages like Perl and Python use the term pattern matching for matching regular expressions against strings. The pattern matching we refer to in Haskell is something completely different. In fact, you're probably best off forgetting what you know about pattern matching for now

Re: rough approximation to pattern matching using local
by BrowserUk (Patriarch) on Jan 25, 2015 at 12:58 UTC

    It is clear that you are expecting match() to construct a variable called $bob, give it the value 45; in such a way that when the anonymous subroutine that is the third argument is run, that variable will exist and be visible to that subroutine.

    What isn't clear is quite how what you are doing would achieve that goal.

    A simple diagnostic run just before you invoke the anonymous sub:

    use Data::Dump qw[ pp ]; ... sub match ($$$) { no strict 'refs'; my ($lhs, $rhs, $fun) = @_; my $locals = {}; # populate locals match_inner($lhs, $rhs, $locals); while (my ($k, $v) = each %$locals) { local ${$k} = $v; } pp $locals; return $fun->(); }

    Shows that what you've done is add a key:bob with the value 45 to the hash(ref) name $locals:

    C:\test>1114414.pl Name "main::bob" used only once: possible typo at C:\test\1114414.pl l +ine 52. { bob => 45 } Use of uninitialized value $bob in print at C:\test\1114414.pl line 52 +.

    What that also shows is that the first warning:Name "main::bob" used only once: is produced at compile time; ie. long before your code ever gets invoked; so nothing your code does at runtime will resolve the problem you are being warned about.

    I think that's because the while loop localizes each variable only within the body of the loop.

    Indeed. Everything you did with local is undone as soon as you leave the body of the loop.

    In order to achieve your goal -- the declaration and initialisation of variables either within the scope of, or closed over by, your anonymous subroutine -- you would have to be modifying the symbol table prior to that anonymous subroutines compilation! Ie. You'd have to do something before your code was run.

    And that a tall order unless your code is running within the compiler itself.


    With the rise and rise of 'Social' network sites: 'Computers are making people easier to use everyday'
    Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
    "Science is about questioning the status quo. Questioning authority". I'm with torvalds on this
    In the absence of evidence, opinion is indistinguishable from prejudice. Agile (and TDD) debunked
Re: rough approximation to pattern matching using local
by BrowserUk (Patriarch) on Jan 25, 2015 at 14:52 UTC

    The only way I can see to getting close to what you want is to eval the subroutines into existence at runtime, something like this:

    #! perl -slw use strict; use Data::Dump qw[ pp ]; sub same_keys { my ($left, $right) = @_; return unless ref $left eq 'HASH' and ref $right eq 'HASH'; return unless keys %$left == keys %$right; for my $key (keys %$left) { exists $right->{$key} or return; } return 1; } sub match_inner { my ($lhs, $rhs, $locals) = @_; if (ref $lhs eq '') { $locals->{$lhs} = $rhs; } elsif (ref $lhs eq 'ARRAY') { if (@$lhs == @$rhs) { for my $i (0 .. $#$lhs) { match_inner($lhs->[$i], $rhs->[$i], $locals); } } } elsif (ref $lhs eq 'HASH') { if (same_keys($lhs,$rhs)) { for my $k (keys %$lhs) { match_inner($lhs->{$k}, $rhs->{$k}, $locals); } } } } sub match ($$$) { my ($lhs, $rhs, $fun) = @_; my $locals = {}; # populate locals match_inner($lhs, $rhs, $locals); my( $code, $k, $v ) = "{\n"; $code .= " my \$$k = '$v';\n" while ($k, $v) = each %$locals; $code .= $fun . "\n}"; print $code; return eval $code; } my $sub = match "bob", 45, q[ sub { print $bob; }; ]; pp $sub; $sub->(); my $sub2 = match ['bill','john'], [123, 'jack' ], q[ sub { print $bill, ' ', $john; } ]; pp $sub2; &$sub2; __END__ C:\test>1114414.pl { my $bob = '45'; sub { print $bob; }; } sub { "???" } 45 { my $john = 'jack'; my $bill = '123'; sub { print $bill, ' ', $john; } } sub { "???" } 123 jack

    But I doubt that actually allows what you're really aiming for.


    With the rise and rise of 'Social' network sites: 'Computers are making people easier to use everyday'
    Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
    "Science is about questioning the status quo. Questioning authority". I'm with torvalds on this
    In the absence of evidence, opinion is indistinguishable from prejudice. Agile (and TDD) debunked
Re: rough approximation to pattern matching using local
by soonix (Chancellor) on Jan 25, 2015 at 12:19 UTC

    You already got some ansers, but I can't resist to chime in :-)

    You haven't exactly told us what you want to achieve. It looks to me like what you want is not necessarily RegExes, but a kind of template toolkit / templating engine.

    Anyway, if you want to follow your initial approach (even if for learning purposes), you should at least read through

    These texts are Perl centered, but contain basics that are valid for any programming language.

Re: rough approximation to pattern matching using local
by LanX (Saint) on Jan 25, 2015 at 20:47 UTC
    You should be clearer about the features you want to implement.

    Sample code would be nice, a test suite even perfect.

    It's not as straight forward as you might think, since Haskell is

    • statically typed,
    • has real function signatures
    • evaluates pattern matching at compile time.
    So which features do you want to translate into Perl's world and how exactly?

    Please be aware that because of Perl's dynamic nature you are free to write something like

    sub multi { my ( $bob ) = @_; if ( $bob == 45 ) { print $bob; return; } elsif (...) { ... return; } else { ... return; } }

    anything else is - at best - "only" syntactic sugar.

    Cheers Rolf

    PS: Je suis Charlie!