Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Object::Pad: Keeping your objects comfy

Object::Pad: Keeping your objects comfy

Perl's new core OO system offers speed and flexibility. It is evolving carefully to provide a well-thought-out solution. The Object::Pad system is written in Perl and is designed to evolve at the speed of, well, Perl. It provides us the opportunity to try new things, see if they really work, and reject the ones that don't.

The first basic step in using it is coming to terms with defining and constructing objects. This talk starts with a basic class using bless and looks at how to convert it to use O::P and where to go from there in managing the construction process. It's intended as a good place to start for converting code to use the new system or get a feel of where to begin when authoring a new class with it.

Avatar for Steven Lembark

Steven Lembark

July 01, 2025
Tweet

More Decks by Steven Lembark

Other Decks in Technology

Transcript

  1. Perl’s built-in OO mechanism Effectively generates closures for objects. Implemented

    in core Perl using C. Takes a while to integrate into Perl’s core code. Catch: Impatience is a virtue.
  2. Fix: Object::Pad Leading edge, including beta code. More easily changed.

    Not expected to be fully stable. Try it, you’ll like it...
  3. Fix: Object::Pad Leading edge, including beta code. More easily changed.

    Not expected to be fully stable. Try it, you’ll like it... … and complain when you don’t!!!
  4. Object::Pad Ecosystem All sorts of additions... Object::Pad a simple syntax

    for lexical field-based objects Object::Pad::Operator::Of access fields of other instances Object::Pad::FieldAttr::Isa apply class type constraints to Object::Pad fields Object::Pad::LexicalMethods operator for lexical method call syntax Object::Pad::SlotAttr::Final declare Object::Pad slots readonly after construction Object::Pad::FieldAttr::Final declare Object::Pad fields readonly after construction Object::Pad::ClassAttr::Struct declare an Object::Pad class to be struct-like Object::Pad::Keyword::Accessor declare lvalue accessors on Object::Pad classes Object::Pad::SlotAttr::Trigger invoke an instance method after a :writer accessor Object::Pad::FieldAttr::Checked apply value constraint checks to Object::Pad fields Object::Pad::FieldAttr::Trigger invoke an instance method after a :writer accessor Object::Pad::FieldAttr::LazyInit lazily initialise Object::Pad fields at first read Object::PadX::Log::Log4perl A logger role for Object::Pad based classes based on Log::Log4perl Object::PadX::Role::AutoJSON Object::Pad role that creates an automatic TO_JSON() method that serializes properly with JSON::XS... Object::PadX::Role::AutoMarshal Object::Pad role that tries to automatically create sub-objects during instantiation. … and more!!!
  5. Object::Pad Ecosystem Some of the toys you can play with:

    Operator::Of peek in other instances (testing). LexicalMethods lexical (private) methods. SlotAttr::Final readonly after construction. Keyword::Accessor lvalue accessors w/ boilerplate FieldAttr::isa type requirement for fields SlotAttr::Trigger post-write dispatch FieldAttr::Checked pre-write sanity checks
  6. Object::Pad Ecosystem All use O::P objects. Catch: You need to

    generate O::P objects to use them. Fix: POD in Object::Pad Object::Pad::Guide::MigratingFromClassicalPerl
  7. Object::Pad Ecosystem All use O::P objects. Catch: You need to

    generate O::P objects to use them. Fix: POD in Object::Pad Object::Pad::Guide::MigratingFromClassicalPerl And the next 45 minutes of your life...
  8. Converting to O::P Things you’ll have to get used to:

    Single inheritance. Decomposing classes into roles. Construction via default new(). The rest of this talk is about these.
  9. Converting to O::P The O::P model is thought out. Also

    evolving. Some of what’s documented is historic. Goal today is a working arrangement. T∞MWTDI
  10. Migration basics package Frobnicate v1.0; use mro::EVERY; sub init {

    state $verbose = $ENV{ VERBOSE } // ''; my $frob = shift; $frob->verbose = $verbose; } sub construct { my $proto = shift; bless \( my $a = '' ), blessed $proto || $proto } sub new { my $frob = &construct; $frob->EVERY::LAST::init( @_ ); $frob } sub debug { my $frob = shift; say join ‘ ‘ => @_ if $frob->verbose; } sub verbose :lvalue { state $verbose }; Project Frobnicate base.
  11. Migration basics package Frobnicate v1.0; use mro::EVERY; sub init {

    state $verbose = $ENV{ VERBOSE } // ''; my $frob = shift; $frob->verbose = $verbose; } sub construct { my $proto = shift; bless \( my $a = '' ), blessed $proto || $proto } sub new { my $frob = &construct; $frob->EVERY::LAST::init( @_ ); $frob } sub debug { my $frob = shift; say join ‘ ‘ => @_ if $frob->verbose; } sub verbose :lvalue { state $verbose }; Project Frobnicate base. Construction stack uses mro::EVERY::LAST
  12. Migration basics package Frobnicate v1.0; use mro::EVERY; sub init {

    state $verbose = $ENV{ VERBOSE } // ''; my $frob = shift; $frob->verbose = $verbose; } sub construct { my $proto = shift; bless \( my $a = '' ), blessed $proto || $proto } sub new { my $frob = &construct; $frob->EVERY::LAST::init( @_ ); $frob } sub debug { my $frob = shift; say join ‘ ‘ => @_ if $frob->verbose; } sub verbose :lvalue { state $verbose }; Project Frobnicate base. Construction stack uses mro::EVERY::LAST One attribute: verbose One method: debug
  13. Migration basics package Frobnicate v1.0; use v5.36; use Object::Pad v0.807;

    sub init { ... } sub construct { ... } sub new { ... } sub debug { my $frob = shift; say join ‘ ‘ => @_ if $frob->verbose; } sub verbose :lvalue { state $verbose }; Step one: Use at least v5.36. Use Object::Pad.
  14. Migration basics package Frobnicate v1.0; use v5.36; use Object::Pad v0.807;

    sub init { ... } sub construct { ... } sub new { ... } sub debug { my $frob = shift; say join ‘ ‘ => @_ if $frob->verbose; } sub verbose :lvalue { state $verbose }; Step one: Use at least v5.36. Use Object::Pad. O::P evolves, make sure to use a version!
  15. Migration basics package Frobnicate v1.0; use v5.36; use Object::Pad v0.807;

    class Frobnicate v1.0.0 ... ; sub init { ... } sub construct { ... } sub new { ... } sub debug { my $frob = shift; say join ‘ ‘ => @_ if $frob->verbose; } sub verbose :lvalue { state $verbose }; Step two: O::P has its own constructor. Replaces construction stack. “package” becomes “class”. verbose becomes a field. Class consumes the method.
  16. Defining a class use v5.36; # or later. use Object::Pad

    v0.807; # with a version. class Frobnicate v1.0.0 :repr( autoselect ) # “native” for internal format, “pvobj” in 5.38+, # “HASH” for ugly code, ‘magic’ for non-hash struct. :abstract # cannot create instances; derived, non-abstract class can create them. :strict( params ) # enforce unknown parameters rules. :isa( ... ) # provide a single base class (see also “inherit” keyword). :does( ... ) # supply list of roles fulfilled (see also “apply” keyword). { ... }
  17. Defining a class use v5.36; # or later. use Object::Pad

    v0.807; # with a version. class Frobnicate v1.0.0 :repr( autoselect ) # “native” for internal format, “pvobj” in 5.38+, # “HASH” for ugly code, ‘magic’ for non-hash struct. :abstract # cannot create instances; derived, non-abstract class can create them. :strict( params ) # enforce unknown parameters rules. :isa( ... ) # provide a single base class (see also “inherit” keyword). :does( ... ) # supply list of roles fulfilled (see also “apply” keyword). { ... }
  18. Defining a class use v5.36; # or later. use Object::Pad

    v0.807; # with a version. class Frobnicate v1.0.0 :repr( autoselect ) # “native” for internal format, “pvobj” in 5.38+, # “HASH” for ugly code, ‘magic’ for non-hash struct. :abstract # cannot create instances; derived, non-abstract class can create them. :strict( params ) # enforce unknown parameters rules. :isa( ... ) # provide a single base class (see also “inherit” keyword). :does( ... ) # supply list of roles fulfilled (see also “apply” keyword). { ... }
  19. Defining a class use v5.36; # or later. use Object::Pad

    v0.807; # with a version. class Frobnicate v1.0.0 :repr( autoselect ) # “native” for internal format, “pvobj” in 5.38+, # “HASH” for ugly code, ‘magic’ for non-hash struct. :abstract # cannot create instances; derived, non-abstract class can create them. :strict( params ) # enforce unknown parameters rules. :isa( ... ) # provide a single base class (see also “inherit” keyword). :does( ... ) # supply list of roles fulfilled (see also “apply” keyword). { ... }
  20. Defining a class use v5.36; # or later. use Object::Pad

    v0.807; # with a version. class Frobnicate v1.0.0 :repr( autoselect ) # “native” for internal format, “pvobj” in 5.38+, # “HASH” for ugly code, ‘magic’ for non-hash struct. :abstract # cannot create instances; derived, non-abstract class can create them. :strict( params ) # enforce unknown parameters rules. :isa( ... ) # provide a single base class (see also “inherit” keyword). :does( ... ) # supply list of roles fulfilled (see also “apply” keyword). { ... }
  21. Defining a class use v5.36; # or later. use Object::Pad

    v0.807; # with a version. class Frobnicate v1.0.0 :repr( autoselect ) # “native” for internal format, “pvobj” in 5.38+, # “HASH” for ugly code, ‘magic’ for non-hash struct. :abstract # cannot create instances; derived, non-abstract class can create them. :strict( params ) # enforce unknown parameters rules. :isa( ... ) # provide a single base class (see also “inherit” keyword). :does( ... ) # supply list of roles fulfilled (see also “apply” keyword). { ... }
  22. Defining a class use v5.36; # or later. use Object::Pad

    v0.807; # with a version. class Frobnicate v1.0.0 :repr( autoselect ) # “native” for internal format, “pvobj” in 5.38+, # “HASH” for ugly code, ‘magic’ for non-hash struct. :abstract # cannot create instances; derived, non-abstract class can create them. :strict( params ) # enforce unknown parameters rules. :isa( ... ) # provide a single base class (see also “inherit” keyword). :does( ... ) # supply list of roles fulfilled (see also “apply” keyword). { ... }
  23. Defining a class use v5.36; # or later. use Object::Pad

    v0.807; # with a version. class Frobnicate v1.0.0 :repr( autoselect ) # “native” for internal format, “pvobj” in 5.38+, # “HASH” for ugly code, ‘magic’ for non-hash struct. :abstract # cannot create instances; derived, non-abstract class can create them. :strict( params ) # enforce unknown parameters rules. :isa( ... ) # provide a single base class (see also “inherit” keyword). :does( ... ) # supply list of roles fulfilled (see also “apply” keyword). { ... } ; # like a subroutine, no closing ‘;’ required – though it doesn’t hurt.
  24. Defining a class use v5.36; use Object::Pad v0.807; class Frobnicate

    v1.0.0 :repr( autoselect ) :abstract :strict( params ) :isa( ... ) :does( ... ) { # class definition: fields, methods... # each object closes over the block. # fields are class scoped, don’t behave # entirely like lexical var’s. see the # O::P documentation for file layout # for working examples. ... } Objects close over the block. Fields are private to the class. Values are private to objects. Examples: https://metacpan.org/pod/ Object::Pad#File-Layout
  25. Defining a class use v5.36; use Object::Pad v0.807; class Frobnicate

    v1.0.0 :repr( autoselect ) :abstract :strict( params ) :isa( ... ) :does( ... ) { inherit OneBaseClass v1; apply ThisRole v2; apply ThatRole v0.2; ... } “inherit” and “apply”. Tradeoff: :isa & :does are a bit more line-noise-ish, but amenable to code generation and review (they’re in one place). More about this later...
  26. Defining a class use v5.40; use Object::Pad v0.807; class Frobnicate

    v1.0.0 :repr( pvobj ) :abstract :strict( params ) { field $verbose; method debug { say join ‘ ‘ => @_ if $verbose; } } v5.38+ allows more effecient “pvobj” storage. One field One method.
  27. Defining a class use v5.40; use Object::Pad v0.807; class Frobnicate

    v1.0.0 :repr( pvobj ) :abstract :strict( params ) { field $verbose; method debug { say join ‘ ‘ => @_ if $verbose; } } $verbose field visible to methods as a scalar.
  28. Defining a class use v5.40; use Object::Pad v0.807; class Frobnicate

    v1.0.0 :repr( pvobj ) :abstract :strict( params ) { field $verbose; method debug { say join ‘ ‘ => @_ if $verbose; } } method: object. has “$self”. Class methods: use “sub”. No $self. Examples later.
  29. Defining a class use v5.40; use Object::Pad v0.807; class Frobnicate

    v1.0.0 :repr( pvobj ) :abstract :strict( params ) { field $verbose; method debug { say join ‘ ‘ => @_ if $verbose; } } Outline of a class: Perl version. O::P with version. Name + attributes + block.
  30. Defining fields use v5.40; use Object::Pad v0.807; class Frobnicate v1.0.0

    :repr( pvobj ) :abstract :strict( params ) { # my $v = $frob->verbose; # $frob->set_verbose( 1 ); field $verbose :reader :writer; field @foo; field %bar; method debug { say join ‘ ‘ => @_ if $verbose; } } : :reader creates a read method. :writer creates a … guess? Both return $self for chaining. Pick your type: field @foo; field %bar; field *bim;
  31. Defining fields use v5.40; use Object::Pad v0.807; class Frobnicate v1.0.0

    :repr( pvobj ) :abstract :strict( params ) { # my $v = $frob->verbose; # $frob->set_verbose( 1 ); field $verbose :reader :writer; method debug { say join ‘ ‘ => @_ if $verbose; } } : :reader creates a read method. :writer creates a set_*() You can always add your own accessors: format output (enum). validate input.
  32. Defining fields use v5.40; use Object::Pad v0.807; class Frobnicate v1.0.0

    :repr( pvobj ) :abstract :strict( params ) { # my $v = $frob->get_verbose; # $frob->set_verbose( 1 ); field $verbose :reader(get_verbose) :writer; method debug { say join ‘ ‘ => @_ if $verbose; } } : Both take a name argument. Handy if existing code uses get_/set_ standard for accessors.
  33. Defining fields use v5.40; use Object::Pad v0.807; class Frobnicate v1.0.0

    :repr( pvobj ) :abstract :strict( params ) { # my $v = $frob->verbose; # $frob->verbose = 1; field $verbose :mutator; method debug { say join ‘ ‘ => @_ if $verbose; } } : :mutator installs an lvalue Only handles scalar fields.
  34. Defining fields use v5.40; use Object::Pad v0.807; class Frobnicate v1.0.0

    :repr( pvobj ) :abstract :strict( params ) { # my $v = $frob->verbose; # $frob->verbose = 1; field $verbose :mutator; method debug { say join ‘ ‘ => @_ if $verbose; } } : :mutator installs an lvalue Only handles scalar fields. Anything more complicated than a single value probably deserves hardwired accessors.
  35. Defining fields use v5.40; use Object::Pad v0.807; class Frobnicate v1.0.0

    :repr( pvobj ) :abstract :strict( params ) { # my $v = $frob->is_debug; # $frob->is_debug = 1; field $verbose :mutator( is_debug ); method debug { say join ‘ ‘ => @_ if $verbose; } } : :mutator installs an lvalue Only handles scalar fields. Also takes a name. Handy for providing saner API without hacking method guts.
  36. Roles simplify reuse use v5.40; use Object::Pad v0.807.0; class Frobnicate

    v1.0.0 :repr( pvobj ) :abstract :strict( params ) :does( Logger ) { fields $verbose :mutator; } role Logger v1.0.0 { requires verbose; field $moon_phase :reader = ‘’; method debug { say join ‘ ‘ => @_ if $self->verbose } method info { say join ‘ ‘ => @_ } } “:does” composes role into the class. Role methods visible in the class: $frob->debug( … ); $frob->info( … );
  37. Roles simplify reuse use v5.40; use Object::Pad v0.807.0; class Frobnicate

    v1.0.0 :repr( pvobj ) :abstract :strict( params ) :does( Logger ) { fields $verbose :mutator; } role Logger v1.0.0 { requires verbose; field $moon_phase :reader = ‘’; method debug { say join ‘ ‘ => @_ if $self->verbose } method info { say join ‘ ‘ => @_ } } “:does” composes role into the class. Role methods visible in the class: $frob->debug( … ); $frob->info( … ); Role fields are private.
  38. Roles simplify reuse “apply” replaces :does with more future-proof syntax.

    But... Existing Perl has “:does” Attribute simpler for code generator/review packages. Artistic decision. use v5.40; use Object::Pad v0.807.0; class Frobnicate v1.0.0 :repr( pvobj ) :abstract :strict( params ) { apply Logger; fields $verbose :mutator; } role Logger v1.0.0 { requires verbose; field $moon_phase :reader = ‘’; method debug { say join ‘ ‘ => @_ if $self->verbose } method info { say join ‘ ‘ => @_ } }
  39. Roles simplify reuse “requires” checks a class. use v5.40; use

    Object::Pad v0.807.0; class Frobnicate v1.0.0 :repr( pvobj ) :abstract :strict( params ) :does( Logger ) { fields $verbose :mutator; } role Logger v1.0.0 { requires verbose; field $moon_phase :reader = ‘’; method debug { say join ‘ ‘ => @_ if $self->verbose } method info { say join ‘ ‘ => @_ } }
  40. Roles simplify reuse “requires” checks class. :mutator defines method. requires

    validates it exists. $self->verbose won’t fail at runtime. use v5.40; use Object::Pad v0.807.0; class Frobnicate v1.0.0 :repr( pvobj ) :abstract :strict( params ) :does( Logger ) { fields $verbose :mutator; } role Logger v1.0.0 { requires verbose; field $moon_phase :reader = ‘’; method debug { say join ‘ ‘ => @_ if $self->verbose } method info { say join ‘ ‘ => @_ } }
  41. Roles simplify reuse Division of labor: Class has control over

    field: sets default, defines accessors. Role uses them via accessor. use v5.40; use Object::Pad v0.807.0; class Frobnicate v1.0.0 :repr( pvobj ) :abstract :strict( params ) :does( Logger ) { fields $verbose :mutator; } role Logger v1.0.0 { requires verbose; field $moon_phase :reader = ‘’; method debug { say join ‘ ‘ => @_ if $self->verbose } method info { say join ‘ ‘ => @_ } }
  42. Roles simplify migration role Logger v1.0.0 :compat( invokable ) {

    ‘invokable’ attribute allows calling role methods with non-O::P objects. Simplifies migration: recycle ore-existing objects into classes during transition. Not for permanent use.
  43. What’s nu? O::P has a default new() operator. Uses flat

    key => value arguments. Has several phasers to simplify use. Sorry, nothing you can set on stun...
  44. What’s nu? O::P has a default new() operator. Uses flat

    key => value arguments. Has several phasers to simplify use. BUILDARGS handle :param attributes BUILD & ADJUST strict checking
  45. What’s nu? O::P has a default new() operator. Uses flat

    key => value arguments. Has several phasers to simplify use. BUILDARGS handle :param attributes BUILD & ADJUST All you really need. strict checking
  46. :param and new Param attribute makes the key a new

    parameter: field $bar :param :mutator; Foo->new( bar => 42 );
  47. :param and new Standardize argument passing: new( key => value,

    ... ) flat list of scalars. Instead of these: ( 1, 2 ) ( ‘foo=1’ , ‘bar=2’ ) ( ‘+foo’, ‘-bar’ ) q{ JSON... } +{ foo => 1, bar => 2 }
  48. :param and new Standardize argument passing: new( key => value,

    ... ) flat list of scalars. Use these: ( 1, 2 ) foo => 1, bar => 2 ( ‘foo=1’ , ‘bar=2’ ) foo => 1, bar => 2 ( ‘+foo’, ‘-bar’ ) foo => 1, bar => ‘’ q{ JSON... } JSON => $JSON_STRING +{ foo => 1, bar => 2 } $hashref->%*
  49. :param standardizes argument handling use v5.40; use Object::Pad v0.807.0; class

    Frobnicate v1.0.0 :repr( pvobj ) :abstract :strict( params ) :does( Logger ) { field $verbose :param :mutator ; } Frobnicate::new takes a single parameter: Frobnicate->new( verbose => 1 )
  50. :param standardizes argument handling use v5.40; use Object::Pad v0.807.0; class

    Frobnicate v1.0.0 :repr( pvobj ) :abstract :strict( params ) :does( Logger ) { field $verbose :param :mutator ; } Frobnicate::new takes a single parameter: Frobnicate->new( verbose => 1 ) :param sans default creates a required argument to new. Multiple ways to handle this...
  51. field defaults use v5.40; use Object::Pad v0.807.0; class Frobnicate v1.0.0

    :repr( pvobj ) :abstract :strict( params ) :does( Logger ) { field $verbose :param :mutator = $ENV{ VERBOSE } // ‘’ ; } field can assign a default. Default assigned by new().
  52. field defaults use v5.40; use Object::Pad v0.807.0; class Frobnicate v1.0.0

    :repr( pvobj ) :abstract :strict( params ) :does( Logger ) { field $verbose :param :mutator = do { state $default = $ENV{ VERBOSE } // ‘’ }; } Catch: %ENV IS GLOBAL! Change during execution. Fix, obviously: A closure. $default stable after first call.
  53. field defaults use v5.40; use Object::Pad v0.807.0; class Frobnicate v1.0.0

    :repr( pvobj ) :abstract :strict( params ) :does( Logger ) { my $def_verbose = sub { # delay reading until runtime... }; field $verbose :param :mutator = $def_verbose->() ; } Non-trivial defaults can be handled by subs. NO access to object. Lexically scoped subs keep the initialization private to the class.
  54. field defaults use v5.40; use Object::Pad v0.807.0; class Frobnicate v1.0.0

    :repr( pvobj ) :abstract :strict( params ) :does( Logger ) { my sub def_verbose = sub { # ditto with a lexical sub }; field $verbose :param :mutator = def_verbose ; } Newer “my sub” syntax is nice for class-scoped initializers.
  55. field defaults use v5.40; use Object::Pad v0.807.0; class Frobnicate v1.0.0

    :repr( pvobj ) :abstract :strict( params ) :does( Logger ) { field $config :reader = Object::Trampoline->open ( # delay constructing until # it’s actually needed :-) ’Frobnicate::Config’ , $ENV{ CONFIG_URL } ); field $verbose :param :mutator = $config->value( ‘verbose’ ) ; } Trampoline objects delay construction. Nice for things like dictionaries service connections heavier config's
  56. field defaults use v5.40; use Object::Pad v0.807.0; class Frobnicate v1.0.0

    :repr( pvobj ) :abstract :strict( params ) :does( Logger ) { field $config :reader = Object::Trampoline->open ( # delay constructing until # it’s actually needed :-) ’Frobnicate::Config’ , $ENV{ CONFIG_URL } ); field $verbose :param :mutator = $config->value( ‘verbose’ ) ; } Earlier fields are visible to later ones, as with lexicals in a block.
  57. field defaults use v5.40; use Object::Pad v0.807.0; class Frobnicate v1.0.0

    :repr( pvobj ) :abstract :strict( params ) :does( Logger ) { field $config :reader = Object::Trampoline->open ( # delay constructing until # it’s actually needed :-) ’Frobnicate::Config’ , $ENV{ CONFIG_URL } ); field $verbose :param :mutator = $config->value( ‘verbose’ ) ; } :reader leaves $frob->config() available to derived classes: $frob ->config ->value( ‘foobar’ );
  58. field defaults from roles use v5.40; use Object::Pad v0.807.0; use

    Object::Trampoline; class Frobnicate v1.0.0 :repr( pvobj ) :abstract :strict( params ) :does(Logger) :does( Frobnicate::Config ) { field $verbose :reader = Frobnicate->config->read( ‘verbose’ ) } role Frobnicate::Config v1.0.0 { field $config :reader = Object::Trampoline->open ( ’Frobnicate::Config’ , $ENV{ CONFIG_URL } ); } Makes a nice role: One field. One method via :reader.
  59. field defaults from roles use v5.40; use Object::Pad v0.807.0; use

    Object::Trampoline; role Frobnicate::Config v1.0.0 { my sub open_config { # validate at runtime $ENV{ CONFIG_URL } or die “Bogus Config: ...”; } field $config_url :reader = open_config; field $config :reader = Object::Trampoline->open ( ’Frobnicate::Config’ , $config_url ); } Makes a nice role: One field. One method via :reader. Extend the role to modify the path, re-read configs, validate, etc. Helps keep base class clean.
  60. fields can interact on assignment use v5.40; use Object::Pad v0.807.0;

    class Frobnicate v1.0.0 :repr( pvobj ) :abstract :strict( params ) :does( Logger ) :does( Frobnicate::Config ) { field $init_verbose :reader = Frobnicate ->config ->value( ‘verbose’ ); field $verbose :mutator = $init_verbose; method reset_verbose { $verbose = $init_verbose }; } Successive fields can cache initial values. $init_verbose caches the initial value of $verbose. :reader for checking if the value has changed.
  61. What’s nu? BUILDARGS preceeds :param handling. Makes the stack usable

    for :param fields. Called from least-to-most derived classes. Often only need one of these.
  62. BUILDARGS Class method: sub, regular stack, no $self. class Frobnicate

    v1.0.0 { field $verbose :param :mutator = $ENV{ VERBOSE } || ‘’; apply Logger v1.0; sub BUILDARGS # class method: “sub” not “method”, no $self. { my ( undef, %argz ) = @_; # ignore the class name, assume k => v already used # e.g., old system used case-insensitive parameters # fix: force lower case. m{\u} and $argz{ lc $_ } = delete $argz{ $_ } for keys %argz; %argz # k => v list now matches param names. } }
  63. BUILDARGS Munge @_ into K => V with :param field

    named. class Frobnicate v1.0.0 { field $verbose :param :mutator = $ENV{ VERBOSE } || ‘’; apply Logger v1.0; sub BUILDARGS # class method: “sub” not “method”, no $self. { my ( undef, %argz ) = @_; # ignore the class name, assume k => v already used # e.g., old system used case-insensitive parameters # fix: force lower case. m{\u} and $argz{ lc $_ } = delete $argz{ $_ } for keys %argz; %argz # k => v list now matches param names. } }
  64. BUILDARGS @_ = $class->BUILDARGS( @_ ); class Frobnicate v1.0.0 {

    field $verbose :param :mutator = $ENV{ VERBOSE } || ‘’; apply Logger v1.0; sub BUILDARGS # class method: “sub” not “method”, no $self. { my ( undef, %argz ) = @_; # ignore the class name, assume k => v already used # e.g., old system used case-insensitive parameters # fix: force lower case. m{\u} and $argz{ lc $_ } = delete $argz{ $_ } for keys %argz; %argz # k => v list now matches param names. } }
  65. BUILDARGS Whatever it takes to get K => V. class

    Frobnicate v1.0.0 { field $verbose :param :mutator = $ENV{ VERBOSE } || ‘’; apply Logger v1.0; sub BUILDARGS { shift; # old system used ( “a=1”, “b=2” ) strings. # split them into a new stack. map { ($k,$v) = split ‘=’, $_, 2; ( $k => $v ) } @_ } }
  66. BUILDARGS Can assign defaults. Usually a bad idea: Splits up

    field definition. class Frobnicate v1.0.0 :repr( pvobj ) :abstract :strict( params ) { field $verbose :param :mutator = $ENV{ VERBOSE } // ‘’; sub BUILDARGS { my ( $proto, %argz ) = @_; state $def_verbose= $ENV{ VERBOSE } // ‘’; # possible disconnect or missing default. $argz{ verbose } //= $def_verbose; %argz } }
  67. BUILDARGS SUPER/NEXT chaining is not automatic! class Frobnicate::Foobar v1.0.0 {

    inherit Frobnicate; # derive from a single base class. sub BUILDARGS { my $proto= shift; # grab prototype off the stack! my %argz = $proto->SUPER::BUILDARGS( @_ ); # dispatch to base class FIRST! # two of the flat-hash values need to be # split one more time to fully flatten them. my @atomic = map { split ‘:’, $_, 2 } delete @argz{ qw( foo bar ) }; ( @atomic, %argz ) } }
  68. BUILDARGS Then remove the cruft from %argz class Frobnicate::Foobar v1.0.0

    { inherit Frobnicate; # derive from a single base class. sub BUILDARGS { my $proto= shift; # grab prototype off the stack! my %argz = $proto->SUPER::BUILDARGS( @_ ); # dispatch to base class FIRST! # two of the flat-hash values need to be # split one more time to fully flatten them. my @atomic = map { split ‘:’, $_, 2 } delete @argz{ qw( foo bar ) }; ( @atomic, %argz ) } }
  69. BUILDARGS Return flat list for @_. class Frobnicate::Foobar v1.0.0 {

    inherit Frobnicate; # derive from a single base class. sub BUILDARGS { my $proto= shift; # grab prototype off the stack! my %argz = $proto->SUPER::BUILDARGS( @_ ); # dispatch to base class FIRST! # two of the flat-hash values need to be # split one more time to fully flatten them. my @atomic = map { split ‘:’, $_, 2 } delete @argz{ qw( foo bar ) }; ( @atomic, %argz ) } }
  70. What’s nu? When BUILDARGS is complete. The stack is now

    K => V for any :params fields. :param fields are assigned from the stack. defaults are processed for the rest. :param fields require stack value or in-class default. Missing params are fatal. Fields without :param are handled in BUILD/ADJUST. Preferally ADJUST.
  71. BUILD Looks like a package BEGIN block. Before the object

    is fully constructed. Has args, but no $self – no access to object methods. class Frobnicate v1.0.0 :repr( pvobj ) :abstract :strict( params ) { field $verbose :param :mutator; field $obj_id :reader; apply Logger v1.0; ... BUILD # block, not a sub! { my( undef, %argz )= @_; # guaranteed validated k => v list from BUILDARGS ... } }
  72. What’s nu? ADJUST called in class order after :param &

    BUILD. Similar to CHECK block in Perl modules. Has constructed $self with :param values & the stack. Multi-field assignment. Adjust field values. Validate & log object’s initial state. Clean up stack for strict checking.
  73. ADJUST completes the object Named block – no “sub”. Usually

    has signiture & :params attribute. class Frobnicate v1.0.0 :repr( pvobj ) :abstract :strict( params ) { field $verbose :param :mutator = ‘’; apply Logger v1.0; ADJUST :params ( %argz ) # signiture pushes non-param values into hash. { $self->info( Verbosity => $verbose ) if $self->verbose; $self->debug( ‘Post processing:’, \%argz ); } }
  74. ADJUST completes the object Named block with a signature. Has

    $self & :param fields. class Frobnicate v1.0.0 :repr( pvobj ) :abstract :strict( params ) { field $verbose :param :mutator = ‘’; apply Logger v1.0; ADJUST :params ( %argz ) { $self->info( Verbosity => $verbose ) # $self and initialized fields. if $self->verbose; $self->debug( ‘Post processing:’, \%argz ); } }
  75. ADJUST completes the object Derived class: No BUILDARGS. Rely on

    base class. ADJUST is called after base class’. Inhertis debug() from Logging class Frobnicate::UnitVector v1.0.0 :isa( Frobnicate ) { use Carp qw( croak ); our @CARP_NOT = qw( Object::Pad ); field $x :param :reader = 0.0 ; field $y :param :reader = 0.0 ; field $z :param :reader = 0.0 ; field $mag :reader ; ADJUST { $x != 0.0 || $y != 0.0 || $z != 0.0 or croak “Zero magnitude vector: $x, $y, $z”; $mag = sqrt( $x ** 2 + $y ** 2 + $z ** 2 ) or croak “Precision issue: zero magnitude $x, $y, $z”; $_ /= $mag for $x, $y, $z; $x != 0.0 || $y != 0.0 || $z != 0.0 or croak “Precision issue scaling ‘$mag, co-ords all zero”; $self->debug( “Magnitude: $mag” ); } }
  76. ADJUST completes the object All :params assigned. $mag lacks :param

    class Frobnicate::UnitVector v1.0.0 :isa( Frobnicate ) { use Carp qw( croak ); our @CARP_NOT = qw( Object::Pad ); field $x :param :reader = 0.0 ; field $y :param :reader = 0.0 ; field $z :param :reader = 0.0 ; field $mag :reader ; ADJUST { $x != 0.0 || $y != 0.0 || $z != 0.0 or croak “Zero magnitude vector: $x, $y, $z”; $mag = sqrt( $x ** 2 + $y ** 2 + $z ** 2 ) or croak “Precision issue: zero magnitude $x, $y, $z”; $_ /= $mag for $x, $y, $z; $x != 0.0 || $y != 0.0 || $z != 0.0 or croak “Precision issue scaling ‘$mag, co-ords all zero”; $self->debug( “Magnitude: $mag” ); } }
  77. ADJUST completes the object Validate values. String inputs may exceed

    precision, leave us with 0.0. class Frobnicate::UnitVector v1.0.0 :isa( Frobnicate ) { use Carp qw( croak ); our @CARP_NOT = qw( Object::Pad ); field $x :param :reader = 0.0 ; field $y :param :reader = 0.0 ; field $z :param :reader = 0.0 ; field $mag :reader ; ADJUST { $x != 0.0 || $y != 0.0 || $z != 0.0 or croak “Zero magnitude vector: $x, $y, $z”; $mag = sqrt( $x ** 2 + $y ** 2 + $z ** 2 ) or croak “Precision issue: zero magnitude $x, $y, $z”; $_ /= $mag for $x, $y, $z; $x != 0.0 || $y != 0.0 || $z != 0.0 or croak “Precision issue scaling ‘$mag, co-ords all zero”; $self->debug( “Magnitude: $mag” ); } }
  78. ADJUST completes the object Compute & validate derived value. Ditto

    squaring small values. class Frobnicate::UnitVector v1.0.0 :isa( Frobnicate ) { use Carp qw( croak ); our @CARP_NOT = qw( Object::Pad ); field $x :param :reader = 0.0 ; field $y :param :reader = 0.0 ; field $z :param :reader = 0.0 ; field $mag :reader ; ADJUST { $x != 0.0 || $y != 0.0 || $z != 0.0 or croak “Zero magnitude vector: $x, $y, $z”; $mag = sqrt( $x ** 2 + $y ** 2 + $z ** 2 ) or croak “Precision issue: zero magnitude $x, $y, $z”; $_ /= $mag for $x, $y, $z; $x != 0.0 || $y != 0.0 || $z != 0.0 or croak “Precision issue scaling ‘$mag, co-ords all zero”; $self->debug( “Magnitude: $mag” ); } }
  79. ADJUST completes the object Adjust :param values to magnitude. class

    Frobnicate::UnitVector v1.0.0 :isa( Frobnicate ) { use Carp qw( croak ); our @CARP_NOT = qw( Object::Pad ); field $x :param :reader = 0.0 ; field $y :param :reader = 0.0 ; field $z :param :reader = 0.0 ; field $mag :reader ; ADJUST { $x != 0.0 || $y != 0.0 || $z != 0.0 or croak “Zero magnitude vector: $x, $y, $z”; $mag = sqrt( $x ** 2 + $y ** 2 + $z ** 2 ) or croak “Precision issue: zero magnitude $x, $y, $z”; $_ /= $mag for $x, $y, $z; $x != 0.0 || $y != 0.0 || $z != 0.0 or croak “Precision issue scaling ‘$mag, co-ords all zero”; $self->debug( “Magnitude: $mag” ); } }
  80. ADJUST completes the object Final sanity check. At this point

    we have a unit vector with reasonable co-ords and magnitude. class Frobnicate::UnitVector v1.0.0 :isa( Frobnicate ) { use Carp qw( croak ); our @CARP_NOT = qw( Object::Pad ); field $x :param :reader = 0.0 ; field $y :param :reader = 0.0 ; field $z :param :reader = 0.0 ; field $mag :reader ; ADJUST { $x != 0.0 || $y != 0.0 || $z != 0.0 or croak “Zero magnitude vector: $x, $y, $z”; $mag = sqrt( $x ** 2 + $y ** 2 + $z ** 2 ) or croak “Precision issue: zero magnitude $x, $y, $z”; $_ /= $mag for $x, $y, $z; $x != 0.0 || $y != 0.0 || $z != 0.0 or croak “Precision issue scaling ‘$mag, co-ords all zero”; $self->debug( “Magnitude: $mag” ); } }
  81. ADJUST completes the object Avoid O::P stack with carp. Avoids

    need for confess to get past Object::Pad calls on stack. class Frobnicate::UnitVector v1.0.0 :isa( Frobnicate ) { use Carp qw( croak ); our @CARP_NOT = qw( Object::Pad ); field $x :param :reader = 0.0 ; field $y :param :reader = 0.0 ; field $z :param :reader = 0.0 ; field $mag :reader ; ADJUST { $x != 0.0 || $y != 0.0 || $z != 0.0 or croak “Zero magnitude vector: $x, $y, $z”; $mag = sqrt( $x ** 2 + $y ** 2 + $z ** 2 ) or croak “Precision issue: zero magnitude $x, $y, $z”; $_ /= $mag for $x, $y, $z; $x != 0.0 || $y != 0.0 || $z != 0.0 or croak “Precision issue scaling ‘$mag, co-ords all zero”; $self->debug( “Magnitude: $mag” ); } }
  82. ADJUST cleans off the stack %argz has unused k:v content.

    Anything passed but not consumed by a :param k:v. Could handle in BUILDARGS. Still require ADJUST. class Frobnicate::Database v1.0.0 :isa( Frobnicate ) :does( FileConfig ) { use Carp qw( croak ); our @CARP_NOT = qw( Object::Pad ); field $dsn :reader; # no param, just the field field $dbh :reader; ADJUST :params ( %argz ) { state $dbi_flagz = { qw( RaiseError 1 AutoCommit 0 ) }; state $def_creds = $self->read_config( ‘dsn’ ); $dsn = first { $_ } delete @argz{ qw( cred authn ) }, $def_creds or die “Botched config: lacks ‘cred’, ‘authn’ & ‘dsn’”; $dbh = eval { DBI->connect( $dsn , $dbi_flagz ) } or die “Botched config: unusable ‘$dsn’, $@”; } }
  83. ADJUST cleans off the stack Existing system uses variable keys.

    delete $argz{...} avoids complaints from strict params. Each ADJUST should clean up non-param keys from its class. class Frobnicate::Database v1.0.0 :isa( Frobnicate ) :does( FileConfig ) { use Carp qw( croak ); our @CARP_NOT = qw( Object::Pad ); field $dsn :reader; field $dbh :reader; ADJUST :params ( %argz ) { state $dbi_flagz = { qw( RaiseError 1 AutoCommit 0 ) }; state $def_creds = $self->read_config( ‘dsn’ ); $dsn = first { $_ } delete @argz{ qw( cred authn ) }, $def_creds or die “Botched config: lacks ‘cred’, ‘authn’ & ‘dsn’”; $dbh = eval { DBI->connect( $dsn , $dbi_flagz ) } or die “Botched config: unusable ‘$dsn’, $@”; } }
  84. ADJUST cleans off the stack ADJUST can use variables to

    consume the stack. $creds & $authn off the stack into lexicals. Better if param used in multiple levels. class Frobnicate::Database v1.0.0 :isa( Frobnicate ) :does( FileConfig ) { use Carp qw( croak ); our @CARP_NOT = qw( Object::Pad ); field $dsn :reader; field $dbh :reader; ADJUST :params ( :$creds :$authn ) { state $dbi_flagz = { qw( RaiseError 1 AutoCommit 0 ) }; state $def_creds = $self->read_config( ‘dsn’ ); $dsn = $creds || $authn || $def_creds or die “Botched config: lacks ‘creds’ & ‘dsn’”; $dbh = eval { DBI->connect( $dsn , $dbi_flagz ) } or die “Botched config: unusable ‘$dsn’, $@”; } }
  85. What’s nu? After ADJUST strict param checking is run if

    classes request it. It issues a warning if %argz is non-empty.
  86. What’s nu? Aside: I’ve ignored ADJUSTARGS. Please consider it a

    failed experiment. Lots more detail in the Migration and O::P POD.
  87. What’s nu? :param fields with defaults for automated handling. BUILDARGS

    form input to K => V pairs. ADJUST manages post-param housekeeping & logging.
  88. Object::Pad structure Not that much syntax: class :repr :abstract :does

    :isa role :compat requires field :param :reader :writer :mutator default BUILDARGS ADJUST :param %argz :$foo
  89. Bedside Reading As always, metacpan.org is your favorite hangout: Object::Pad

    Keywords, basic structure, file-layout Object::Pad::Guide::MigratingFromClassicalPerl How-to for switching over.