Reinventing For
You've got a hash. You want to iterate over its keys. What do you reach for?If you said "for loop," congratulations. You're normal.
But what if I told you there's another way? A way that involves regex substitution, anonymous arrays, and the kind of code that makes reviewers question your sanity?
Let's build a for loop out of things that aren't for loops.
Part 1: THE HASH
First, the data:A humble hash reference. Two keys, two values. Nothing controversial yet.my $db = { name => 'Mike', age => '45' };
Part 2: THE SANE APPROACH
Here's how a reasonable person would iterate:Output:for my $key (reverse sort keys %{$db}) { print qq|$key: $db->{$key}\n|; }
Beautiful. Readable. Your coworkers won't plot your demise.name: Mike age: 45
But we're not here for beautiful. We're here to learn.
Part 3: THE UNHINGED ALTERNATIVE
Behold:Same output. No for loop. Maximum confusion.qq|@{[ reverse sort keys %{$db} ]}| =~ s~\S+~print qq|$&: $db->{$&}\n|~ger;
Let's dissect this beast.
Part 4: THE OUTER SHELL - qq||
This is a double-quoted string. The qq lets you pick your own delimiters (here we're using pipes). Everything inside gets interpolated.qq|...|
Why qq| instead of quotes? Because we're going to have quotes and other nonsense inside, and escaping gets ugly fast.
Part 5: THE BABYCART - @{[]}
This is the babycart operator. Not an official operator - more like three operators wearing a trenchcoat.@{[ reverse sort keys %{$db} ]}
Breaking it down:
So we:PIECE WHAT IT DOES ----------- -------------------------------------------- @{ ... } Dereference an array reference [ ... ] Create an anonymous array reference reverse sort Sort and reverse (alphabetically, descending) keys %{$db} Get all keys from the hash reference
The babycart trick lets you run arbitrary code inside a string and interpolate the result. It's the crowbar of Perl string manipulation.1. Get the keys: ('name', 'age') 2. Sort them: ('age', 'name') 3. Reverse them: ('name', 'age') 4. Wrap in anonymous array: ['name', 'age'] 5. Dereference: ('name', 'age') 6. Interpolate into string: "name age"
.--. |o_o | |:_/ | // \ \ (| | ) /'\_ _/`\ \___)=(___/
Part 6: THE BINDING - =~
The =~ operator binds a regex operation to a string.qq|...| =~ s~~~ger;
But here's the thing: we're not modifying a variable. We're running a substitution on a string literal. The /r flag (coming up) means we don't need a modifiable target - we get a new string back.
Part 7: THE SUBSTITUTION ENGINE
This is where the magic happens. Let's break it down:s~\S+~print qq|$&: $db->{$&}\n|~ger
The \S+ pattern matches each key in our interpolated string. For "name age", it matches "name" first, then "age".PIECE WHAT IT DOES ---------------------------- --------------------------------- s~~~ Substitution with ~ as delimiter \S+ Match one or more non-whitespace print qq|$&: $db->{$&}\n| The replacement (code!) g Global - match all occurrences e Evaluate replacement as Perl code r Return result, don't modify in-place
The /e flag is the star of the show. Instead of replacing with a literal string, it RUNS the replacement as Perl code.
So for each match:The /g flag means "do this for every match." That's our loop.1. $& contains the matched key ("name" or "age") 2. The print statement executes 3. We print the key and its value
Part 8: THE MATCH VARIABLE
This special variable holds whatever the last regex matched. In our substitution, it's each key in turn.$&
The regex engine is doing our iteration for us.First match: $& = "name" -> prints "name: Mike" Second match: $& = "age" -> prints "age: 45"
Part 9: ADDING FILTERS
Want to match only specific keys? Add grep:Output:qq|@{[ grep { m~^name$~ } keys %{$db} ]}| =~ s~\S+~print qq|$&: $db->{$&}\n|~ger;
Now we're:name: Mike
You can use any grep condition. Regex. Comparisons. Subroutine calls. The world is your horrifying oyster.1. Getting all keys 2. Filtering with grep (only keys matching "name") 3. Interpolating the survivors 4. Running the substitution "loop"
Part 10: THE ANNOTATED VERSION
#!/usr/bin/env perl use feature 'say'; use strict; use warnings; # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # The data structure # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ my $db = { name => 'Mike', age => '45' }; # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # The normal way (for comparison) # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # for my $key (reverse sort keys %{$db}) # { # print qq|$key: $db->{$key}\n|; # } # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # The unhinged way # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Step 1: qq|...| creates an interpolated string # Step 2: @{[...]} interpolates the result of arbitrary code # Step 3: reverse sort keys gets our keys in order # Step 4: =~ binds the regex to our string # Step 5: s~~~ger substitutes with code evaluation # Step 6: \S+ matches each key # Step 7: The /e flag runs print for each match # Step 8: The /g flag repeats for all matches qq|@{[ reverse sort keys %{$db} ]}| =~ s~\S+~print qq|$&: $db->{$&}\n|~ger;
Part 11: WHAT YOU LEARNED
This abomination teaches real concepts:The techniques are legitimate. The combination is cursed.CONCEPT WHERE IT APPEARS ---------------------- --------------------------------- References $db = { ... } Dereferencing %{$db}, $db->{$&} Hash operations keys %{$db} String interpolation qq|...|, @{[...]} Regex binding =~ Substitution flags /g, /e, /r Match variables $& List operations reverse, sort, grep
Part 12: WHEN TO ACTUALLY USE THIS
Never.Okay, almost never. Maybe:
In production? Use a for loop. Your future self will thank you.* Code golf (minimize character count) * One-liners (when you can't use a block) * Teaching (to show what's possible) * Obfuscation contests
But understanding WHY this works makes you a better Perl programmer. You'll recognize the patterns when you see them. You'll know that /e can run code. You'll remember that strings can contain computed values.
And someday, when you need to do something weird in a one-liner, you'll have the tools.
perl.gg_______________ / \ | FOR MY $KEY | | (KEYS %HASH) | | { ... } | \_______________/ | | "Too easy" | V _______________ / \ | qq|@{[...]}| | | =~ s~~~ger; | \_______________/ "Same result, more suffering"