Six months ago I gave an overview of Perl’s list proÂcessÂing funÂdaÂmenÂtals, briefly describÂing what lists are and then introÂducÂing the built-​in map and grep funcÂtions for transÂformÂing and filÂterÂing them. Later on, I comÂpiled a list (how approÂpriÂate) of list proÂcessÂing modÂules availÂable via CPAN, notÂing there’s some conÂfusÂing dupliÂcaÂtion of effort. But you’re a busy develÂopÂer, and you just want to know the Right Thing To Do™ when faced with a list proÂcessÂing challenge.
First, some credÂit is due: these are all restateÂments of sevÂerÂal Perl::Critic poliÂcies which in turn codÂiÂfy stanÂdards described in Damian Conway’s Perl Best Practices (2005). I’ve repeatÂedÂly recÂomÂmendÂed the latÂter as a startÂing point for higher-​quality Perl develÂopÂment. Over the years these pracÂtices conÂtinÂue to be re-​evaluated (includÂing by the author himÂself) and varÂiÂous authors release new polÂiÂcy modÂules, but perlcritic remains a great tool for ensurÂing you (and your team or othÂer conÂtribÂuÂtors) mainÂtain a conÂsisÂtent high stanÂdard in your code.
With that said, on to the recommendations!
Don’t use grep to check if any list elements match
It might sound weird to lead off by recÂomÂmendÂing not to use grep, but someÂtimes it’s not the right tool for the job. If you’ve got a list and want to deterÂmine if a conÂdiÂtion matchÂes any item in it, you might try:
if (grep { some_condition($_) } @my_list) {
... # don't do this!
}
Yes, this works because (in scalar conÂtext) grep returns the numÂber of matchÂes found, but it’s wasteÂful, checkÂing every eleÂment of @my_list (which could be lengthy) before finalÂly proÂvidÂing a result. Use the stanÂdard List::Util module’s any funcÂtion, which immeÂdiÂateÂly returns (“short-​circuits”) on the first match:
use List::Util 1.33 qw(any);
if (any { some_condition($_) } @my_list) { ... # do something }
As a side note for web develÂopÂers, the Perl Dancer frameÂwork also includes an any keyÂword for declarÂing mulÂtiÂple HTTP routes, so if you’re mixÂing List::Util in there don’t import it. Instead, call it explicÂitÂly like this or you’ll get an error about a redeÂfined function:
use List::Util 1.33;
if (List::Util::any { some_condition($_) } @my_list) { ... # do something }
I menÂtioned this back in March, but it bears repeatÂing: map and grep are intendÂed as pure funcÂtions, not mutaÂtors with side effects. This means that the origÂiÂnal list should remain unchanged. Yes, each eleÂment aliasÂes in turn to the $_ speÂcial variÂable, but that’s for speed and can have surÂprisÂing results if changed even if it’s techÂniÂcalÂly allowed. If you need to modÂiÂfy an array in-​place use someÂthing like:
for (@my_array) { $_ = ...; # make your changes here }
If you want someÂthing that looks like map but won’t change the origÂiÂnal list (and don’t mind a few CPAN depenÂdenÂcies), conÂsidÂer List::SomeUtils’ apply function:
use List::SomeUtils qw(apply);
my @doubled_array = apply {$_ *= 2} @old_array;
Lastly, side effects also include things like manipÂuÂlatÂing othÂer variÂables or doing input and outÂput. Don’t use map or grep in a void conÂtext (i.e., withÂout a resultÂing array or list); do someÂthing with the results or use a for or foreach loop:
map { print foo($_) } @my_array; # don't do this print map { foo($_) } @my_array; # do this instead
map { push @new_array, foo($_) } @my_array; # don't do this @new_array = map { foo($_) } @my_array; # do this instead
my @new_array = map foo($_), @old_array; # don't do this my @new_array2 = grep !/^#/, @old_array; # don't do this
Or like this:
my @new_array = map { foo($_) } @old_array; my @new_array2 = grep {!/^#/} @old_array;
Do it the secÂond way. It’s easÂiÂer to read, espeÂcialÂly if you’re passÂing in a litÂerÂal list or mulÂtiÂple arrays, and the expresÂsion forms can conÂceal bugs. This recÂomÂmenÂdaÂtion is codÂiÂfied by the BuiltinFunctions::RequireBlockGrep and BuiltinFunctions::RequireBlockMap Perl::Critic poliÂcies and comes from Perl Best Practices.
Refactor multi-​statement maps, greps, and other list functions
map, grep, and friends should folÂlow the Unix phiÂlosÂoÂphy of ​“Do One Thing and Do It Well.” Your readÂabilÂiÂty and mainÂtainÂabilÂiÂty drop with every stateÂment you place inside one of their blocks. Consider junior develÂopÂers and future mainÂtainÂers (this includes you!) and refacÂtor anyÂthing with more than one stateÂment into a sepÂaÂrate subÂrouÂtine or at least a for loop. This goes for list proÂcessÂing funcÂtions (like the aforeÂmenÂtioned any) importÂed from othÂer modÂules, too.
4 thoughts on “Better Perl: Four list processing best practices with map, grep, and more”
[…] Better Perl: Four list proÂcessÂing best pracÂtices with map, grep, and more […]
Loading…
Use List::Util::first instead of any to avoid name clashÂes and not worÂry about verÂsions of List::Util.
Loading…
They are not equivÂaÂlent and using first instead of any can lead to bugs. first will return the first eleÂment for which the conÂdiÂtion is true; if that eleÂment is itself a false valÂue (0, the null string, or undef), the funcÂtion will evalÂuÂate to false even though the eleÂment was found.
Loading…
[…] Better Perl: Four list proÂcessÂing best pracÂtices with map, grep, and more […]
Loading…
Comments are closed.
{"id":"11","mode":"button","open_style":"in_modal","currency_code":"USD","currency_symbol":"$","currency_type":"decimal","blank_flag_url":"https:\/\/phoenixtrap.com\/wp-content\/plugins\/tip-jar-wp\/\/assets\/images\/flags\/blank.gif","flag_sprite_url":"https:\/\/phoenixtrap.com\/wp-content\/plugins\/tip-jar-wp\/\/assets\/images\/flags\/flags.png","default_amount":500,"top_media_type":"featured_image","featured_image_url":"https:\/\/phoenixtrap.com\/wp-content\/uploads\/2021\/02\/image-200x200.jpg","featured_embed":"","header_media":null,"file_download_attachment_data":null,"recurring_options_enabled":true,"recurring_options":{"never":{"selected":true,"after_output":"One time only"},"weekly":{"selected":false,"after_output":"Every week"},"monthly":{"selected":false,"after_output":"Every month"},"yearly":{"selected":false,"after_output":"Every year"}},"strings":{"current_user_email":"","current_user_name":"","link_text":"Leave a tip!","complete_payment_button_error_text":"Check info and try again","payment_verb":"Pay","payment_request_label":"The Phoenix Trap","form_has_an_error":"Please check and fix the errors above","general_server_error":"Something isn't working right at the moment. Please try again.","form_title":"The Phoenix Trap","form_subtitle":"Do you like what you see? Leave a one-time or recurring tip!","currency_search_text":"Country or Currency here","other_payment_option":"Other payment option","manage_payments_button_text":"Manage your payments","thank_you_message":"Thank you for being a supporter!","payment_confirmation_title":"The Phoenix Trap","receipt_title":"Your Receipt","print_receipt":"Print Receipt","email_receipt":"Email Receipt","email_receipt_sending":"Sending receipt...","email_receipt_success":"Email receipt successfully sent","email_receipt_failed":"Email receipt failed to send. Please try again.","receipt_payee":"Paid to","receipt_statement_descriptor":"This will show up on your statement as","receipt_date":"Date","receipt_transaction_id":"Transaction ID","receipt_transaction_amount":"Amount","refund_payer":"Refund from","login":"Log in to manage your payments","manage_payments":"Manage Payments","transactions_title":"Your Transactions","transaction_title":"Transaction Receipt","transaction_period":"Plan Period","arrangements_title":"Your Plans","arrangement_title":"Manage Plan","arrangement_details":"Plan Details","arrangement_id_title":"Plan ID","arrangement_payment_method_title":"Payment Method","arrangement_amount_title":"Plan Amount","arrangement_renewal_title":"Next renewal date","arrangement_action_cancel":"Cancel Plan","arrangement_action_cant_cancel":"Cancelling is currently not available.","arrangement_action_cancel_double":"Are you sure you'd like to cancel?","arrangement_cancelling":"Cancelling Plan...","arrangement_cancelled":"Plan Cancelled","arrangement_failed_to_cancel":"Failed to cancel plan","back_to_plans":"\u2190 Back to Plans","update_payment_method_verb":"Update","sca_auth_description":"Your have a pending renewal payment which requires authorization.","sca_auth_verb":"Authorize renewal payment","sca_authing_verb":"Authorizing payment","sca_authed_verb":"Payment successfully authorized!","sca_auth_failed":"Unable to authorize! Please try again.","login_button_text":"Log in","login_form_has_an_error":"Please check and fix the errors above","uppercase_search":"Search","lowercase_search":"search","uppercase_page":"Page","lowercase_page":"page","uppercase_items":"Items","lowercase_items":"items","uppercase_per":"Per","lowercase_per":"per","uppercase_of":"Of","lowercase_of":"of","back":"Back to plans","zip_code_placeholder":"Zip\/Postal Code","download_file_button_text":"Download File","input_field_instructions":{"tip_amount":{"placeholder_text":"How much would you like to tip?","initial":{"instruction_type":"normal","instruction_message":"How much would you like to tip? Choose any currency."},"empty":{"instruction_type":"error","instruction_message":"How much would you like to tip? Choose any currency."},"invalid_curency":{"instruction_type":"error","instruction_message":"Please choose a valid currency."}},"recurring":{"placeholder_text":"Recurring","initial":{"instruction_type":"normal","instruction_message":"How often would you like to give this?"},"success":{"instruction_type":"success","instruction_message":"How often would you like to give this?"},"empty":{"instruction_type":"error","instruction_message":"How often would you like to give this?"}},"name":{"placeholder_text":"Name on Credit Card","initial":{"instruction_type":"normal","instruction_message":"What is the name on your credit card?"},"success":{"instruction_type":"success","instruction_message":"Enter the name on your card."},"empty":{"instruction_type":"error","instruction_message":"Please enter the name on your card."}},"privacy_policy":{"terms_title":"Terms and conditions","terms_body":null,"terms_show_text":"View Terms","terms_hide_text":"Hide Terms","initial":{"instruction_type":"normal","instruction_message":"I agree to the terms."},"unchecked":{"instruction_type":"error","instruction_message":"Please agree to the terms."},"checked":{"instruction_type":"success","instruction_message":"I agree to the terms."}},"email":{"placeholder_text":"Your email address","initial":{"instruction_type":"normal","instruction_message":"What is your email address?"},"success":{"instruction_type":"success","instruction_message":"Enter your email address"},"blank":{"instruction_type":"error","instruction_message":"Enter your email address"},"not_an_email_address":{"instruction_type":"error","instruction_message":"Make sure you have entered a valid email address"}},"note_with_tip":{"placeholder_text":"Your note here...","initial":{"instruction_type":"normal","instruction_message":"Attach a note to your tip (optional)"},"empty":{"instruction_type":"normal","instruction_message":"Attach a note to your tip (optional)"},"not_empty_initial":{"instruction_type":"normal","instruction_message":"Attach a note to your tip (optional)"},"saving":{"instruction_type":"normal","instruction_message":"Saving note..."},"success":{"instruction_type":"success","instruction_message":"Note successfully saved!"},"error":{"instruction_type":"error","instruction_message":"Unable to save note note at this time. Please try again."}},"email_for_login_code":{"placeholder_text":"Your email address","initial":{"instruction_type":"normal","instruction_message":"Enter your email to log in."},"success":{"instruction_type":"success","instruction_message":"Enter your email to log in."},"blank":{"instruction_type":"error","instruction_message":"Enter your email to log in."},"empty":{"instruction_type":"error","instruction_message":"Enter your email to log in."}},"login_code":{"initial":{"instruction_type":"normal","instruction_message":"Check your email and enter the login code."},"success":{"instruction_type":"success","instruction_message":"Check your email and enter the login code."},"blank":{"instruction_type":"error","instruction_message":"Check your email and enter the login code."},"empty":{"instruction_type":"error","instruction_message":"Check your email and enter the login code."}},"stripe_all_in_one":{"initial":{"instruction_type":"normal","instruction_message":"Enter your credit card details here."},"empty":{"instruction_type":"error","instruction_message":"Enter your credit card details here."},"success":{"instruction_type":"normal","instruction_message":"Enter your credit card details here."},"invalid_number":{"instruction_type":"error","instruction_message":"The card number is not a valid credit card number."},"invalid_expiry_month":{"instruction_type":"error","instruction_message":"The card's expiration month is invalid."},"invalid_expiry_year":{"instruction_type":"error","instruction_message":"The card's expiration year is invalid."},"invalid_cvc":{"instruction_type":"error","instruction_message":"The card's security code is invalid."},"incorrect_number":{"instruction_type":"error","instruction_message":"The card number is incorrect."},"incomplete_number":{"instruction_type":"error","instruction_message":"The card number is incomplete."},"incomplete_cvc":{"instruction_type":"error","instruction_message":"The card's security code is incomplete."},"incomplete_expiry":{"instruction_type":"error","instruction_message":"The card's expiration date is incomplete."},"incomplete_zip":{"instruction_type":"error","instruction_message":"The card's zip code is incomplete."},"expired_card":{"instruction_type":"error","instruction_message":"The card has expired."},"incorrect_cvc":{"instruction_type":"error","instruction_message":"The card's security code is incorrect."},"incorrect_zip":{"instruction_type":"error","instruction_message":"The card's zip code failed validation."},"invalid_expiry_year_past":{"instruction_type":"error","instruction_message":"The card's expiration year is in the past"},"card_declined":{"instruction_type":"error","instruction_message":"The card was declined."},"missing":{"instruction_type":"error","instruction_message":"There is no card on a customer that is being charged."},"processing_error":{"instruction_type":"error","instruction_message":"An error occurred while processing the card."},"invalid_request_error":{"instruction_type":"error","instruction_message":"Unable to process this payment, please try again or use alternative method."},"invalid_sofort_country":{"instruction_type":"error","instruction_message":"The billing country is not accepted by SOFORT. Please try another country."}}}},"fetched_oembed_html":false}
[…] Better Perl: Four list proÂcessÂing best pracÂtices with map, grep, and more […]
Use List::Util::first instead of any to avoid name clashÂes and not worÂry about verÂsions of List::Util.
They are not equivÂaÂlent and using
first
instead ofany
can lead to bugs.first
will return the first eleÂment for which the conÂdiÂtion is true; if that eleÂment is itself a false valÂue (0, the null string, orundef
), the funcÂtion will evalÂuÂate to false even though the eleÂment was found.[…] Better Perl: Four list proÂcessÂing best pracÂtices with map, grep, and more […]