<!-- category: functional -->
The /e and /r Combo in Map
Modify copies, not originals. Evaluate code in the replacement. Do both at once.That line takes every element ofmy @doubled = map { s~(\d+)~$1 * 2~er } @prices;
@prices, finds every number, doubles it, and returns the modified copy. The original @prices is untouched.
Two flags do the heavy lifting. The /e flag treats the replacement side as Perl code and evaluates it. The /r flag returns a modified copy instead of mutating the original. Put them together in a map and you get safe, functional transformations with computational power in the replacement.
Part 1: THE /r FLAG REFRESHER
Normallys~~~ modifies the variable it's bound to and returns the substitution count:
Withmy $text = "price: 50"; my $count = ($text =~ s~50~100~); # $count is 1, $text is now "price: 100"
/r, it returns the modified string and leaves the original alone:
The "r" stands for non-destructive (the docs say "return"). The variable is never touched. You get a fresh copy.my $text = "price: 50"; my $new = ($text =~ s~50~100~r); # $new is "price: 100", $text is still "price: 50"
Part 2: THE /e FLAG REFRESHER
Normally the replacement side ofs~~~ is a string:
Withmy $text = "count: 5"; $text =~ s~5~10~; # literal replacement
/e, the replacement is evaluated as Perl code:
The capturedmy $text = "count: 5"; $text =~ s~(\d+)~$1 * 2~e; # $text is now "count: 10"
$1 contains "5". The expression $1 * 2 evaluates to 10. The match is replaced with the result of the evaluation.
This is arbitrary Perl code. Function calls, conditionals, math, whatever you want:
$text =~ s~(\w+)~uc($1)~eg; # uppercase every word $text =~ s~(\d+)~sprintf("%04d", $1)~eg; # zero-pad numbers $text =~ s~(error|warn)~"[$1]"~egi; # bracket keywords
Part 3: COMBINING /e AND /r
Put them together and you get evaluated replacements that return copies:The original is preserved. The replacement was computed, not literal. Both flags doing their job simultaneously.my $original = "Item costs 25 dollars"; my $inflated = ($original =~ s~(\d+)~$1 * 1.15~er); # $original: "Item costs 25 dollars" # $inflated: "Item costs 28.75 dollars"
The order of the flags doesn't matter. s~~~er and s~~~re are identical. But er reads better because you naturally think "evaluate, then return."
Part 4: THE MAP CONNECTION
Here's where it gets interesting. Insidemap, the default variable $_ is aliased to each element of the source list. If you mutate $_, you mutate the original.
Dangerous:
You changed the originals. That's almost never what you want in amy @names = ('alice', 'bob', 'charlie'); my @upper = map { s~^(\w)~uc($1)~e; $_ } @names; # @names is NOW ('Alice', 'Bob', 'Charlie') -- MUTATED!
map.
Safe:
Themy @names = ('alice', 'bob', 'charlie'); my @upper = map { s~^(\w)~uc($1)~er } @names; # @names is still ('alice', 'bob', 'charlie') # @upper is ('Alice', 'Bob', 'Charlie')
/r flag makes it functional. No mutation. The map builds a new list from returned copies. The source list survives.
Part 5: PRICE DOUBLING
You have a list of price strings. Double every number in them:Result:my @prices = ( 'Widget: $25', 'Gadget: $50', 'Doohickey: $12.99', ); my @doubled = map { s~([\d.]+)~$1 * 2~ger } @prices;
TheWidget: $50 Gadget: $100 Doohickey: $25.98
/g flag handles multiple numbers per string. The /e does the math. The /r protects the originals. Three flags, zero side effects.
Part 6: UNIT CONVERSION
Convert temperatures in text from Fahrenheit to Celsius:Result:my @readings = ( 'Lab A: 72F', 'Lab B: 68F', 'Lab C: 75F', ); my @celsius = map { s~(\d+)F~ sprintf("%.1fC", ($1 - 32) * 5/9) ~er } @readings;
The replacement side is a fullLab A: 22.2C Lab B: 20.0C Lab C: 23.9C
sprintf call. The /e flag doesn't care how complex your expression is. If it's valid Perl, it runs.
Convert bytes to human-readable sizes:
Result:my @sizes = ('file1: 1048576', 'file2: 5368709120', 'file3: 1024'); my @human = map { s~(\d+)$~ my $b = $1; $b >= 1073741824 ? sprintf("%.1fG", $b/1073741824) : $b >= 1048576 ? sprintf("%.1fM", $b/1048576) : $b >= 1024 ? sprintf("%.1fK", $b/1024) : "${b}B" ~er } @sizes;
A full conditional expression inside the replacement. Perl doesn't blink.file1: 1.0M file2: 5.0G file3: 1.0K
Part 7: DATE REFORMATTING
You have dates in MM/DD/YYYY and you want ISO 8601:Result:my @dates = ('01/15/2026', '12/25/2025', '03/29/2026'); my @iso = map { s~(\d{2})/(\d{2})/(\d{4})~"$3-$1-$2"~er } @dates;
The captures2026-01-15 2025-12-25 2026-03-29
$1, $2, $3 grab month, day, year. The replacement rearranges them. The /e evaluates the double-quoted string so the variables interpolate. The /r returns the copy.
Part 8: CHAINING WITH GREP
Combinemap with grep for filter-and-transform pipelines:
Result:my @log_lines = ( '2026-03-28 ERROR: disk usage 95%', '2026-03-28 INFO: backup complete', '2026-03-28 ERROR: memory usage 88%', '2026-03-28 INFO: cron job started', ); my @alerts = map { s~(\d+)%~"$1% (CRITICAL)" ~er } grep { m~ERROR~ } @log_lines;
Read bottom to top: filter for ERROR lines, then transform the percentage values. The original2026-03-28 ERROR: disk usage 95% (CRITICAL) 2026-03-28 ERROR: memory usage 88% (CRITICAL)
@log_lines is untouched. The output is a fresh list of modified copies.
Part 9: BUILDING DATA PIPELINES
Stack multiple transformations by nesting maps or chaining/r inside a single map:
Result:my @raw = ( ' ALICE::Engineering::75000 ', ' BOB::Sales::62000 ', ' CHARLIE::Marketing::58000 ', ); my @formatted = map { s~^\s+|\s+$~~gr # trim =~ s~^(\w)(\w+)~$1 . lc($2)~er # title case name =~ s~::~, ~gr # delimiters to commas =~ s~(\d+)$~ sprintf('$%s', commify($1)) ~er # format salary } @raw; sub commify { my $n = reverse $_[0]; $n =~ s~(\d{3})(?=\d)~$1,~g; return scalar reverse $n; }
Each step in the chain usesAlice, Engineering, $75,000 Bob, Sales, $62,000 Charlie, Marketing, $58,000
/r to pass its result to the next. The /e flag on specific steps does the computation. The map collects the final results. Nothing is mutated.
Part 10: THE FUNCTIONAL ARGUMENT
Why does this matter? Because functional transforms are easier to reason about.When you see this:
You know three things immediately:my @results = map { s~(\d+)~$1 * 2~er } @input;
@inputis unchanged after this line@resultscontains modified copies- The transformation is self-contained
No spooky action at a distance. No hidden mutation. No "wait, did the loop body change my source data?" The /r flag is a contract: this substitution is read-only with respect to the original.
Compare to the imperative approach:
Four lines to do what one line does. A manual copy. A mutable variable. A push. It all works, but it's ceremony around a simple idea.my @results; for my $item (@input) { my $copy = $item; $copy =~ s~(\d+)~$1 * 2~e; push @results, $copy; }
The /er combo in map says what it means: transform and return. Nothing more.
perl.gg@input | +---------+ | map | | s~~~er | +---------+ | @output Input unchanged. Output derived. Functional Perl. No mutation. .--. |o_o | |:_/ | // \ \ (| | ) /'\_ _/`\ \___)=(___/