Badger::Class(3pm)

πŸ“¦
version 0.16 Β· 4 versions
, in debian trixie, ubuntu noble, ubuntu resolute

okay so bestie, this is Badger::Class(3pm) and it's literally the class metaprogramming module that makes Perl modules slay. no cap. πŸ’…

✨ NAME ✨

Badger::Class β€” class metaprogramming module πŸ’–


🌈 SYNOPSIS 🌈

           # composing a new module
           package Your::Module;

           use Badger::Class
               base        => 'Badger::Base',  # define base class(es)
               version     => 1.00,            # sets $VERSION
               debug       => 0,               # sets $DEBUG
               throws      => 'wobbler',       # sets $THROWS error type
               import      => 'class',         # import class() subroutine
               utils       => 'blessed params',# imports from Badger::Utils
               codec       => 'storable',      # imports from Badger::Codecs
               codecs      => 'base64 utf8'    # codecs do encode/decode
               constants   => 'TRUE FALSE',    # imports from Badger::Constants
               constant    => {                # define your own constants
                   pi      => 3.14,
                   e       => 2.718,
               },
               words       => 'yes no quit',   # define constant words
               accessors   => 'foo bar',       # create accessor methods
               mutators    => 'wiz bang',      # create mutator methods
               as_text     => 'text',          # auto-stringify via text() method
               is_true     => 1,               # overload boolean operator
               overload    => {                # overload other operators
                   '>'     => 'more_than',
                   '<'     => 'less_than',
               },
               vars        => {
                   '$FOO'  => 'Hello World',   # defines $FOO package var
                   '@BAR'  => [10,20,30],      # defines @BAR
                   '%BAZ'  => {x=>10, y=>20},  # defines %BAZ
                   # leading '$' is optional for scalar package vars
                   WIZ     => 'Hello World',   # defines $WIZ as scalar value
                   WAZ     => [10,20,30],      # defines $WAZ as list ref
                   WOZ     => {a=>10,y=>20},   # defines $WOZ as hash ref
                   WUZ     => sub { ... },     # defines $WUZ as code ref
               },
               methods     => {                # create/bind methods
                   wam     => sub { ... },
                   bam     => sub { ... },
               },
               exports     => {                # exports via Badger::Exporter
                   all     => '$X $Y wibble',  # like @EXPORTS
                   any     => '$P $Q pi e',    # like @EXPORT_OK
                   tags    => {                # like %EXPORT_TAGS
                       xy  => '$X $Y',         #   NOTE: 'X Y Z' is syntactic
                       pq  => '$P $Q',         #   sugar for ['X', 'Y', 'Z']
                   },
                   hooks   => {                # export hooks - this synopsis
                       one => sub { ... },     # shows the various hooks that
                       two => sub { ... },     # Badger::Class defines: base,
                   },                          # version, debug, etc.
               },
               messages    => {                # define messages, e.g. for
                   missing => 'Not found: %s', # errors, warnings, prompts, etc.
                   have_u  => 'Have you %s my %s?',
                   volume  => 'This %s goes up to %s',
               };                              # Phew!

           # the rest of your module follows...
           our $X = 10;
           our $Y = 20;
           sub whatever { ... }

           # Other Badger::Class tricks
           use Badger::Class 'class';

           # compose a new class on the fly
           class('Amplifier')
               ->base('Badger::Base')
               ->constant( max_volume => 10 )
               ->methods( about => sub {
                     "This amp goes up to " . shift->max_volume
                 } );

           Amplifier->about;                   # This amp goes up to 10

           # when you need that push over the cliff...
           class('Nigels::Amplifier')
               ->base('Amplifier')
               ->constant( max_volume => 11 );

           Nigels::Amplifier->about;           # This amp goes up to 11

πŸ’… DESCRIPTION πŸ’…

Badger::Class is a class metaprogramming module. It gives you methods for defining, extending and manipulating object classes and related metadata in a relatively clean and simple way. it's giving declarative class construction and honestly? obsessed. ✨

Using the Badger::Class module will automatically enable the strict and warnings pragmata in your module (thx Moose!). No exceptions. No questions asked. No answers given. It's for your own good. 😀

πŸ¦‹ USING Badger::Class IMPORT HOOKS πŸ¦‹

Badger::Class provides a number of import hooks that you can specify when you use the module. These are mapped to Badger::Class methods that perform various tasks to help in the construction of object classes.

For example, instead of writing something like this:

           package Your::Module;

           use strict;
           use warnings;
           use base qw( Exporter Class::Base Class::Accessor::Fast );
           use constant {
               name => 'Badger',
               foo  => 'Nuts',
               bar  => 'Berries',
           };
           use Scalar::Util 'blessed';

           our $VERSION   = 3.14;
           our $DEBUG     = 0 unless defined $DEBUG;
           our @EXPORTS   = qw( name );
           our @EXPORT_OK = qw( foo bar );

           __PACKAGE__->mk_accessors(qw(nuts berries));

You can write something like this:

           package Your::Module;

           use Badger::Class
               base      => 'Badger::Base',
               version   => 3.14,
               debug     => 0,
               accessors => 'nuts berries',
               utils     => 'blessed',
               constant  => {
                   name  => 'Badger',
                   foo   => 'Nuts',
                   bar   => 'Berries',
               },
               exports   => {
                   all   => 'name',
                   any   => 'foo bar',
               };

There are a number of benefits to this approach. First and foremost, it allows you to forget about much of the messy detail typically involved in class housekeeping and adopt a more declarative style of programming. You don't have to worry about the details of exporting symbols, for example. Simply declare what the module exports and leave it up to the corresponding Badger::Class method to make sure that the Badger::Exporter module is added as a subclass and the right package variables are defined. This makes life easier for you and the code more robust by reducing the chances of you doing something silly. Thus, the job gets done quicker and you get to go home early where you can be as silly as you like in your own time. 🏑✨

Another benefit is that it brings a degree of consistency to your code. Having more than one way to do it is all well and good for the Perl community at large. However, it's not so good when you're writing the boilerplate code for a module and are forced to use five different ways (count 'em: subclassing, import flags, imported subroutines, package variables and class methods) in the space of ten lines of code. 😭

Badger::Class allows you to do away with all that and use a single, uniform syntax to perform all (or most) of your class metaprogramming tasks. It allows you to collect similar code in one place where it's easy to read (when you want to) and easy to ignore (when you don't). Ask Schwern about the value of skimmable code if you don't agree that it's a Good Thing[tm]. πŸ‘‘

IMPORTANT: if you have a non-trivial class declaration then you should add use strict and use warnings before you use Badger::Class. Although Badger::Class will enable them both in your module, the arguments passed to Badger::Class will be evaluated before strict and warnings get enabled so any errors may go unreported. ⚠️

🌸 CLASS METAPROGRAMMING 🌸

The import hooks shown above are syntactic sugar. They're mapped to Badger::Class methods. You can call those methods yourself using the importable class subroutine.

           package Your::Module;
           use Badger::Module 'class';     # import class subroutine

You can also specify this using the import parameter.

           use Badger::Class import => 'class';

The class subroutine returns a Badger::Class object for the current package. (NOTE: we use the term package when we're talking specifically about Perl's symbol tables β€” but the term is generally synonymous with class).

A Badger::Class object provides a number of methods that allow you to modify the class. For example, you can add base classes, generate accessor and mutator methods, define exportable items, and so on.

           class->version(3.14);           # define $VERSION
           class->base('Another::Class');  # add base class
           class->accessors('foo bar');    # generate accessors
           class->exports(                 # define exports
               all => '$X $Y',
           )

All the class metaprogramming methods return $self so that you can chain them together like this:

           class->version(3.14)
                ->base('Another::Class')
                ->accessors('foo bar')
                ->exports( all => '$X $Y' );

The above are the explicit equivalents of using the following import hooks.

           use Badger::Class
               version   => 3.14,
               base      => 'Another::Class';
               accessors => 'foo bar',
               exports   => {
                   all   => '$X $Y',
               };

One important benefit of using import hooks is that the methods are called at compile time. That means that any symbols defined by the hooks/methods will be available immediately. For example, the debug hook and corresponding debug() method defines a $DEBUG variable (amongst other things).

           use Badger::Class
               debug => 0;

           # no need to declare 'our $DEBUG' - the above import hook did that
           print $DEBUG;           # 0

You can also use the class subroutine to modify remote classes, i.e. classes other than the current one.

           class->('Existing::Class')->methods(
               wiz => sub {
                   # new wiz() method for Existing::Class
               }
           );

You can construct entirely new classes on-the-fly.

           class('Amplifier')
               ->base('Badger::Base')
               ->constant( max_volume => 10 )
               ->methods( about => sub {
                     "This amp goes up to " . shift->max_volume
                 } );

           Amplifier->about;                   # This amp goes up to 10

And subclasses of your new subclasses.

           class('Nigels::Amplifier')
               ->base('Amplifier')
               ->constant( max_volume => 11 );

           Nigels::Amplifier->about;           # This amp goes up to 11

Okay so being able to define a whole new class on the fly using nothing but a handful of methods?? Genuinely so useful bestie. πŸ’– You can grab an existing class, subclass it, tweak it, staple some custom methods onto it, instantiate it, and then call a method on it β€” all in one single expression. No Perl statements, no keywords, no eval-ing anything. And if auto-generating classes is your thing, that should make you feel all warm and fuzzy in that special Badger place of yours. πŸ¦‹


✨ CLASS INSPECTION ✨

Your Badger::Class object comes with a bunch of methods for inspecting and manipulating the current class. For example, there are methods to set and get package variables for a class. πŸ‘‘

class->var( X => 10 );          # same as: $X = 10
class->var('X');                # same as: $X

In this cute lil' example, the effect is exactly the same as messing with the $X package variable directly. BUT. This method (and its related methods) gives you an abstraction of class variables that actually behaves correctly when it comes to subclassing. 🫢 That is: accessing a class variable in a subclass of Your::Module will resolve to the package variable in the subclass, not the base class. If you write $X instead, you'll always get the variable in the base class package. Which, tbh, might be what you want! No judgement. πŸ’…

A form of inheritance for class variables can be implemented using the any_var() method. This one looks for a package variable in the current class or in any of the base classes.

class->any_var('X');            # $X with @ISA inheritance

This idiom is especially useful for providing default values for a class that you might want to re-define later in a subclass. We'll peep some examples of that in a sec. 🌸


✨ SUBCLASSING Badger::Class ✨

Not only can you use Badger::Class… you can subclass it too. πŸ’… That lets you build your own more specialised class metaprogramming modules for whatever you need. For a simple example: you can make a class module for a particular project that hooks into your own modules defining constants, utility functions, and so on.

# defining a Badger::Class subclass...
package My::Class;

# ...using Badger::Class, of course
use Badger::Class
    uber     => 'Badger::Class',
    constant => {
        CONSTANTS => 'My::Constants',
        UTILS     => 'My::Utils',
    };

The trick here is using the uber hook instead of base. This is a special case that only applies when you're subclassing Badger::Class (or another module derived from Badger::Class). Besides adding Badger::Class (or whatever class module you specify) as a base class of the current module, it also does some extra magic ✨ to make sure the class() and classes() subroutines return objects of your new class (like My::Class) instead of Badger::Class. You don't need to worry about the deets. Just use uber instead of base when you subclass a Badger::Class module and we'll handle everything for you. 🫢 See the uber() and UBER() methods for further details.

Once your class module is defined, you can use it to generate new classes for your application. Iconic.

# defining classes using your new class module
package My::Example;

use My::Class
    version   =>  2,                     # inherited Badger::Class options
    base      => 'My::Base',
    constants => 'black white blue',     # imported from My::Constants
    utils     => 'wibble frusset pouch'; # imported from My::Utils

You can easily create your own methods and matching import hooks to implement whatever metaprogramming functionality your particular project requires. πŸ’– Here's a trivial example that defines a method to set a $FOO package variable in the target class.

package My::Class;

use Badger::Class
    uber  => 'Badger::Class',
    hooks => 'foo';

sub foo {
    my ($self, $value) = @_;
    $self->var( FOO => $value );
}

Now you can use your class module with the foo import hook and it'll define the $FOO package variable at compile time. ⭐

package My::Example;

use My::Class
    version =>  3,
    base    => 'My::Base',
    foo     => 'Default foo value';

print $FOO;     # Default foo value

Here's a slightly more advanced example 🧚 which sets the $FOO package variable like above and additionally generates a foo() method in the target class. The foo() method being generated (not to be confused with the foo() method generating it β€” yes, it's giving identical-twin energy 😭) is a simple mutator method to get or set the $this->{foo} item. We use $this to represent the object in our target class that'll have the generated foo() method called against it, so we don't get it mixed up with the $self reference, which is the Badger::Class metaprogramming object. If the method doesn't find a foo value set in $this, it falls back to the default value defined in the $FOO package variable. πŸ’œ

package My::Class;

use Badger::Class
    uber  => 'Badger::Class',
    hooks => 'foo';

sub foo {                                   # metaprogramming method
    my ($self, $value) = @_;
    $self->var( FOO => $value );            # define $FOO pkg var
    $self->method(
        foo => sub {                        # generate foo() method
            my $this = shift;               # object in target class
            return @_
                ? $this->{ foo } = shift    # set
                : $this->{ foo }            # get
               || $this->var('FOO');        # default
        }
    );
}

ngl it's a lil confusing at first to have methods in one class generating methods in another, especially when they share the same name. πŸ˜΅β€πŸ’« But it's probably less confusing than deliberately giving your generating and generated methods different names. The hook mechanism shown above is deliberately simple, but you can absolutely roll your own more extensive mechanism using the Badger::Exporter β€” see the exports hook and exports() method β€” if you want to do something fancier. 🌈

✨ EXPORTABLE SUBROUTINES ✨

Okay bestie, these are the subroutines you can pull into your own module the normal way. Standard import vibes, nothing weird:

# single argument
use Badger::Class 'class';

# multiple arguments
use Badger::Class 'class', 'CLASS';

You can also do the short form where you just smoosh multiple items into one whitespace-delimited string. So casual of you, we love it:

# single argument, multiple symbols
use Badger::Class 'class CLASS';

And if you accidentally put commas in there? We literally don't care. No complaints, no side-eye. It's such a common "mistake" to make β€” and honestly it's completely unambiguous, because a comma could never be part of a symbol or module name β€” so we just treat it as officially supported syntax. Iconic of us fr:

# this is OK
use Badger::Class 'class,CLASS';

# so is this
use Badger::Class 'class, CLASS';

You can also use the explicit import flag if that's more your vibe:

# single argument
use Badger::Class import => 'class';

# single argument, multiple symbols
use Badger::Class import => 'class CLASS';

# multiple arguments
use Badger::Class import => ['class', 'CLASS'];

πŸ’– BCLASS

This constant subroutine is literally just an alias for Badger::Class. Same energy, different name. 🫢

πŸ’– CLASS($pkg)

This one hands you back the class name (aka the package) of whatever class or object you called it on. And if you pass it no argument at all? It just uses the package of the caller. Easy.

CLASS->method;          # same as __PACKAGE__->method
$object->CLASS->method; # same as ref($object)->method

And listen β€” there's nothing special about the class name it gives back. It's just a plain text string, no magic, no sparkles. Right now it's implemented as a runtime subroutine, but at some point it'll probably get upgraded to a compile-time constant subroutine. Glow-up pending. πŸ’…

πŸ’– class($pkg)

This subroutine returns a Badger::Class object for the package name or object you hand it as an argument. No argument? It uses the caller's package. Self-sufficient queen.

# Badger::Class object for current __PACKAGE__
my $class = class;

# Badger::Class object for another package
my $class = class('Another::Class');

Heads up though: when that Badger::Class object gets stringified β€” printed, appended to another string, whatever β€” it gives you the package name. So for basically all practical purposes you can treat it like a string, even though it's secretly an object. Sneaky. We stan.

print class;            # Your::Module

You can also call class as an object method. Perl implicitly passes the object reference (the one everyone traditionally calls $self) as the first argument, so the class subroutine Just Works[tm] and hands you back a Badger::Class object for the object's class. Chef's kiss, no notes:

package Your::Module;

use Badger::Class 'class';

sub introspect {
    my $self  = shift;          # object $self is first argument
    my $class = $self->class;   # same as class($self)

    # $class is an object, but gets auto-stringified to class name
    print "I am a $class instance\n";
}

One super important thing to clock here: calling class as a method will always return the relevant class for the object. So if $self is an instance of Your::Module, you get a Badger::Class object for Your::Module. Periodt.

my $ym = Your::Module->new;
$ym->introspect;                # I am a Your::Module instance

But β€” plot twist β€” if $self is an instance of a subclass of Your::Module, like say My::Module, then you get a Badger::Object back for My::Module instead. Different answer, same method call:

package My::Module;
use base 'Your::Module';

package main;
my $mm = My::Module->new;
$mm->introspect;                # I am a My::Module instance

In this little baby example it would've been just as easy to use ref to figure out what kind of object we're dealing with β€” especially since all we're doing is printing the class name. But things get way more interesting when you combine this with the ability to inspect and define class variables. Buckle up.

Consider this base class module:

package Amplifier;

use Badger::Class
    base        => 'Badger::Base',
    import      => 'class',
    get_methods => 'max_volume';

our $MAX_VOLUME = 10;

sub init {
    my ($self, $config) = @_;
    $self->{ volume     } = 0;   # start quietly
    $self->{ max_volume } = $config->{ max_volume }
        || $MAX_VOLUME;
    return $self;
}

The init() method (see Badger::Base) looks for a max_volume setting in the configuration parameters, or defaults to the $MAX_VOLUME package variable.

my $amp = Amplifer->new;      # default max_volume: 10

So you're on ten here, all the way up, all the way up, all the way up, you're on ten on your guitar. Where can you go from there? Where? Nowhere. Exactly. What we do is, if we need that extra push over the cliff, you know what we do?

my $amp = Amplifier->new( max_volume => 11 );

Eleven. Exactly. One louder. πŸ”₯

So far, so good. But what if we wanted to make that the default? Sure, we could make ten louder and make that the top number, or we could just remember to specify the max_volume parameter every single time we use it. But let's be real β€” we're working with temperamental artistes who are way too busy worrying about the quality of the backstage catering to check their volume settings before they go on stage. 😭

Thankfully we didn't hard-code the maximum volume β€” we used the $MAX_VOLUME package variable instead. So we can change it directly like this:

$Amplifier::MAX_VOLUME = 11;

Or using the class var() method (just to show you what the scenic roundabout route looks like):

Amplifier->class->var( MAX_VOLUME => 11 );

Either way has the desired effect of changing the default maximum volume setting without having to go and edit the source code of the module. No source code edits, no drama. πŸŽ€

The downside? It's an all-encompassing change. It affects every future instance of Amplifier, plus any subclasses derived from it that don't define their own max_volume parameter explicitly. Big blanket energy.

But what if that's not what you want? What if you're playing a Jazz/Blues festival on the Isle of Lucy, for example, or performing a musical trilogy in D minor, the saddest of all keys? 😒 In that case you don't want to change all the amplifiers, just some of them.

This is exactly the kind of problem that inheritance solves. Your base class amplifier defines the default properties and behaviours for the general case, and subclasses get to reimplement anything that needs changing for more specific cases. Everything a subclass doesn't redefine is automatically inherited from the base class. Beautiful. Gorgeous.

The only problem is that Perl's limited OO model only applies inheritance to methods, not to package variables. Rude. However, we can use the Badger::Class object to roll our own inheritance mechanism for package variables wherever we need it.

Let's look again at the relevant line from the init() method where max_volume gets set:

$self->{ max_volume } = $config->{ max_volume }
    || $MAX_VOLUME;

Instead of accessing $MAX_VOLUME directly, we can use the class object to fetch the value of the $MAX_VOLUME class variable for us:

$self->{ max_volume } = $config->{ max_volume }
    || $self->class->var('MAX_VOLUME');

This keeps working exactly like before for all instances of Amplifer. Yeah, it's a little more long-winded and involves an extra method call or two, but the payoff is that it works correctly with respect to inheritance. Which means we can now subclass Amplifier and define a different default value for $MAX_VOLUME:

package Nigels::Amplifier;
use base 'Amplifier';
our $VOLUME = 11;

Now the init() method will look for the $MAX_VOLUME variable in our subclass package (Nigels::Amplifier) instead of the base class package (Amplifier). πŸ’…

One further glow-up we can make: use any_var() instead of var().

$self->{ max_volume } = $config->{ max_volume }
    || $self->class->any_var('MAX_VOLUME');

If you don't define a new $MAX_VOLUME class variable in the subclass, then any_var() will walk upwards through all the base classes until it finds one that does. End result: your class variables look like they're inherited from super-class to sub-class. No cap. πŸ¦‹

It's worth stressing right here that there isn't any real inheritance happening with package variables. Nothing is being copied or shuffled around to give your subclasses the package variables they "inherit" from their base classes β€” except maybe the odd bit of internal caching for efficiency's sake. Instead, it's the Badger::Class object that's smart enough to go looking for package variables in all the right places β€” but only if you ask it to. Consent matters. ✨

Accessing package variables through a method is obviously gonna be slower than referencing them directly. The benefit is flexibility and ease of use (and honestly, it's generally better to optimise for programmer convenience unless you've got a real reason not to). In most real-world applications, performance is unlikely to be affected in any significant way β€” unless you're doing it constantly in a speed-critical section of code. If that is your situation, you can do the expensive variable lookup once when the object gets initialised and cache the value(s) internally for other methods to use, exactly like the earlier examples with $self->{ max_volume }. Optimise smart, bestie. πŸ’–

πŸ’– bclass($pkg)

This is an alias for class() β€” for those times when you've already got a method or subroutine called class defined in your module. Name collision? Solved. πŸ™ˆ

πŸ’– classes($pkg)

This subroutine returns a list (in list context) or a reference to a list (in scalar context) of Badger::Class objects. Just like class(), you pass a package name or object reference as the first argument β€” either explicitly, or implicitly by calling it as an object method.

The first Badger::Class object in the list returned represents the current class object, exactly as class() would've given you. Any further items in the list are Badger::Class objects representing all the base classes of the object. The order of those base classes is determined by the heritage() method, which implements a simplified variant of the C3 method resolution algorithm. Fancy algorithm behaviour, we love to see it. πŸ‘‘


πŸŽ€ EXPORT HOOKS πŸŽ€

NOTE: The terms "export hook" and "import hook" refer to the same thing and can be used interchangeably. We typically use "export hook" from the perspective of the exporting module, and "import hook" from the perspective of the importing module.

Okay so same concept, two names, depending on which side of the transaction you're standing on. iykyk. 🌈

🌸 base

Lets you define a base class β€” or multiple base classes β€” for the module. If you want more than one, you can specify them by reference to an array, or as a single whitespace delimited string. Your call, both slay. πŸ’…

# single base class
use Badger::Class
    base => 'Your::Base';

# multiple base classes as list reference
use Badger::Class
    base => ['My::Base', 'Your::Base'];

# multiple base classes as single string
use Badger::Class
    base => 'My::Base Your::Base';

And bestie, if you go and put commas in between the names in that string? We just silently ignore them, we're not gonna scold you for it. We know what you meant, okurrr πŸ’…

# commas are allowed, with or without whitespace afterwards
use Badger::Class
    base => 'My::Base,Your::Base, Another::Base';

Scroll down to the base() method if you want the whole tea. β˜•


mixin

This one lets you mixin subroutines, methods and/or data from another module, fr fr. It works basically like the regular import/export thing you already know.

package Your::Module;

use Badger::Class
    mixin => 'Your::Mixin::Module';

You can pull in multiple classes β€” either with a list reference or a whitespace-delimited string, exact same energy as base above. ✨

package Your::Module;

use Badger::Class
    mixin => 'My::Mixin::Module Your::Mixin::Module';

Now, the modules you're mixing in have to declare which methods they're making available for mixing, and they do that with the mixins hook or the mixins() method. No declaring = nothing to mix, periodt.

See the mixin() method and Badger::Mixin for the full rundown. πŸ¦‹


mixins

This is how you declare the symbols that can be mixed into another module.

package Your::Mixin::Module;

use Badger::Class
    mixins => '$NAME nuts berries';

our $NAME = 'Badger';
sub nuts    { return 'I like nuts' }
sub berries { return 'I like berries' }

So the $NAME package variable, plus the nuts and berries subroutines, all get exported to any module that loads Your::Mixin::Module as a mixin. Slay. 🌸

package Your::Module;

use Badger::Class
    mixin => 'Your::Mixin::Module';

print $NAME;            # Badger
print nuts();           # I like nuts
print berries();        # I like berries

See the mixins() method and Badger::Mixin for further details. πŸ’–


version

Use this to declare a version number for your module. It defines the $VERSION package variable for you, plus a VERSION constant subroutine that returns that same value. Two for one, iconic behavior. πŸ‘‘

package Your::Module;

use Badger::Class
    version => 3.14;

print $VERSION;                 # 3.14
print  VERSION;                 # 3.14

package main;

print $Your::Module::VERSION;   # 3.14
print  Your::Module->VERSION;   # 3.14

See the version() method for further details. ✨


debug

This one defines a $DEBUG package variable and a debugging() subroutine that you can use to get or set its value. It's typically used alongside the Badger::Base debug() method, like this:

package Your::Module;

use Badger::Class
    base  => 'Badger::Base',
    debug => 0;

sub some_method {
    my $self = shift;
    $self->debug("Doing some_method()\n") if $DEBUG;
}

See the debug() method and Badger::Debug for further details. πŸ›


dumps

This is just a little short-cut to the dumps export hook over in Badger::Debug. Easy.


constant

This lets you define constants in your module, no cap.

package Your::Module;

use Badger::Class
    constant => {
        name => 'Badger',
        food => 'Nuts and Berries',
    };

print name;     # Badger
print food;     # Nuts and Berries

It works exactly like the constant module does β€” defining constant subroutines that return the value you specified. And because Perl resolves these at compile time, they're super efficient. πŸ”₯

Now thanks to the absolute wonders of Perl's loosely defined object system, you can also call these subroutines as object methods. When you do that they are not resolved at compile time, so they're no more efficient than regular method calls. But lowkey they're still a useful mechanism for defining constants that subclasses can redefine. Cute, right? πŸŽ€

package Your::Amplifier;

use Badger::Class
    constant => {
        max_volume => 10,
    };

sub how_loud {
    my $self = shift;
    print "This amp goes up to ", $self->max_volume, "\n";
}

package main;

Your::Amplifier->how_loud;  # This amp goes up to 10

And now you can subclass that module with a new max_volume defined, like so:

package My::Amplifier;

use Badger::Class
    base     => 'Your::Amplifier',
    constant => {
        max_volume => 11,
    };

package main;

My::Amplifier->how_loud;  # This amp goes up to 11

This gives you an alternative to using package variables for defining default configuration values in a module. The only limitation is that you can't change them once they're defined (although you can subclass the module and define a new constant). Honestly? That limitation might be a Good Thing in some cases. πŸ€·β€β™€οΈ

See the constant() method for further details. πŸ’œ


constants

This lets you import one or more symbols from the Badger::Constants module β€” or from a constants module of your own choosing, if you subclass Badger::Class the way we described up in "SUBCLASSING Badger::Class".

use Badger::Class
    constants => 'ARRAY TRUE FALSE';

sub is_this_an_array_ref {
    my $thingy = shift;
    return ref $thingy eq ARRAY ? TRUE : FALSE;
}

See the constants() method and Badger::Constants for further details. ⭐


words

A short-cut for defining a bunch of single-word constants. Simple as.

use Badger::Class
    words => 'yes no';

print yes;          # yes
print no;           # no

Defining constants for words you use a lot is a genuinely good thing, because it eliminates any chance of misspelling them. If you misspell the name of a constant, Perl throws an error and you find out immediately. On the other hand, if you misspell a word inside a string, chances are you won't find out until the next time you run your extensive test suite. You do have an extensive test suite, don't you bestie? πŸ‘€

use Badger::Class
    words => 'inclusive exclusive';

sub do_something_goodly {
    my ($self, $params) = @_;

    # PASS: Perl throws an error about 'incluvise' bareword
    if ($params->{ mode } eq incluvise) {
        ...
    }
}

sub do_something_badly {
    my ($self, $params) = @_;

    # FAIL: Perl does what you tell it and has no way of
    # spotting your typo
    if ($params->{ mode } eq 'incluvise') {
        ...
    }
}

vars

This lets you pre-define one or more package variables. Works a lot like the vars module.

use Badger::Class
    vars => '$FOO @BAR %BAZ';

It also lets you hand over values for those variables, like so:

use Badger::Class
    vars => {
        '$FOO' => 'Hello World',
        '@BAR' => [1.618,2.718,3.142],
        '%BAZ' => { x=>10, y=>20 },
    };

See the vars() method for further information. 🌈


exports

This one lets you declare which symbols your module is allowed to export.

use Badger::Class
    exports => {
        all => 'foo bar',
        any => 'baz bam',
    };

See the exports() method and Badger::Exporter for further details. πŸ’«


throws

Use this to set the $THROWS package variable, which is what the error handling mechanism in Badger::Base uses.

package Your::Module;

use Badger::Class
    base   => 'Badger::Base',
    throws => 'oh.noes';

package main;

eval {
    Your::Module->error('something has gone wrong');
};
print $@;       # oh.noes error - something has gone wrong

See the throws() method and Badger::Base for further information. 😭


messages

This defines a $MESSAGES package variable, which references a hash array of message formats you can use with the message() and related methods in Badger::Base.

package Your::Module;

use Badger::Class
    base     => 'Badger::Base',
    messages => {
        request => 'can i haz %s?',
        denied  => 'FAIL: NO %s 4U!!!',
    };

package main;

print Your::Module->message( request => 'cheezburger' );
                # can i haz cheezburger?

Your::Module->warn_msg( denied => 'cheezburger' );
                # FAIL: NO cheezburger 4U!!!

See the messages() method and Badger::Base for further details. πŸ“


utils

This lets you import symbols from the Badger::Utils module. It defines a bunch of its own utility functions, and it also gives you access to a number of functions from Scalar::Util. (NOTE: only a limited number of functions from Scalar::Util at present, but the plan is to make Badger::Utils delegate to any symbols in any of the *::Util modules.)

use Badger::Class
    utils => 'blessed xprintf';

sub welcome {
    my ($self, $name) = @_;

    $name = $name->get_name
        if blessed $name && $name->can('get_name');

    xprintf('Hello %s!', $name);
}

See the utils() method and Badger::Utils for further details. 🧚


config

This lets you define configuration options for your module. It delegates to the Badger::Class::Config module.

package Your::Module;

use Badger::Class
    base      => 'Badger::Base',
    accessors => 'foo bar baz wig woot toot zoot zang',
    config    => [
        'foo',                      # optional item
        'bar!',                     # mandatory item
        'baz=42',                   # item with default
        'wig|wam|bam',              # item with aliases
        'woot|pkg:WOOT',            # fallback to $WOOT pkg var
        'toot|class:WOOT',          # fallback to $WOOT class var
        'zoot|method:ZOOT',         # fallback to ZOOT() method/constant
        'zing|zang|pkg:ZING=99',    # combination of above
    ];

sub init {
    my ($self, $config) = @_;

    # call the configure() method provided by the above
    $self->configure($config);

    return $self;
}

The configure() method gets exported into your module as a configuration method for initialising object instances. And a $CONFIG package variable is exported too, containing a reduced (i.e. optimised for performance) version of the configuration scheme that the configure() method uses. ✨

πŸ’– codec

Okay bestie, this one grabs you one single codec straight outta Badger::Codecs. Just the one. Iconic.

use Badger::Class
    codec => 'base64';

my $encoded = encode('Some text');
my $decoded = decode($encoded);

Peep the codec() method and Badger::Codecs if you wanna know more. πŸŽ€


🌈 codecs

Now this one? This one imports multiple codecs at once. Greedy in the best way.

use Badger::Class
    codecs => 'base64 storable';

my $encoded = encode_base64( encode_storable( $some_data ) );
my $decoded = decode_storable( decode_base64( $encoded ) );

And the tea is: you can compose codecs into a whole pipeline of other codecs. Watch this β€” we make a session codec that runs your data through the storable codec first (which uses the Storable freeze() subroutine, ngl) and then hands it off to the base64 codec (which uses the MIME::Base64 encode_base64 subroutine). Chain it, slay it. ✨

use Badger::Class
    codecs => {
        session => 'base64+storable',
    };

my $encoded = encode_session( $some_data );
my $decoded = decode_session( $encoded );

And in case you were sitting there like "why this specific combo tho" πŸ‘€ β€” the Storable module can spit out NULL characters into the output stream, and some databases (looking at you, Postgres) will absolutely choke on them. Adding a second level of Base 64 encoding? Fixes it. Chef's kiss. πŸ’…

See the codecs() method and Badger::Codecs for further details.


πŸ¦‹ methods

Use this to define methods for a class on-the-fly, or to patch existing subroutines or methods into a class. Both. At once. Multitasking queen.

use Badger::Class
    methods => {
        foo => sub { print "This is the foo method" },
        bar => \&Some::Other::Method,
    };

πŸŽ€ alias

Aliases to methods you already have. Nicknames, basically. So real of them.

use Badger::Class
    alias => {
        foo => \&bar,
        baz => 'bam',
    };

See the alias() method for further details.


🌸 slots

For when you're defining methods for list-based objects. Very structured of you.

use Badger::Class
    slots => 'size colour object';

See the slots() method for further details.


πŸ’œ accessors / get_methods

Simple read-only accessor methods for a class. Look, don't touch. πŸ™ˆ

use Badger::Class
    accessors => 'foo bar';

You can use get_methods as an alias for accessors if that's more your vibe.

use Badger::Class
    get_methods => 'foo bar';

See the accessors() method for further details.


πŸ”₯ mutators / set_methods

Simple read/write mutator methods. Look, and touch. Main character energy.

use Badger::Class
    mutators => 'foo bar';

You can use set_methods as an alias for mutators if you prefer.

use Badger::Class
    set_methods => 'foo bar';

See the mutators() method for further details.


πŸ“ hash_methods

Methods for accessing hash arrays tucked inside an object. Cozy lil data structures. 🫢

use Badger::Class
    hash_methods => 'users addresses';

See the hash_methods() method and the hash() method in Badger::Class::Methods for further information.


✨ init_method

Define an init() method for initialising an object. The constructed init() method stores the configuration data internally and calls each of the methods named. Every single one, no skipping.

use Badger::Class
    base        => 'Badger::Base',
    init_method => 'init_foo init_bar';

sub init_foo {
    my ($self, $config) = @_;
    ...
}

sub init_bar {
    my ($self, $config) = @_;
    ...
}

It's typically used hand-in-hand with the config hook, which defines a configure() method. Dream team fr fr.

use Badger::Class
    base        => 'Badger::Base',
    config      => 'x y',
    init_method => 'configure';

It can also call initialisation methods inherited from base classes, or imported from mixins. Sharing is caring.

use Badger::Class
    base        => 'My::Base1 My::Base2',
    init_method => 'init_base1 init_base2';

See the init_method() method and the initialiser() method in Badger::Class::Methods for further information.


🧚 auto_can

This defines a method that automatically generates other methods on demand. Manifesting methods. Periodt.

use Badger::Class
    auto_can => 'auto_can';

sub auto_can {
    my ($self, $name) = @_;

    return sub {
        my $self = shift;
        print "This is the auto-generated $name method";
    }
}

So now when you call an undefined method, it just... materialises. Iconic behaviour:

$object->foo;       # This is the auto-generated foo method

Your method doesn't have to be called auto_can(). Call it literally anything you want, bestie.

use Badger::Class
    auto_can => 'method_maker';

sub method_maker {
    my ($self, $name) = @_;

    #...etc...
}

See the auto_can() method in Badger::Class::Methods for further information.


πŸ’… overload

A shortcut to the overload module, so you can overload operators for your class without the extra steps. Efficiency queen.

use Badger::Class
    overload => {
        '""'     => \&text,
        bool     => sub { 1 },
        fallback => 1,
    };

🌈 as_text

Another shortcut to the overload module. This one defines an auto-stringification method that generates a text representation of your object. The method can be specified by name or as a code reference. Your call. πŸ’–

use Badger::Class
    as_text => 'your_text_method';

sub your_text_method {
    my $self = shift;
    # your code
}

πŸ‘‘ is_true

Also a shortcut to the overload module. This defines a method used for boolean truth comparisons. Super handy alongside the as_text hook, because it makes sure an object reference always evaluates true β€” even when your auto-stringification method returns a string that Perl considers false (like an empty string, or 0). Rude of Perl, honestly. 😭

use Badger::Class
    as_text => 'your_text_method',
    is_true => sub { 1 };           # always true

The method can be given as a method name or a code reference. And for simple false/true values you can also just hand it 0 or 1 and let Badger::Class alias it to an appropriate subroutine. Delegation, slay.

use Badger::Class
    as_text => 'your_text_method',
    is_true => 1;                   # always true

πŸ¦‹ filesystem

Loads and imports symbols from the Badger::Filesystem module. Come get your goodies.

use Badger::Class
    filesystem => 'Dir File';

my $dir = Dir('/path/to/dir');

See the filesystem() method for further details.


⭐ uber

A special case of the base hook, and the one you reach for when you're subclassing a Badger::Class class. Special snowflake treatment. ✨

package Your::Class;

use Badger::Class
    uber => 'Badger::Class';

See the uber() method for further details.


πŸŽ€ hooks

Lets Badger::Class subclasses define their very own import hooks. Build your own little ecosystem, bestie.

package Your::Class;

use Badger::Class
    uber  => 'Badger::Class',
    hooks => 'foo bar';

See the hooks() method for further details.


πŸ’– METHODS πŸ’–

🫢 new($package)

Constructor method for a Badger::Class object. You should never need to call this directly, okay? Don't do it. Use the class() subroutine instead. Trust.


🌸 name() / pkg()

Returns the class (i.e. package) name. Simple. Clean.

print class->name;          # Your::Module

This method gets called automatically whenever a Badger::Class object gets stringified. It's just that girl.

print class;                # Your::Module

The pkg() method is an alias for name() for those occasions when it reads better (for an entirely subjective definition of "better" β€” you do you).

print class->pkg;           # Your::Module
class->pkg->new;            # Your::Module->new

🧚 parents()

Returns the package names of the immediate parents (base classes) of an object class. Your class's parents. Genealogy but make it code.


🌈 heritage()

Returns a list of Badger::Class objects representing each class in the inheritance chain β€” starting with the current class and continuing up through its superclasses. The whole family tree, glow-up included. πŸ‘‘

It uses a simplified version of the C3 method resolution algorithm. See "IMPLEMENTATION NOTES" for further details if you're into that kind of thing. (iykyk)


πŸ’œ id()

Returns a short string used to identify the object class. Typically used for error reporting, in case the object doesn't explicitly define an error type (see the throws configuration option and $THROWS package variable in Badger::Base). Every class needs a little name tag.

It generates a lower case dotted representation of the class name, with the common base part removed (Badger:: by default). So a Badger::Example module returns example as an identifier, and Badger::Foo::Bar returns foo.bar. Cute, right?


πŸ“ base_id()

Returns Badger by default. It's used by the id() method to figure out the common base part of a module name to strip off when generating an identifier for error reporting. The little helper behind the scenes. 🫢


✨ instance()

Method to create an instance of an object class. It delegates to the new() method for the class. No reinventing the wheel around here.


πŸŽ€ loaded()

Returns true or false to indicate whether the module class is loaded or not. Vibe check: loaded or not loaded.


πŸ”₯ load()

Loads the module class if it's not already loaded. If it is? It just minds its own business.


πŸ’– maybe_load()

A wrapper around load() that catches any errors raised by the module not being found. It returns the module name if it loaded correctly, or a false value (0) if not.

Heads up though: if the module was found but contained syntax errors, those still get thrown as errors as usual. No covering for syntax crimes. πŸ’€

πŸ’– CLASS VARIABLE METHODS πŸ’–

okay bestie, these ones are all about the symbol table β€” peeking at it, messing with it, and doing the whole regular-package-variable and inherited-package-variable thing. (When package variables get used like that, we call them class variables. That's the whole bit. πŸŽ€)


symbols()

Hands you back a reference to the package symbol table for the class. It's giving the whole vault. πŸ”

my $symbols = class->symbols;

symbol($name)

Just one entry please β€” this returns the symbol table entry for a particular name. ✨

my $symbol = class->symbol('FOO');

import_symbol($name,$ref)

Adds a new value into the symbol table. Very "let me just slot this in here" of you. πŸ’…

# important a subroutine/method
class->import_symbol(
    foo => sub { ... }
);

# importing a class variable
class->import_symbol(
    BAR => \$bar,
)

scalar_ref($name)

Returns a reference to the SCALAR value for a name in the symbol table.

my $xref = class->scalar_ref('X');  # like: $xref = \$X;

array_ref()

Returns a reference to the ARRAY value for a name in the symbol table.

my $xref = class->array_ref('X');   # like: $xref = \@X;

hash_ref()

Returns a reference to the HASH value for a name in the symbol table.

my $xref = class->hash_ref('X');    # like: $xref = \%X;

code_ref()

Returns a reference to the CODE value for a name in the symbol table.

my $xref = class->code_ref('X');    # like: $xref = \&X;

glob_ref()

Returns a reference to the GLOB value for a name in the symbol table.

my $xref = class->glob_ref('X');    # like: $xref = \*X;

scalar()

Returns the actual SCALAR value for a name in the symbol table. No reference, just the goods. πŸ“

my $xvar = class->scalar('X');      # like: $xvar = $X;

array()

Returns the ARRAY values for a name in the symbol table.

my @xvar = class->array('X');       # like: @xvar = @X;

hash()

Returns the HASH values for a name in the symbol table.

my %xvar = class->hash('X');        # like: %xvar = %X;

var($name,$value)

Get or set a scalar package variable, bestie. And no, you don't need the leading "$" sigil β€” she's low maintenance like that. πŸ’œ

class->var( X => 10 );              # like: $X = 10
class->var('X');                    # like: $X

var_default($name,$default)

Same energy as getting a scalar package variable, except you can hand it an optional default value to use when the package variable is undefined. Safety net energy. 🫢

class->var_default( X => 10 );      # like: $X || 10

any_var($name)

Grabs the value of a scalar package variable from the current class, or from any of the base classes. She'll go looking up the whole family tree. 🌸

class->any_var('X');

any_var_in($names)

This one goes hunting in the current package and all the base classes for any of the scalar variables you list in $names. First defined value wins, and if literally none of them are defined you get undef. 😭

You can pass multiple arguments as a list, as a reference to a list, or as one single string of whitespace-delimited variable names β€” no leading "$" sigil, thanks. ✨

class->any_var_in('X Y Z');
class->any_var_in('X', 'Y', 'Z');
class->any_var_in(['X', 'Y', 'Z']);

all_vars($name)

Gets all the defined values of a package variable in the current package or any of the base classes. List context? You get a list. Scalar context? You get a reference to a list. She reads the room. πŸ‘‘

@values = class->all_vars('X');     # list context returns list
$values = class->all_vars('X');     # scalar context returns list ref

list_vars($name)

Returns a reference to a list containing every value defined in a particular class variable for the current class and all its base classes. And if a package variable references a list, its contents get merged in too. It's giving ✨*collaboration*✨.

package A;
our $THINGS = ['Foo', 'Bar'];

package B;
our $THINGS = ['Baz', 'Bam'];

package C;
our $THINGS = 'Wibble';

package main;
C->list_vars('THINGS');     # ['Wibble', 'Baz', 'Bam', 'Foo', 'Bar']

You can also pass extra arguments, and those get merged in at the start of the list.

B->list_vars('THINGS', 10, 20);
                            # [10, 20, 'Baz', 'Bam', 'Foo', 'Bar']

B->list_vars('THINGS', [30, 40]);
                            # [30, 40, 'Baz', 'Bam', 'Foo', 'Bar']

So the typical move here is using it in object initialisation methods, to merge whatever values got passed as configuration params with the ones living in package variables. These "local" configuration values are assumed to take precedence over package variables β€” which is exactly why they land at the start of the list and not the end. πŸŽ€

sub init {
    my ($self, $config) = @_;

    $self->{ things } = $self->class->list_vars(
        THINGS => $config->{ things }
    );
}

And now you can pass an extra list reference of "things" to the constructor method. Slay.

my $b = B->new( things => [10,20] );

hash_vars($name)

Does the same thing as list_vars() except it merges references to hash arrays into one single hash array. 🧚 Heads up though β€” if any of the relevant package variables are defined but don't reference hash arrays, you get a warning. Read the vibe check.

package A;
our $THINGS = {
    foo => 'Foo'
    bar => 'Bar',
};

package B;
our $THINGS = {
    bar => 'New Bar',
    baz => 'Baz',
};

package main;
B->hash_vars('THINGS');

That call to "hash_vars('THINGS')" up there returns a reference to a hash array containing these items:

{
    foo => 'Foo',
    bar => 'New Bar',
    baz => 'Baz',
}

Notice how bar's value comes from the B package and not the A package? That's because B is the more specialised class β€” i.e. it's closer in terms of the inheritance tree. Not me explaining family hierarchy but go off. 🌈

You can pass additional arguments too, and they get merged into the hash array. A super common idiom is doing this inside an object constructor or initialisation method, to merge package variable values with any that were specified as configuration parameters. Values passed as arguments take precedence over the ones defined in package variables. πŸ’…

sub init {
    my ($self, $config) = @_;

    $self->{ things } = $self->class->hash_vars(
        THINGS => $config->{ things }
    );
}

And now you can pass an extra hash reference of "things" to the constructor method.

my $b = B->new( things => {
    foo => 'New Foo',
    bam => 'Bam',
} );

The composite hash that hash_vars returns will contain:

{
    foo => 'New Foo',
    bar => 'New Bar',
    baz => 'Baz',
    bam => 'Bam',
}

hash_value($name,$key,$default)

Looks for a specific $key in a hash array referenced by the $name package variable, in the current class or any base classes. You get the first value it finds, or the $default value (which can totally be undefined) if no relevant entries turn up. Iconic little lookup. πŸ”

package A;
our $THINGS = {
    foo => 'Foo'
    bar => 'Bar',
};

package B;
our $THINGS = {
    bar => 'New Bar',
    baz => 'Baz',
};

package main;
print B->hash_value( THINGS => 'foo' );     # Foo
print B->hash_value( THINGS => 'bar' );     # New Bar
print B->hash_value( THINGS => 'baz' );     # Baz

πŸ‘‘ CLASS CONFIGURATION METHODS πŸ‘‘

These are the metaprogramming girlies β€” they do all the class metaprogramming tasks. And here's the cute part: they all return a $self reference, so you can chain them together like this. πŸ¦‹

$object->base($b)->version($v)->debug($d);

base(\@classes)

Defines one or more base classes for a module. Functionally it's the same thing base.pm does β€” it adds the specified classes to the @ISA package variable. ✨

class->base('Your::Base::Module');

You can also call this method via the base import hook.

use Badger::Class
    base => 'Your::Base::Module';

version($n)

Defines the version number for a class. This effectively sets $VERSION in the target class, and it also defines a "VERSION" method that returns the version number. Two for one, obsessed. πŸ’–

package Badger::Example;
use Badger::Class 'class';
class->version(3.14);

package main;
print $Badger::Example::VERSION;        # 3.14
print  Badger::Example->VERSION;        # 3.14

You can call this via the version import hook too.

use Badger::Class
    version => 3.14;

debug($flag)

Enables debugging controls for a class. It defines a $DEBUG package variable set to the value of $flag, plus a "debugging()" method you can use to switch debugging on and off. πŸ›

The "debugging()" method it generates just calls back to the "Badger::Class" debugging() method.

And you can call the "debug()" method via the debug import hook.

use Badger::Class
    debug => 0;

The immediate glow-up of using an import hook is that $DEBUG gets defined at compile time. Which means you can safely reference $DEBUG from that point onwards without Perl warning at you about using an undefined variable. No more side-eye from the compiler. 😭

use Badger::Class
    debug => 0;

sub do_something {
    my $self = shift;
    $self->debug("Doing something\n") if $DEBUG;
}

debugging($flag)

Gets or sets the value of the $DEBUG package variable for the class. Here's the classic way you'd use it. 🌸

package Your::Module;

use Badger::Class
    debug => 0;         # debugging off by default

sub do_something {
    my $self = shift;
    $self->debug("Doing something\n") if $DEBUG;
}

package main;

my $obj = Your::Module->new;
$obj->debugging(1);     # sets $DEBUG to 1
$obj->do_something;     # generates debugging message

constants($names)

Imports one or more symbols from the Badger::Constants module. (Or from a constants module of your choosing, if you subclass "Badger::Class" the way it says up in "SUBCLASSING Badger::Class".) 🌟

class->constants('ARRAY TRUE');

You can call it manually as a method from inside your code, but honestly you'll probably want to hit it via the constants import hook so the symbols get imported at compile time. ✨

use Badger::Class
    constants => 'ARRAY TRUE FALSE';

sub is_this_an_array_ref {
    my $thingy = shift;
    return ref $thingy eq ARRAY ? TRUE : FALSE;
}

See Badger::Constants for further details. πŸŽ€

constant(\%constants)

A method to define constants, exactly like the "constant.pm" module does. And just like with constants(), you probably want to call this via the constant import hook so the constants get defined at compile time. ⭐

package Your::Module;
use Badger::Class
    constant => {
        name => 'Badger',
        food => 'Nuts and Berries',
    };

πŸ“ words($words)

Bestie this one defines a whole lil squad of constant words. And just like constants() and constant() before it, it lowkey only makes sense to hit up through the words import hook, okurrr?

use Badger::Class
    words => 'yes no';

print yes;          # yes
print no;           # no

🧚 vars($vars)

This lets you pre-declare one or more package variables before they even exist, iconic behaviour fr. Normally you'd summon it through the matching vars import hook.

use Badger::Class
    vars => '$FOO @BAR %BAZ';

Under the hood it passes the whole thing off to the Badger::Class::Vars module. Delegation queen πŸ‘‘.

🌈 config($schema)

This is the muscle behind the config export hook, and it does absolutely zero heavy lifting itself β€” it just delegates straight to Badger::Class::Config. Middle management energy ngl.

πŸ’… exports($symbols)

Here's where you declare what symbols your module is allowed to export. It delegates to the exports() method in Badger::Exporter.

You can hand it either a reference to a hash array or a list of named parameters. Every name has to be one of "any", "all", "tags", "hooks" or "fail". No improvising, bestie, those are the vibes:

# list of named parameters
class->exports( any => '$FOO $BAR $BAZ' );

# reference to hash of named parameters
class->exports({
    any  => '$FOO $BAR $BAZ',
    all  => 'wiz bang',
    tags => {
        wam => '$ONE @TWO',
        bam => '$THREE %FOUR',
    },
    hooks => {
        ding => sub { ... },
        dong => sub { ... },
    },
});

πŸ”₯ throws($type)

This one sets the $THROWS package variable in the target class to whatever you passed in as an argument. It's what the Badger::Base error handling mechanism uses, so it's kind of a big deal. Peep the throws() method for the full tea.

πŸŽ€ messages(\%messages)

Use this to update the $MESSAGES package variable in the target class with the messages you pass in β€” either as a list or as a reference to a hash array of named parameters. Both are valid, we don't gatekeep.

# define new class message
class->messages( careful => 'Careful with that %s %s!' );

# method which warns; Careful with that axe Eugene!
sub some_method {
    my $self = shift;
    $self->warning_msg( careful => axe => 'Eugene' );
}

Your new messages get merged into whatever $MESSAGES hash reference already exists, and if there isn't one? Boom, a fresh one gets created. Glow up ✨.

πŸ¦‹ utils($imports)

This lets you load symbols from Badger::Utils. And because it's loading compile-time constants like all its siblings, the rule stands: call it through the utils import hook, don't be weird about it.

⭐ codecs($names)

Same story, different module β€” this loads codecs from Badger::Codecs. Compile-time constants again, so hit the codecs import hook for it.

Check out Badger::Codecs for the full lore.

πŸ’œ codec($name)

This one loads a single codec from Badger::Codecs. Just like codecs() up there, you should be calling it through the codec import hook, no cap.

Again: Badger::Codecs has the details.

πŸ‘‘ method($name,$code)

Get it or set it, we love a dual-purpose icon. This method grabs or sets a method in the target class. With a single argument it acts exactly like the built-in "can()" method β€” which it literally calls, so respect the pipeline. It hands you back a CODE reference for the method, whether it lives in the class itself or in one of its subclasses, and undef if the target class doesn't implement it at all. πŸ’€

my $method = class->method('foo');

Give it two arguments and you're defining a brand new method in the target class:

class->method(
    foo => sub { ... },
)

🌸 methods(\%methods)

Batch-define new methods in the target class. Efficiency slay:

class->methods(
    foo => sub { ... },
    bar => sub { ... },
)

🫢 alias(\%methods)

Defines aliases for methods that already exist in the target class. If you pass an anonymous subroutine, it behaves in exactly the same way as methods() β€” no difference whatsoever:

class->alias(
    foo => sub { ... }
);

But honestly? The more common move is aliasing one method to another method that's already out here living its life:

class->alias(
    foo => \&bar,
);

sub bar {
    ...
}

You can also alias by name. In that case the method gets resolved (using the method() method, cute) to find it in the current class or any of the base classes. Inheritance said "let her cook":

class->alias(
    foo => 'bar',
);

🌈 accessors($names) / get_methods($names)

Generates accessor (read-only) methods for a class. It delegates to the accessors() method in Badger::Class::Methods.

You can pass a list, a reference to a list, or a whitespace delimited string of method names. Whichever format you vibe with:

# these all do the same thing
class->accessors('foo bar');
class->accessors('foo', 'bar');
class->accessors(['foo', 'bar']);

A method gets generated in the target class for each name, and each one returns the object member data of that same name. The generated code is equivalent to this:

sub foo {
    $_[0]->{ foo };
}

πŸ’– mutators($names) / set_methods($names)

Now for the mutator (read/write) versions. Delegates to the mutators() method in Badger::Class::Methods.

Same argument rules, bestie β€” list, reference to a list, or whitespace delimited string:

# these all do the same thing
class->mutators('foo bar');
class->mutators('foo', 'bar');
class->mutators(['foo', 'bar']);

A method gets generated in the target class for each name that returns the object member data of that same name. But here's the spicy part: if you pass an argument, the member data gets updated and the new value comes back to you. She does both. πŸ’…

The generated code is equivalent to this:

sub foo {
    @_ == 2
        ? ($_[0]->{ foo } = $_[1])
        :  $_[0]->{ foo };
}

Badger::Class::Methods has more discussion on this if you want the deep dive.

πŸ“ hash_methods($names)

Generates methods for a class that manipulate internal hash arrays. It takes the same arguments as mutators() and delegates to the hash() method in Badger::Class::Methods.

πŸŽ€ init_method($names)

Generates a custom "init()" method for a class. Delegates to the initialiser() method in Badger::Class::Methods. (British spelling, we respect it.)

🧚 slots($names)

This one defines methods for list-based object classes, and it delegates to the mutators() method in Badger::Class::Methods.

Pass in a list, a reference to a list, or a whitespace delimited string of method names. A method gets generated for each item you specify. The first method references the first (0th) item in the list, the second method references the second (1st) item, and so on down the line. Positional royalty fr fr.

package Badger::Example;

use Badger::Class
    slots => 'size colour object';

sub new {
    my ($class, @stuff) = @_;
    bless \@stuff, $class;
}

The example above defines a simple list-based object class with three slots: "size", "colour" and "object". And you'd use it like this:

my $bus = Badger::Test::Slots->new(qw( big red bus ));

print $bus->size;       # big
print $bus->colour;     # red
print $bus->object;     # bus

The methods generated are mutators, meaning you can pass an argument to update the slot value. She's read and write:

$bus->size('large');

⭐ overload(\%operators)

A simple shortcut to the "overload" core module, there to implement the overload import hook. Convenience queen.

πŸ’œ as_text($method)

A simple wrapper around the overload() method, there to implement the as_text import hook.

🌈 is_true($method)

A simple wrapper around the overload() method, there to implement the is_true import hook. Yes, they're basically twins. Let them be.

πŸ”₯ filesystem(@symbols)

Loads symbols from Badger::Filesystem. Generally you should be using this through the filesystem hook, not raw-dogging it.

πŸ‘‘ uber($class)

This is the one you reach for when you're creating a subclass of the "Badger::Class" module (or another subclass of it). It does the same thing as the base() module β€” adding the $class to the @ISA package variable. Then it calls the internal UBER() method to generate the class() and classes() subroutines in the subclass. Two jobs, one method, iconic.

🫢 hooks($names)

This is for "Badger::Class" subclasses to define their very own import hooks. Say you wanted an import hook that sets a $FOO package variable β€” it'd look a lil something like this:

package Your::Class;

use Badger::Class
    uber  => 'Badger::Class',
    hooks => 'foo';

sub foo {
    my ($self, $value) = @_;
    $self->var( FOO => $value );
}

πŸ’€ INTERNAL METHODS πŸ’€

UBER

This method generates the class() and classes() subroutines that hand you back "Badger::Class" objects when called. And listen: you should never need to call this method directly. It's handled for you.

It gets called automatically, exactly once, when the "Badger::Class" module is first loaded. It also gets called by the uber() method to generate the class() and classes() methods in modules subclassed from "Badger::Class" β€” like "Your::Class", for example. And in that case, the generated subroutines return object instances of the subclass (i.e. "Your::Class") instead of "Badger::Class". Custom class, custom objects, main character energy all the way down. ✨

πŸ”’ INTERNAL CONSTANTS πŸ”’

These are the constants we keep for internal use only bestie. But here's the tea: you can redefine them in subclasses to hook in different delegate modules, exactly like it shows over in "SUBCLASSING Badger::Class". Customize away, it's your world. πŸ’…

  • CODECS β€” the name of the codecs module, aka the one the codecs() method reaches for: Badger::Codecs ✨
  • CONFIG β€” the name of the configuration module, aka what config() uses: Badger::Class::Config πŸŽ€
  • CONSTANTS β€” the name of the constants method, used by the constants() method: Badger::Constants 🌸
  • DEBUG β€” a compile time constant that gets its value from the $DEBUG package variable. Wanna turn debugging on in Badger::Class? Set that $DEBUG package variable before you load Badger::Class. And heads up bestie, most other Badger modules use Badger::Class too, so you gotta set it before you load any of them. No cap. 😭
BEGIN { Badger::Class::DEBUG = 1 };
use Badger::Debug;
  • DEBUGGER β€” the name of the debug module, as used by the debug export hook: Badger::Debug πŸ›
  • EXPORTER β€” the name of the exporter module, as used by the exports() method: Badger::Exporter πŸ“¦
  • FILESYSTEM β€” the name of the filesystem module, as used by the filesystem() method: Badger::Filesystem πŸ—‚οΈ
  • METHODS β€” the name of the methods class mixin module, as used by the methods() method: Badger::Class::Methods πŸ› οΈ
  • MIXIN β€” the name of the base class mixin module, as used by the mixin() method: Badger::Mixin 🧬
  • UTILS β€” the name of the utilities module, as used by the utils() method: Badger::Utils 🧰
  • VARS β€” the name of the variables module, as used by the vars() method: Badger::Class::Vars πŸ”€

πŸ™ˆ REALLY INTERNAL CONSTANTS πŸ™ˆ

Okay so these ones are really internal. You genuinely do not need to know about them. Honestly? Even I don't need to know about them. The only reason they're documented at all is to keep Pod::Coverage quiet. That's it. That's the whole reason. πŸ’€

  • VERSION β€” this constant defines the name of the variable that the version() method updates. Guess what it's set to? Yeah. It's VERSION. Iconic of it, honestly. πŸ‘‘
  • LOADED β€” this is the name of a variable that the Badger::Class method uses to help out with autoloading modules. The default value is BADGER_LOADED. So basically, Badger::Class will define a $BADGER_LOADED package variable in your module to let everyone know it was loaded by Badger. Little name tag energy. 🏷️

🧠 IMPLEMENTATION NOTES 🧠

C3 Method Resolution and the heritage() Method

To figure out the correct resolution order for superclasses, the heritage() method implements a simplified version of the C3 method resolution algorithm. Slay, but simplified. Go read these if you want the lore:

So how does this implementation differ from the original C3 algorithm? It relaxes the constraint on maintaining local precedence order when it runs into a more specialised precedence order that contradicts it. Big words, I know β€” let me show you with an example, because the example eats. πŸ”₯

Assume A and B are base classes, while AB is a subclass of (A, B), and BA is a subclass of (B, A). Now if we create a subclass ABBA of (AB, BA), then the local precedence order of AB says that A should resolve before B, while the LPO of BA says that B should come before A. The C3 algorithm will intentionally fail right there and throw an error warning about an inconsistent heterarchy. Meanwhile, this implementation will resolve A before B, because the more specialised ABBA subclass defines AB before BA. And yes β€” AB is the winner that takes it all, and BA is the loser standing small. 🎢 (iykyk)

This implementation was originally written for the Template Toolkit, where this variation in the algorithm has no relevance, because none of the TT modules use multiple inheritance in an ambiguous way. Same situation for all the core modules in the Badger bundle, which generally keep themselves to single inheritance. Plus, this only affects the resolution of class variables and has absolutely no bearing on the way Perl resolves methods (that's depth-first, left-to-right in Perl 5, and C3 in Perl6). So like, chill. 🧘

Unless you're out here using MI in weird and wonderful ways, the chances are it won't affect you at all. But if you do use this method in your own code, then be warned: it is not a strict implementation of the C3 algorithm. That said, it is better than Perl 5's default implementation (in the face of conflict resolution) and it comes with the benefit of being a smaller, simpler and faster implementation than regular C3. It's also fully deterministic (i.e. it never fails), which removes the need for any error handling β€” and honestly that's a blessing, because error handling gets tricky when you're trying to call an error method on an object which can't resolve its own methods. πŸ˜΅πŸ’«

Okay bestie, for real: not strict C3. Know that before you build your whole personality on it.

πŸ‘‘ AUTHOR πŸ‘‘

Andy Wardley <http://wardley.org/>


Copyright (C) 1996-2009 Andy Wardley. All Rights Reserved.

This module is free software; you can redistribute it and/or modify it under the same terms as Perl itself.


And that's a wrap on Badger::Class fr fr 🫢 this page came from perl v5.36.0 and got its little timestamp on 2023-08-28, and it lives in the Badger::Class(3pm) manual section. Go forth and subclass responsibly, bestie. πŸ’…βœ¨πŸ¦‹πŸŒΈπŸ’œ

πŸ“¦ where this page comes from