NAME

Marlin::Manual::Beginning - getting started with object-oriented programming and Marlin

DESCRIPTION

The examples in this manual assume you are using Perl 5.20 or above, and have included use v5.20; (or a higher version) at the top of your file to enable any modern Perl features that are disabled by default. Marlin does support older version of Perl, but this manual is written with modern Perl in mind.

Additionally, they assume you have subroutine signatures enabled. This is enabled by default if you have use v5.36; (or a higher version) at the top of your file. If you are targetting older versions of Perl, you can enable it using use experimental "signatures";. In summary, make sure your file starts with one of these two prologues:

use v5.20; # or higher
use experimental "signatures";

Or:

use v5.36; # or higher

Objects

Object-oriented programming is a programming style based on objects, which are entities that encapsulate data and can be manipulated by calling methods.

Example 1

my $user = get_current_user();

$user->change_password_to( "S3CRET" );

Here $user is an object and change_password_to is method that we are calling on the object. Presumably one of the pieces of data encapsulated by the $user object is a password. It probably has other information associated with the user too, like their username and perhaps their real name, email address, and so on.

One of the principles of encapsulation is that from outside the object, we don't always need to think about what data is stored inside the object. When we call the change_password_to method, we assume it's probably updating a password it has stored internally, but it may also be doing other things like setting a "last updated" date, and emailing the user to inform them that their password was changed. (Though hopefully not insecurely including the new password in the email!)

Classes and Instances

Objects are typically defined using classes. A class can be thought of as a "kind" or "type of thing". Rather than Alice's user object and Bob's user object needing to define what data they store and what methods they provide from scratch, we define a "User" class which $alice and $bob will be instances of.

In Perl, a class is just a Perl package that we decide to use as a class. For example:

Example 2

package Local::User {}

This class is not very interesting or very useful.

Constructors

We need a way to make instances of the class. We do that with a constructor. A simple constructor is pretty easy to write in Perl.

Example 3

package Local::User {
  sub new ( $class ) {
    return bless( {}, $class );
  }
}

The bless keyword is a Perl built-in that takes any reference and associates it with a particular package.

We can now use our constructor.

Example 3.1

my $alice = Local::User->new();
my $bob   = Local::User->new();

die if $alice == $bob;  # different objects, does not die

This is slightly more useful than Example 2 because at least we can create instances of the class.

While writing simple constructors like this is easy in Perl, it is also repetitive. Writing complex constructors is an even bigger chore and it's easy to mess up and get things wrong. For this reason, Perl has a number of toolkits to do it for you. In Perl v5.38 and above, there's even a newer, improved way to define classes.

Here is how this class could have been written using a few of those toolkits.

Example 4

# Moose
package Local::User {
  use Moose;
}

# Moo
package Local::User {
  use Moo;
}

# Mouse
package Local::User {
  use Mouse;
}

# Class::Tiny
package Local::User {
  use Class::Tiny;
}

# Marlin
package Local::User {
  use Marlin;
}

# New Perl 5.38 style
use v5.38;
use feature 'class';
class Local::User {}

Apart from the last one, I'm sure you can see a pattern here.

This is the Marlin manual, so of course we will focus on using Marlin to build your classes, but we may touch on the others occasionally.

As an aside, there are object-oriented languages which don't use classes, or rely less on the idea of classes. It is possible to program like that in Perl too, but class-based object-oriented programming is what dominates, in Perl and in most languages.

Attributes

The objects constructed in Example 3 are a start. We can create two Local::User objects and see that they're different users, but that's pretty much all we can do. We're not storing any information about Alice and Bob in the objects, so they're not really meaningfully different.

When we introduced the idea of objects, we said they encapsulate data. To do this, we define attributes (also referred to as properties or members in some programming languages). Let's assume that we want our Local::User objects to store the user's username, password, real name, email address, and timestamps for when the user was created and when it was last updated. In Marlin, this is really easy:

Example 5

package Local::User {
  use Marlin qw( username password name email created updated );
}

(The qw() operator is a simple way to create a list or words in Perl. It is not specific to Marlin. qw( foo bar ) creates a list ( "foo", "bar" ). Expect this manual to frequently switch between using qw() and more explicit lists with no further explanation. See perlop for more information on the qw() operator.)

If you're familiar with Moose, Mouse, or Moo, that might be more familiar as:

Example 5.1

package Local::User {
  use Moose;
  has username  => ( is => 'ro' );
  has password  => ( is => 'ro' );
  has name      => ( is => 'ro' );
  has email     => ( is => 'ro' );
  has created   => ( is => 'ro' );
  has updated   => ( is => 'ro' );
}

Instantly you can see Marlin saves a lot of typing. (Or a lot of copy and pasting!) One principle Marlin follows is to try to encourage good practices by making them easy. This is an example of that.

Now let's create some users.

Example 5.2

my $alice = Local::User->new(
  name      => 'Alice Smith',
  username  => 'as1',
  password  => 'S3CRET',
);

my $bob = Local::User->new(
  name      => 'Bob Dobalina',
  username  => 'bd1',
  password  => 'h1dd3n',
  email     => 'bob.dob@example.net',
);

As you can see, the values for each attribute are passed to the constructor as key-value pairs. The constructor uses those values to initialize each attribute.

Required and Optional Attributes

As you can see in Example 5.2, we didn't initialize all of the attributes. We didn't initialize Alice's email address, nor the created/updated timestamps for either user. But if certain attributes are missing, it may make using the objects harder in the future. How would we be able to log in as a user if there is no username?

Let's decide to make at least the username and password required, but leave the rest as optional.

Example 6

package Local::User {
  use Marlin qw(
    username!
    password!
    name?
    email?
    created?
    updated?
  );
}

Adding the exclmation mark indicates that an attribute is required. It means that it is an error to create an object without providing those values to the constructor. The constructor will complain if you forget them.

Example 6.1

# This will result in an error!
my $eve = Local::User->new( name => 'Eve Jones' );

Adding the question mark indicates that an attribute is optional, and automatically creates a method for the object to check if a value was provided.

Example 6.2

if ( $alice->has_email ) {
  say $alice->email;
}

The has_email method returns true if email was set, and false if it was not. The has_email method is called a predicate method, while the email method is called a reader or getter. Readers for an attribute usually have the same name as the attribute itself.

The default, if neither an exclamation mark nor a question mark is used, is for the attribute to be optional, but no predicate method created.

It is possible to set the email attribute to a false or empty value and has_email will still return true. Setting an attribute, even to a false value, still counts as setting it.

Example 6.3

my $eve = Local::User->new(
  username  => 'ej1',
  password  => 'UwillNEVERguess',
  email     => undef,
);

if ( $eve->has_email ) {
  # This will try to print an undefined value and
  # may issue a warning because of that!
  say $eve->email;
}

Type constraints can help protect against situations like that.

Type Constraints

It is useful to be able to indicate for each attribute, what type of data it should expect. Passing the wrong kind of data to the constructor will then result in an error.

Example 7

package Local::User {
  use Email::Address;
  use Types::Common -types, -lexical;
  use Marlin
    'username!'  => NonEmptyStr,
    'password!'  => NonEmptyStr,
    'name?'      => NonEmptyStr,
    'email?'     => StrMatch[ $Email::Address::addr_spec ],
    'created?'   => PositiveOrZeroInt,  # timestamp
    'updated?'   => PositiveOrZeroInt;  # timestamp
}

Marlin works in conjunction with Types::Standard, Types::Common, and other Type::Tiny-based type libraries. Just import your preferred type library before you use Marlin and you'll be able to validate each attribute using type constraints.

Example 7.1

# This will result in an error because the email isn't a
# string matching the required regular expression.
my $eve = Local::User->new(
  username  => 'ej1',
  password  => 'UwillNEVERguess',
  email     => undef,
);

Defaults for Attributes

For non-required attributes, it may make sense to provide a default value. This is very simple: just provide a reference to a sub which returns the default value.

Example 8

package Local::User {
  use Email::Address;
  use Types::Common -types, -lexical;
  use Marlin
    'username!'  => NonEmptyStr,
    'password!'  => NonEmptyStr,
    'name?'      => NonEmptyStr,
    'email?'     => StrMatch[ $Email::Address::addr_spec ],
    'created'    => sub { return time() },
    'updated'    => sub { return time() };
}

Here we've removed the question marks by the created and updated attributes because we no longer really need has_created and has_updated predicate methods. We know the object will always have a created timestamp and a updated timestamp because if they are not passed to the constructor the default will be used.

Methods

A method is a thing your object is capable of doing. To add methods to a class, we just defined subs in the class. Each method takes the object as its first argument. By convention, the variable name for the object is $self.

Let's define a change_password_to method.

Example 9

package Local::User {
  use Email::Address;
  use Types::Common -types, -lexical;
  use Marlin
    'username!'  => NonEmptyStr,
    'password!'  => NonEmptyStr,
    'name?'      => NonEmptyStr,
    'email?'     => StrMatch[ $Email::Address::addr_spec ],
    'created'    => sub { return time() },
    'updated'    => sub { return time() };
  
  sub change_password_to ( $self, $new_password ) {
    
    die "Password too short" if length($new_password) < 6;
    ...;
  }
}

Seems simple, right?

But that leads us to the next topic. How does the method alter the data stored in $self to update the user's password?

Writer Methods

Similar to the reader (or getter) method introduced in Example 6.2, Marlin can create writer methods, also called setter methods. By default, Marlin does not create writer methods, but you can add them on a per-attribute basis.

Example 10

package Local::User {
  use Email::Address;
  use Types::Common -types, -lexical;
  use Marlin
    'username!'   => NonEmptyStr,
    'password=!'  => NonEmptyStr,
    'name?'       => NonEmptyStr,
    'email?'      => StrMatch[ $Email::Address::addr_spec ],
    'created'     => sub { return time() },
    'updated='    => sub { return time() };
  
  sub change_password_to ( $self, $new_password ) {
    
    die "Password too short" if length($new_password) < 6;
    
    $self->_set_password( $new_password );
    $self->_set_updated( time() );
    
    return $self;
  }
}

Adding an equals sign instructs Marlin to create a writer method. Marlin names the writer method "_set_" followed by the attribute name. By convention, any methods that start with an underscore are considered part of the class's internal API and should not be used by third parties.

The change_password_to method simply calls the _set_password method to change the password data stored inside the object. It also calls _set_updated to change the updated timestamp, recording when the user object was last updated.

Let's see our new method being used.

Example 10.1

my $eve = Local::User->new(
  username  => 'ej1',
  password  => 'UwillNEVERguess',
);

$eve->change_password_to( 'even+MORE_secret~4-u' );

say $eve->password;  # should say the new password

Note that it doesn't matter which way around you include these trailing symbols in the attribute name: 'password=!' or 'password!=' are the same thing.

Accessors

The change_password_to method allows our class a lot of control over how passwords will be changed, enforcing security policies, updating the updated timestamp, etc.

Sometimes we wish to allow an attribute to be updated with a lot less ceremony. Take, for example, Local::User's name attribute. For the purposes of running the system, it doesn't really matter what a user's real name is. In this case we can provide an accessor method to allow people to update it. Accessor methods are sometime also called mutators.

An accessor is a combined reader and writer method. To request an accessor, use a double-equals sign. See the "name==?" attribute in Example 11.

Example 11

package Local::User {
  use Email::Address;
  use Types::Common -types, -lexical;
  use Marlin
    'username!'   => NonEmptyStr,
    'password=!'  => NonEmptyStr,
    'name==?'     => NonEmptyStr,
    'email?'      => StrMatch[ $Email::Address::addr_spec ],
    'created'     => sub { return time() },
    'updated='    => sub { return time() };
  
  sub change_password_to ( $self, $new_password ) { ... }
}

Marlin will name the accessor what it would have named the reader, and create the accessor instead of the reader.

When calling the accesor method without any extra arguments, it acts like a reader. But when you include an additional argument, it acts like a writer.

Example 11.1

my $eve = Local::User->new(
  name      => 'Eve Jones',
  username  => 'ej1',
  password  => 'UwillNEVERguess',
);

# Acts like a reader
say "My name is " . $eve->name;

# Acts like a writer
$eve->name( "Slim Shady" );

# Acts like a reader again
say "My name is " . $eve->name;

Note that the term accessor is also used as an umbrella term to cover readers, writers, accessors, predicates, and (though these haven't been introduced in the manual yet) clearers.

When to use readers/getters and when to use accessors/mutators

It makes sense to default to attributes having only reader methods. If you change your mind later and realize you want the attribute to be read-write, you can turn it into an accessor later. For anybody who is already using that method as a reader, it will continue to work as normal.

Changing your mind the other way, and switching what was an accessor into just a reader could break the code of anybody trying to use the accessor to set a new value.

Your class is also likely to be easier to understand and debug if you know that code outside your class isn't going to be tampering with the values of your attributes.

Forbidding Explicit Attribute Initialization

If the Local::User class is capable of managing the created and updated timestamps itself, we might not want code outside the class setting them.

If the attributes just have readers, outside code can't set a new timestamp using an accessor. The code in Example 12 should result in an error.

Example 12

# Alice was created in the future???
$alice->created( time() + 3600 );

However, code outside the class can set them when the object is first created. See Example 12.1.

Example 12.1

# Alice was created in the future???
my $alice = Local::User->new(
  username  => 'as1',
  password  => 'S3CR3T',
  created   => time() + 3600,
);

We can prevent this by forbidding passing a value for that attribute to the constructor.

Example 12.2

package Local::User {
  use Email::Address;
  use Types::Common -types, -lexical;
  use Marlin
    'username!'   => NonEmptyStr,
    'password=!'  => NonEmptyStr,
    'name==?'     => NonEmptyStr,
    'email?'      => StrMatch[ $Email::Address::addr_spec ],
    '.created'    => sub { return time() },
    '.updated='   => sub { return time() };
  
  sub change_password_to ( $self, $new_password ) { ... }
}

Note the dot before the attribute name. This is the only piece of modifying punctuation that occurs at the start of the attribute name, though it is also supported at the end for consistency with the exclamation mark and question mark.

With this change, the code in Example 12.1 will result in an error. The created and updated attributes cannot be explicitly set when constructing the object.

Note that if you forbid an attribute from being initialized like this, you should probably ensure there is another way to set its value, like a writer or a default. Otherwise there's not much point in the attribute exiting at all!

Options Hashrefs

One thing that I glossed over but you may have noticed is that each of our attributes has either a type constraint or a default, but there doesn't seem to be a way for an attribute to have both!

If you want to have both, you'll have to switch to using a slightly more verbose syntax and provide a hashref of options for the attribute. Let's ensure that our timestamps have the type PositiveOrZeroInt.

Example 13

package Local::User {
  use Email::Address;
  use Types::Common -types, -lexical;
  use Marlin
    'username!'   => NonEmptyStr,
    'password=!'  => NonEmptyStr,
    'name==?'     => NonEmptyStr,
    'email?'      => StrMatch[ $Email::Address::addr_spec ],
    '.created'    => {
      isa           => PositiveOrZeroInt,
      default       => sub { return time() },
    },
    '.updated='   => {
      isa           => PositiveOrZeroInt,
      default       => sub { return time() },
    };
  
  sub change_password_to ( $self, $new_password ) { ... }
}

Instead of following the attribute name with a type constraint or a coderef for a default, we have a hashref that includes both of them. Other options are also allowed in this hashref. In this part of the manual, we will only address one other option.

Eager Versus Lazy Defaults

The defaults introduced in Example 8 and the defaults shown in Example 13 actually have a subtle difference.

Defaults provided by a simple coderef without an options hashref (like in Example 8) are lazy builders. This means that the constructor actually ignores the default, and instead the reader method will set the attribute value to the default if it notices the attribute hasn't been set yet.

When you supply a default using a hashref, it is no longer lazy; it is eager. This means that the default value will be set in the constructor.

In our case the values being generated are timestamps, so are time sensitive. For that reason, we probably do want them to happen in the constructor. But we can control whether they are lazy or eager via another option.

Example 14

package Local::User {
  use Email::Address;
  use Marlin::Util qw( true false ), -lexical;
  use Types::Common -types, -lexical;
  use Marlin
    'username!'   => NonEmptyStr,
    'password=!'  => NonEmptyStr,
    'name==?'     => NonEmptyStr,
    'email?'      => StrMatch[ $Email::Address::addr_spec ],
    '.created'    => {
      isa           => PositiveOrZeroInt,
      lazy          => false,
      default       => sub { return time() },
    },
    '.updated='   => {
      isa           => PositiveOrZeroInt,
      lazy          => false,
      default       => sub { return time() },
    };
  
  sub change_password_to ( $self, $new_password ) { ... }
}

The lazy option controls whether a default is lazy or eager. If you don't include the lazy option, defaults will default to being eager unless you provide a default without a hashref, in which case they default to being lazy.

Note that we imported true and false keywords from Marlin::Util. There are other modules that offer convenient boolean keywords, such as boolean and builtin. Alternatively, the numbers 0 for false and 1 for true also work.

Polymorphism

This is a big and important part of object-oriented programming. It was previously mentioned that because of encapsulation, an object not only contains all the data it needs, but all the methods needed to operate on that data.

So for example a Local::User object might have a rename method to change the user's name. But a Local::Video object might also have a rename method. And so might a Local::Image object.

Example 15

# Is it a user, a video, or an image?
# We don't care!
$object->rename( $new_name );

Inheritance

Rather than writing separate rename method for videos and images, we might define a class Local::Media and indicate that videos and images are just different kinds of media.

Example 16

package Local::Media {
  use Marlin
    qw( name== );
  
  sub rename ( $self, $new_name ) {
    warn "Renaming to $new_name...";
    $self->_set_name( $new_name );
  }
  
  sub rename_randomly ( $self ) {
    my $random_name = ...;
    $self->rename( $random_name );
  }
}

package Local::Video {
  use Marlin -extends => "Local::Media",
    qw( height width runtime );
  
  sub play ( $self ) {
    ...;
  }
}

package Local::Image {
  use Marlin -extends => "Local::Media",
    qw( height width );
    
  sub show ( $self ) {
    ...;
  }
}

my $pic = Local::Image->new;
my $vid = Local::Video->new;

for my $thing ( $pic, $vid ) {
  $thing->rename_randomly if $thing->isa( 'Local::Media' );
}

The Local::Media class is called a base class or parent class. Local::Video and Local::Image are both derived classes or child classes which inherit from or extend the base class.

This example also shows the isa method, a special method which all objects in Perl have. It can be used to check if an object is an instance of a particular class, and it takes inheritance into account.

As you can see in the example, Marlin indicates the base class using the -extends option. Alternatively you can use the -parent, -base, or -isa options. They all do the same thing; just use whichever you like the sound of more.

Example 16.1

package Local::AudioRecording {
  use Marlin -base => "Local::Media", qw( runtime );
}

package Local::Sketch {
  use Marlin -parent => "Local::Image";
}

package Local::Photograph {
  use Marlin -isa => "Local::Image";
}

Roles

Sometimes there's not an obvious base class to put shared methods into. Like Local::User also has a rename method, but it's not a type of media. In this case, you can create a special type of package called a role. Just use Marlin::Role instead of Marlin.

Example 17

package Local::Nameable {
  use Marlin::Role qw( name== );
  
  sub rename ( $self, $new_name ) {
    $self->_set_name( $new_name );
  }
  
  sub rename_randomly ( $self ) {
    my $random_name = ...;
    $self->rename( $random_name );
  }
}

package Local::User {
  use Email::Address;
  use Marlin::Util qw( true false ), -lexical;
  use Types::Common -types, -lexical;
  use Marlin
    -with         => [ "Local::Nameable" ],
    'username!'   => NonEmptyStr,
    'password=!'  => NonEmptyStr,
    'email?'      => StrMatch[ $Email::Address::addr_spec ],
    '.created'    => {
      isa           => PositiveOrZeroInt,
      lazy          => false,
      default       => sub { return time() },
    },
    '.updated='   => {
      isa           => PositiveOrZeroInt,
      lazy          => false,
      default       => sub { return time() },
    };
  
  sub change_password_to ( $self, $new_password ) { ... }
}

package Local::Media {
  use Marlin -with => [ "Local::Nameable" ];
}

package Local::Video {
  use Marlin -base => "Local::Media",
    qw( height width runtime );
  
  sub play ( $self ) {
    ...;
  }
}

...;

The Local::Nameable package is now a role. A role is like a class, except that you cannot create instances of it. It needs to be consumed by classes, and you can create instances of those classes.

The -with option indicates which roles a class should consume. It can also be written as -does or -roles. Again they all do the same thing; just use whichever you like the sound of more.

SEE ALSO

Marlin::Manual::BetterAttributes - next steps defining attributes.

Marlin, Marlin::Util.

AUTHOR

Toby Inkster <tobyink@cpan.org>.

COPYRIGHT AND LICENCE

This software is copyright (c) 2026 by Toby Inkster.

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

DISCLAIMER OF WARRANTIES

THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.