Recently, I took a stab at learning Haskell, but that's not really where this meditation came from. I've always thought there should be a side-effect-less (I know, not a real word) way to do substitutions, so I don't have to explicitely copy over a variable.

my $first; ... my $second = $first; $second =~ s/some/substitution/;

Okay, okay, so this really isn't a big deal, but it's worse off when I'm doing a map...

my @output = map { my $temp = $_; $temp =~ s/some/subst/g; $temp } @input;

So today I was at work, not doing much, and I thought about the different ways I could do this, and I finally wrote a module. I'm wondering what you, my fellow monks, think about this.

So what I finally decided on is a source filter. I know, there are a lot of people out there that eschew source filters, but I think it may be the best way to do it. So I ended up with the ability to do this:

use Regex::FunctionalSubstitute; my $first; ... my $second = $first \~ s/some/subst/g;

and

my @output = map { $_ \~ s/some/subst/g; } @input;

Frankly, especially in the map case where map is meant to transform, this just feels right to me. Now, I'm not sure about the exact syntax... I decided on \~, but am certainly not set on it, for two reasons. 1: \ gives the look of "not binding" compared to = looks like "binding". 2: \ also is part of Haskell's anonymous sub syntax, which is sometimes what I think of when I think about Haskell.

I should mention what the source filter does and it's repercussions. The source filter changes: $identifier \~ s/some/subst/ into do { $temp_var = $identifier; $temp_var =~ s/some/subst/; $temp_var }. The problem with this is that $1, $2, etc., and everything involved in regex's will be out of scope, and therefore un-useable, so I would have to find a way to circumvent this, but this may cause similar problems to 'use English'. The source filter changes: $identifier \~ s/some/subst/ into ( $temp_var = $identifier, $temp_var =~ s/some/subst, $temp_var )[2] which has the effect of preserving $1, $2, etc. as well as returning the right value, both inside and outside map.

Well, enough explanation, here's the current code (definitely not finished).

package Regex::FunctionalSubstitute; use Filter::Simple; use strict; use warnings; my $identifier = qr/\$\S+/; my $operator = qr/\\~/; my $temp = '$Regex::FunctionalSubstitute::temporary_variable'; =head1 TODO Test Suite This documentation More accurate 'identifier' regex =head1 Conversion $this = $that \~ s/one/two/; converts to: $this = ( $temp = $that, $temp =~ s/one/two/, $temp )[2] =cut FILTER_ONLY( code => sub { my $ph = $Filter::Simple::placeholder; s{ ($identifier) # the source of the substitutio +n \s* # possible space between $operator # the operator '\~' \s* # more possible space between ($ph) # the regex itself } { ( $temp = $1, $temp =~ $2, $temp )[2] }gx; }, ); 1;

Update: Changed the transformation after fiddling with this for a while, so now $1, $2, etc. should be preserved.

    -Bryan

Replies are listed 'Best First'.
Re: A Functional Substitute
by BrowserUk (Patriarch) on Aug 08, 2005 at 21:45 UTC

    You might find the filter() function in tye's Algorithm::Loops module of interest.

    I always thought that it and the MapCar*() set of functions should have been in a separate module from the loops stuff where they might get seen. Maybe even included into List::Util


    Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
    Lingua non convalesco, consenesco et abolesco. -- Rule 1 has a caveat! -- Who broke the cabal?
    "Science is about questioning the status quo. Questioning authority".
    The "good enough" maybe good enough for the now, and perfection maybe unobtainable, but that should not preclude us from striving for perfection, when time, circumstance or desire allow.
Re: A Functional Substitute
by friedo (Prior) on Aug 08, 2005 at 21:48 UTC
    What about:

    ($second = $first) =~ s/foo/bar/;

      This, and BrowserUk's suggestion are both good ones. BrowserUk's I wasn't even aware of, and this one I had somewhat forgot about. However, I do like the consistency of having one method of doing it that works both standalone and in map. Although I usually argue that adding a variable at the end of a map block isn't that bad, I'm going to be a devil's advocate and argue against it here.

      my @output = map { (my $i = $_) =~ s/foo/bar/; $i } @input;

      Seems less readable to me than

      my @output = map { $_ \~ s/foo/bar/ } @input;

      And I like the use of 'map' instead of 'filter' (especially since I think of grep when I think of filter).

      I guess it looks like personal preference here, so maybe I'll just perfect this one and keep it for myself (since I don't use $1 and $2 very often from what I've noticed, I wouldn't have to fix them just for myself).

          -Bryan

        map { $_ \~ s/foo/bar/ }
        I don't like that $_ there. Shouldn't you be able to do something like
        map { \s/foo/bar/ }
        so that it operates on $_ by default, implicitly?
Re: A Functional Substitute
by tinita (Parson) on Aug 09, 2005 at 09:12 UTC
    why not just write a new function? this one you can use almost like map:
    sub cmap (&@) { map { local $_ = $_; $_[0]->(); $_ } @_[1..$#_]; } my @copy = cmap { s/a/b/ } @array;
Re: A Functional Substitute
by kappa (Chaplain) on Aug 12, 2005 at 16:44 UTC
    The main (and very bold) argument for the substitution being an side-effect operator is the need to know whether it succeeded. You often need to do this:
    s/^($timestamp_re)/decode($1)/e or die "Oh wait, looks like your log i +s garbled!\n";
    ...or this:
    s/11 Aug/10 Aug/g and ++$count;
    How will you know if your substitution function found anything to substitute?
    --kap