╦ ╔═╗╔═╗╔═╗╔═╗ ╦ ╦╦╔═╗ ╦═╗╔═╗╔═╗╔═╗═╗ ╦ ║ ║ ║║ ║╠═╝╚═╗ ╚╗╔╝║╠═╣ ╠╦╝║╣ ║ ╦║╣ ╔╩╦╝ ╩═╝╚═╝╚═╝╩ ╚═╝ ╚╝ ╩╩ ╩ ╩╚═╚═╝╚═╝╚═╝╩ ╚═ ============================================================================ What if you could iterate over a hash without writing a for loop? Not with while. Not with foreach. With regex substitution. my $db = { name => 'Mike', age => '45' }; qq|@{[ reverse sort keys %{$db} ]}| =~ s~\S+~print qq|$&: $db->{$&}\n|~ger; Output: name: Mike age: 45 No loop keyword anywhere. Just a string, a regex, and the /e modifier doing things it probably shouldn't. ============================================================================ PART 1: THE NORMAL WAY ---------------------- Before we go weird, here's how sane people do it: for my $key (reverse sort keys %{$db}) { print qq|$key: $db->{$key}\n|; } Clean. Readable. Maintainable. Everything your code should be. Now let's throw that out the window. ============================================================================ PART 2: THE DATA ---------------- my $db = { name => 'Mike', age => '45' }; Note: $db is a REFERENCE to a hash, not a hash itself. $db Hash reference (scalar) %{$db} Dereferenced hash keys %{$db} Keys of the dereferenced hash $db->{name} Value lookup through the reference This distinction matters. We'll be dereferencing a lot. ============================================================================ PART 3: THE STRING TRICK ------------------------ qq|@{[ reverse sort keys %{$db} ]}| This builds a string containing all the hash keys. Let's unpack it: PIECE WHAT IT DOES --------------------------- -------------------------------- qq|...| Double-quoted string @{[ ... ]} The "baby cart" operator keys %{$db} Get all keys from the hash sort Alphabetize them reverse Flip the order The baby cart @{[...]} is the secret sauce. It: 1. Evaluates the code inside 2. Creates an anonymous array from the result 3. Immediately dereferences it into the string So our string becomes: "name age" Just the keys, space-separated. Now we have something to regex against. ============================================================================ PART 4: THE SUBSTITUTION ------------------------ =~ s~\S+~print qq|$&: $db->{$&}\n|~ger; This is where the loop happens. Breaking it down: PIECE WHAT IT DOES ------- ------------------------------------------------ =~ Bind the string to this regex operation s~~~ Substitution (using ~ as delimiter) \S+ Match one or more non-whitespace chars (a key) print ... The replacement (but it's code, not text) g Global - match ALL keys, not just the first e Evaluate replacement as Perl code r Return result, don't modify original The /g flag is our loop. It finds every match in the string. The /e flag turns each replacement into code execution. For each key matched: 1. $& contains the matched key ("name" or "age") 2. The print statement runs 3. $db->{$&} looks up the value Two keys, two matches, two print statements. Loop complete. .--. |o_o | |:_/ | // \ \ (| | ) /'\_ _/`\ \___)=(___/ ============================================================================ PART 5: THE MATCH VARIABLE -------------------------- $& This special variable holds whatever the regex just matched. When \S+ matches "name", $& is "name". When \S+ matches "age", $& is "age". We use it twice in the replacement: print qq|$&: $db->{$&}\n| ^^ ^^^^^^^ | | | +-- Look up this key's value +------ Print the key itself Same variable, two purposes. The key becomes both label and lookup. ============================================================================ PART 6: WALKTHROUGH ------------------- Starting string: "name age" MATCH $& EXECUTES OUTPUT ----- ------ ----------------------------- ----------- 1 "name" print qq|name: $db->{name}\n| name: Mike 2 "age" print qq|age: $db->{age}\n| age: 45 The /g modifier keeps matching until the string is exhausted. Each match triggers the /e evaluation. Two matches, two prints. ============================================================================ PART 7: FILTERING WITH GREP --------------------------- Want only specific keys? Add a grep: qq|@{[ grep { m~^name$~ } keys %{$db} ]}| =~ s~\S+~print qq|$&: $db->{$&}\n|~ger; The grep filters before the string is built: PIECE WHAT IT DOES ----------------------- -------------------------------- keys %{$db} All keys: (name, age) grep { m~^name$~ } Keep only keys matching "name" Result Just: (name) Now the string is just "name" and we only print one pair. You can use any condition in the grep: # Keys starting with 'n' grep { m~^n~ } keys %{$db} # Keys longer than 3 characters grep { length > 3 } keys %{$db} # Keys that aren't 'age' grep { $_ ne 'age' } keys %{$db} ============================================================================ PART 8: THE ANNOTATED VERSION ----------------------------- #!/usr/bin/env perl use feature qw|say|; # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Our data structure # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Hash reference (not a hash) my $db = { name => 'Mike', age => '45' }; # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # The regex "loop" # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Step 1: Build a string of all keys # @{[...]} interpolates code results into the string # reverse sort orders them Z-A # Step 2: Use s///ge to "iterate" # \S+ matches each key # /g makes it match ALL keys # /e evaluates the replacement as code # /r returns result without modifying original # Step 3: $& holds the current match (the key) # Use it to print key and look up value qq|@{[ reverse sort keys %{$db} ]}| =~ s~\S+~print qq|$&: $db->{$&}\n|~ger; ============================================================================ PART 9: WHY YOU SHOULDN'T DO THIS --------------------------------- Let's be clear: this is a terrible idea for production code. Problems: * Unreadable to anyone who hasn't seen this trick * Slower than a real loop (regex overhead) * $& has performance implications (though modern Perl is better) * Breaks if keys contain whitespace * Debugging is a nightmare But it demonstrates: * The baby cart operator @{[...]} * Regex as iteration with /g * Code execution in substitution with /e * The $& match variable * Reference dereferencing patterns These are all useful concepts. Just... use them normally. ============================================================================ PART 10: WHEN THIS PATTERN IS USEFUL ------------------------------------ The s///ge pattern (minus the weird key iteration) shows up in real code for transformations: # Convert all numbers to their squares $text =~ s~\d+~$& * $&~ge; # Uppercase all words starting with 'a' $text =~ s~\ba\w+~uc($&)~ge; # Replace placeholders with values $template =~ s~\{\{(\w+)\}\}~$data->{$1}~ge; The last one is basically a micro-templating engine. Same pattern, legitimate use case. The baby cart trick is useful for embedding calculations in strings: say "Sum: @{[ 2 + 2 ]}"; # Sum: 4 say "Time: @{[ scalar localtime ]}"; # Time: Sun Jan 5 14:30:00 2025 say "Files: @{[ `ls | wc -l` ]}"; # Files: 42 Know the techniques. Use them wisely. ============================================================================ .---. / o o \ \ L / ----- /| |\ / | | \ | | /| |\ (_| |_) "Who needs for loops anyway?" ============================================================================ japh.codes