Date Created: 2024-10-10 Date Modified: 2024-10-10 ============================================================================ 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: my $db = { name => 'Mike', age => '45' }; A humble hash reference. Two keys, two values. Nothing controversial yet. ============================================================================ PART 2: THE SANE APPROACH ============================================================================ Here's how a reasonable person would iterate: for my $key (reverse sort keys %{$db}) { print qq|$key: $db->{$key}\n|; } Output: name: Mike age: 45 Beautiful. Readable. Your coworkers won't plot your demise. But we're not here for beautiful. We're here to learn. ============================================================================ PART 3: THE UNHINGED ALTERNATIVE ============================================================================ Behold: qq|@{[ reverse sort keys %{$db} ]}| =~ s~\S+~print qq|$&: $db->{$&}\n|~ger; Same output. No for loop. Maximum confusion. Let's dissect this beast. ============================================================================ PART 4: THE OUTER SHELL - qq|| ============================================================================ qq|...| This is a double-quoted string. The qq lets you pick your own delimiters (here we're using pipes). Everything inside gets interpolated. 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 - @{[]} ============================================================================ @{[ reverse sort keys %{$db} ]} This is the "babycart operator." Not an official operator - more like three operators wearing a trenchcoat. Breaking it down: 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 So we: 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" The babycart trick lets you run arbitrary code inside a string and interpolate the result. It's the crowbar of Perl string manipulation. .--. |o_o | |:_/ | // \ \ (| | ) /'\_ _/`\ \___)=(___/ ============================================================================ PART 6: THE BINDING - =~ ============================================================================ qq|...| =~ s~~~ger; The =~ operator binds a regex operation to a string. 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 ============================================================================ s~\S+~print qq|$&: $db->{$&}\n|~ger This is where the magic happens. Let's break it down: 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 \S+ pattern matches each key in our interpolated string. For "name age", it matches "name" first, then "age". 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: 1. $& contains the matched key ("name" or "age") 2. The print statement executes 3. We print the key and its value The /g flag means "do this for every match." That's our loop. ============================================================================ PART 8: THE MATCH VARIABLE ============================================================================ $& This special variable holds whatever the last regex matched. In our substitution, it's each key in turn. First match: $& = "name" -> prints "name: Mike" Second match: $& = "age" -> prints "age: 45" The regex engine is doing our iteration for us. ============================================================================ PART 9: ADDING FILTERS ============================================================================ Want to match only specific keys? Add grep: qq|@{[ grep { m~^name$~ } keys %{$db} ]}| =~ s~\S+~print qq|$&: $db->{$&}\n|~ger; Output: name: Mike Now we're: 1. Getting all keys 2. Filtering with grep (only keys matching "name") 3. Interpolating the survivors 4. Running the substitution "loop" You can use any grep condition. Regex. Comparisons. Subroutine calls. The world is your horrifying oyster. ============================================================================ 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: 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 The techniques are legitimate. The combination is cursed. ============================================================================ PART 12: WHEN TO ACTUALLY USE THIS ============================================================================ Never. Okay, almost never. Maybe: * Code golf (minimize character count) * One-liners (when you can't use a block) * Teaching (to show what's possible) * Obfuscation contests In production? Use a for loop. Your future self will thank you. 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. ============================================================================ _______________ / \ | FOR MY $KEY | | (KEYS %HASH) | | { ... } | \_______________/ | | "Too easy" | V _______________ / \ | qq|@{[...]}| | | =~ s~~~ger; | \_______________/ "Same result, more suffering" ============================================================================ perl.gg