Inspired by my parents coming to visit at the end of the week, I thought I’d write about how Perl classes can have “parents” as well, from which they inherit methods. Although it might seem on the surface as though there’s more than one way to do it, these techniques all share the same underlying mechanism.
Perl classes are just repurposed packages, i.e., a namespace for variables and subroutines. The two key differences are:
Subroutines may expect either an object reference or the name of a class as their first argument.
When you call a method that isn’t defined in a class, Perl will search through the special @ISA array in the package for the name of a class that has that method. (You can change the search order with the mro (method resolution order) pragma.)
If you wanted to do everything by hand at the lowest level, you could make a subclass at compile time like this:
package Local::MyChildClass;
BEGIN { # don't do this:
require Local::MyParentClass;
push @ISA, 'Local::MyParentClass';
}
use base 'Local::MyParentClass'; # don't do this unless you're also using fields
You might see use base in older code especially if it’s also using the fields pragma. However, Perl developers discourage both as the former silences certain module loading errors while the latter is at odds with the object-oriented programming principle of encapsulation.
A couple of years ago my Newfold Digital colleague David Oswald created a fork of parent called parent::versioned that supports specifying the lowest version for superclasses. You call it like this:
use parent::versioned ['Local::MyParentClass' => 1.23];
Within an OO system
There are dozens of object-oriented programming systems on CPAN that provide syntactic sugar and extra features to Perl’s minimal but flexible basics. Two of the more popular ones, Moose and Moo, offer an extends keyword that you should use instead of use parent so that your subclasses may take advantage of their features:
package Local::MyChildClass;
use Moo;
extends 'Local::MyParentClass';
Moose can also specify a required superclass version:
package Local::MyChildClass;
use Moose;
extends 'Local::MyParentClass' => {-version => 1.23};
Also, use the MooseX::NonMoose module when extending non-Moose classes, again so you get Moose features even though your methods are coming from somewhere else:
package Local::MyMooseClass;
use Moose;
use MooseX::NonMoose;
extends 'Local::MyPlainParentClass';
The experimental Object::Pad module specifies a single superclass while defining the class name with an optional version. Per the author’s suggested file layout, including a required minimum version, it would look like:
use Object::Pad 0.41;
package Local::MyChildClass;
class Local::MyChildClass isa Local::MyParentClass 1.23;
Object::Pad and Corinna, its inspiration, are works in progress so this syntax isn’t set in stone. The latter’s designer Curtis “Ovid” Poe blogged earlier this week about considering a more self-consistent syntax.
To quote the Perl documentation, “multiple inheritance often indicates a design problem, but Perl always gives you enough rope to hang yourself with if you ask for it.” All the techniques described above except for Object::Pad support multiple inheritance by specifying a list of superclasses. For example:
package Local::MyChildClass;
use parent qw(Local::MyParentClass1 Local::MyParentClass2);
If you’re using roles instead of or on top of superclasses (I’ve seen both situations) and your OO system doesn’t support them on its own, you can use the Role::Tiny module, first by describing your role in one package and then consuming it in another:
package Local::DoesSomething;
use Role::Tiny;
...
1;
package Local::MyConsumer;
use Role::Tiny::With;
with 'Local::DoesSomething';
...
1;
Moo::Role uses Role::Tiny under the hood and Moo can compose roles from either. The syntax for both Moo and Moose is similar:
package Local::DoesSomething;
use Moo::Role; # or "use Moose::Role;"
...
1;
package Local::MyConsumer;
use Moo; # or "use Moose;"
with 'Local::DoesSomething';
...
1;
Object::Pad specifies roles with the role keyword, and both classes and roles use does to consume them:
use Object::Pad 0.56;
package Local::DoesSomething;
role Local::DoesSomething does Local::DoesSomethingElse;
...
1;
use Object::Pad 0.56;
package Local::MyConsumer;
class Local::MyConsumer does Local::DoesSomething;
...
1;
The previous caveat about possible changes to this syntax applies.
Like parent, (sort of) like child
Of course, the whole point of inheritance or role consumption is so your child or consumer class can reuse functions and methods. Each of the techniques above has its ways of overriding that code, from the Perl built-in SUPER pseudo-class to Moose’s override and super keywords, to Moose’s and Moo’s method modifiers. (You can use the latter outside of Moo since it’s provided by Class::Method::Modifiers.)
I’ve written about choosing between overriding and modifying methods before, and when it comes to Moose and Moo code I’m now on the side of using the around method modifier if a method needs to call an inherited or consumed method of the same name. Object::Pad doesn’t have method modifiers (yet), so classes that use it will have to satisfy themselves with SUPER in their methods with an :override attribute that will throw an error if a parent doesn’t also provide the same method.
The Parent Wrap
In the end, your choice of Perl OO system will determine how (or whether) you handle inheritance and may even be a deciding factor. Which would you choose? And more importantly, have I made my parents proud with this post?
{"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}