The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

Dios - Declarative Inside-Out Syntax

VERSION

This document describes Dios version 0.000007

SYNOPSIS

    use Dios;

    # Declare a derived class...
    class Identity is Trackable {

        # All instances share these variables...
        shared Num %!allocated_IDs;   # Private and readonly
        shared Num $.prev_ID is rw;   # Public and read/write

        # Declare a function (no invocant)...
        func _allocate_ID() {
            while (1) {
                my $ID = rand;
                return $prev_ID =$ID if !$allocated_IDs{$ID}++;
            }
        }

        # Each instance has its own copy of each of these attributes...
        has Num $.ID     = _allocate_ID();  # Initialized by function call
        has Str $.name //= '<anonymous>';   # Initialized by ctor (with default)

        has Passwd $!passwd;                # Private, initialized by ctor

        # Methods have $self invocants, and can access attributes directly...
        method identify ($pwd) {
            return "$name [$ID]" if $pwd eq $passwd;
        }

        # Destructor (submethods are class-specific, not inheritable)...
        submethod DESTROY {
            say "Bye, $name!";
        }
    }

DESCRIPTION

This module provides a set of compile-time keywords that simplify the declaration of encapsulated classes using the "inside out" technique.

The encapsulation, constructor/initialization, destructor, and accessor generation behaviours are all transparently delegated to the Object::Insideout framework. Type checking is provided by the Dios::Types module. Parameter list features are similar to those provided by Method::Signature or Kavorka, but are implemented by the module itself.

As far as possible, the declaration syntax (and semantics) provided by Dios aim to mimic that of Perl 6, except where intrinsic differences between Perl 5 and Perl 6 make that impractical, in which cases the module attempts to provide a replacement syntax (or semantics) that is likely to be unsurprising to experienced Perl 5 programmers.

Note: This module relies on the Keyword::Declare module, which is still in alpha, and which still has compile-time performance issues. Expect slow compiles, but then fast execution. The slow compiles will disappear as Keyword::Declare improves.

INTERFACE

Declaring classes

The module provides a class keyword for declaring classes. The class name can be qualified or unqualified:

    use Dios;

    class Transaction::Source {
        # class definition here
    }

    class Account {
        # class definition here
    }

Specifying inheritance relationships

To specify a base class, add the is keyword after the classname:

    class Account::Personal is Account {
        # class definition here
    }

You can specify multiple bases classes multiple is keywords:

    class Account::Personal is Account is Transaction::Source {
        # class definition here
    }

Declaring object attributes

Within a class, attributes (a.k.a. fields or data members) are declared with the has keyword:

    class Account {

        has $.name is rw //= '<unnamed>';
        has $.ID   is ro   = gen_unique_ID();
        has $!pwd;

        # etc.
    }

Attribute declaration syntax

The full syntax for an attribute declaration is:

    has  <TYPE>  $  [!.]  <NAME>  [is [rw|ro]]  [is req]  :<OPT>  [//=|=] <EXPR>
         ......  .  ....  ......   ..........   ........  ......   ... .  ......
            :    :    :      :          :           :        :      :  :     :
    Type....:    :    :      :          :           :        :      :  :     :
    Scalar sigil.:    :      :          :           :        :      :  :     :
    Public/private....:      :          :           :        :      :  :     :
    Attribute name...........:          :           :        :      :  :     :
    Readonly/read-write.................:           :        :      :  :     :
    Required initialization.........................:        :      :  :     :
    Object::Insideout options................................:      :  :     :
    Default initialized.............................................:  :     :
    Always initialized.................................................:     :
    Initialization value.....................................................:

The various components of an attribute definition must be specified in this order. For example, this is acceptable:

    has Ref $.parent  is ro  is req  :Weak  //= get_common_ancestor();

...but this is not:

    has Ref $.parent  :Weak  is ro  //= get_common_ancestor()  is req;

In particular, any explicit "colon" modifiers like :Weak must appear after any is modifiers, and any initializer expression must be at the end of the entire declaration.

Typed attributes

Attributes can be given a type, by specifying the typename immediately after the has keyword:

        has  Str     $.name;
        has  Int     $.ID;
        has  PwdObj  $!pwd;

You can use any type supported by the Dios::Types module. Untyped attributes can store any Perl scalar value (i.e. their type is Any).

Attribute types are checked on initialization, on direct assignment, and when their write accessor (if any) is called.

Public vs private attributes

An attribute specification can autogenerate read/write or read-only accessor methods (i.e. "getters" and "setters"), if you place a . after the variable's $:

    has $.name;    # Generate accessor methods

Such attributes are referred to as being "public".

If you don't want any accessors generated, use a ! instead:

    has $!password;    # Doesn't generate accessor methods (i.e. private)

Such attributes are referred to as being "private".

Read-only vs read-write attributes

By default, a public attribute autogenerates only a read-accessor (a "getter" method that returns its current value). To request that full read-write accessors ("getter" and "setter") be generated, specify is rw after the attribute name:

    has $.name;          # Autogenerates only getter method
    has $.addr is rw;    # Autogenerates both getter and setter methods

You can also indicate explicitly that you only want a getter:

    has $.name is ro;    # Autogenerates only getter method

Get/set vs unified vs lvalue accessors

The accessor generator can build different styles of accessors (just as Object::Insideout can).

By default, accessors are generated in the "STD" style:

    has $.name is ro;    # print $obj->get_name();
    has $.addr is rw;    # print $obj->get_addr(); $obj->set_addr($new_addr);

However, if the module is loaded with a named "accessor" argument, all subsequent attribute definitions in the current lexical scope are generated with the specified style.

For example, to request a single getter/setter accessor:

    use Dios {accessors => 'unified'};

    has $.name is ro;    # print $obj->name();
    has $.addr is rw;    # print $obj->addr(); $obj->addr($new_addr);

or to request a single lvalue accessor:

    use Dios {accessors => 'lvalue'};

    has $.name is ro;    # print $obj->name();
    has $.addr is rw;    # print $obj->addr(); $obj->addr = $new_addr;

If you want to be explicit about using "STD" style accessors, you can also write:

    use Dios {accessors => 'standard'};

Required attributes

Attributes are initialized using the value of the corresponding named argument passed to their object's constructor (just as in Object::Insideout).

Normally, this initialization is optional: there is no necessity to provide a named initializer argument for an attribute, and no warning or error if none is provided.

If you want to require that the appropriate named initializer value must be present, add is req or is required after the attribute name:

    has $.name is req;   # Must provide a 'name' argument to ctor
    has $.addr;          # May provide an 'addr' argument, but not necessary

If an initializer value isn't provided for a named argument, the class's constructor will throw an exception.

Customizing attributes with Object::InsideOut modifiers

Because Dios uses Object::InsideOut to implement its class behaviours, you can also specify any valid Object::InsideOut modifier as part of an attribute definition. For example:

    has Int   $.ID          :SequenceFrom(1);
    has Vtor  $.validator   :Handles(validate);
    has Ref   $.parent      :Weak;

These must be specified after any is modifier> and before any explicit initializer.

Initializing attributes

Attributes are usually initialized from the arguments passed to their object's constructor, but you can also provide a default initialization to be used if no initial value is passed, by specifying a trailing //= assignment:

    has $.addr //= '<No known address>';

The expression assigned can be as complex as you wish, and can also refer directly to the object being initialized as $self:

    state $AUTOCHECK;

    has $.addr //= $AUTOCHECK ? $self->check_addr() : '<No known address>';

Note, however that other attributes cannot be directly referred to in an initialization (as they are not guaranteed to have been defined within the object at that point).

Declaring class attributes

Attributes declared with a has are per-object. That is, every object has its own version of the attribute variable, distinct from every other object's version of that attribute.

However, it is also possible to declare one or more "class attributes", which are shared by every object of the class. This is done by declaring the attribute with the keyword shared instead of has:

    class Account {

        shared $.status;   # All account objects share this $status variable

        has $.name;        # Each account object has its own $name variable

    }

Shared attributes have the following declaration syntax:

    shared  [<TYPE>]  [$@%]  [!.]  <NAME>  [is [rw|ro]]  [= <EXPR>] ;
             ......    ...   ....  ......   ..........    ........
                :       :      :      :          :            :
    Type [opt]..:       :      :      :          :            :
    Scalar sigil........:      :      :          :            :
    Public/private.............:      :          :            :
    Attribute name....................:          :            :
    Readonly/read-write [opt]....................:            :
    Initialization [opt]......................................:

That is, they can have most of the same behaviours as per-object has attributes, except that they are never initialized from the constructor arguments, so they can't be marked is required, and any initialization must be via simple assignment (=), not default assignment (//=).

Unlike has attributes, shared attributes can be declared as scalars, arrays, or hashes. For example:

    class Account {

        shared %is_active; # Track active objects...

        submethod BUILD    { $is_active{$self} = 1;    }
        submethod DESTROY  { delete $is_active{$self}; }
    }

Declaring methods and subroutines

Dios provides two keywords, method and func, with which you can declare methods and functions. Methods can only be declared inside a Dios class definition, but functions can be declared in any scope.

A second difference is that methods automatically have their invocant unpacked, either implicitly into $self, or explicitly into a defined invocant parameter.

A third difference is that every method in Dios gets direct private access to its attribute variables. That is: you can refer to an attribute from within a method simply by using its name without the . or ! (see the use of direct lookups on %is_active in the Account class example at the end of the previous section).

Both methods and functions may be declared with a parameter list, as described in the subsequent subsections. If no parameter list is specified, it is treated as an empty parameter list (i.e. as declaring that the method or subroutine takes no arguments).

Parameter list syntax

A function parameter list consists of zero or more comma-separated parameter specifications in parentheses:

    func NAME ( PARAM, PARAM, PARAM, ... ) { BODY }

A method parameter list consists of an optional invocant specification, followed by the same zero or more parameter specifications:

    method NAME ( INVOCANT: PARAM, PARAM, PARAM, ... ) { BODY }
    method NAME (           PARAM, PARAM, PARAM, ... ) { BODY }

As a special case, both methods and functions can be specified with a single ( *@_ ) parameter (note: not ( @_ )), in which case methods still unpack their invocant, but otherwise no parameter processing is performed and the arguments remain in @_.

Invocant parameters

By default, methods have their invocant object unpacked into a parameter named $self. If you prefer some other name, you can specify the invocant parameter explicitly, followed by a colon:

    method ($invocant: $other, $args, $here) {...}
    method (    $this: $other, $args, $here) {...}
    method (      $me: $other, $args, $here) {...}

Note that the colon is essential:

    method ($this: $that) {...}  # Invocant is $this, plus one arg

    method ($this, $that) {...}  # Invocant is $self, plus two args

Like all other kinds of parameters, explicit invocants can be specified with any type supported by Dios::Types. Generally this makes little sense unless that type is the name of the current class, or one of its base classes, in which case it is merely redundant.

However, the mechanism does have one important use: to specify a class-only or object-only method:

    # A method callable only on the class itself
    method list_active (Class $self:) {...}

    # A method callable only on instances of the class
    method make_active (Obj $self:) {...}

Positional parameters

A positional parameter specifies that there must be a corresponding single argument in the argument list, which is then assigned to the parameter variable.

Positional parameters may be specified as scalars:

    func add_soldier ($name, $rank, $serial_num) {...}

in which case the corresponding argument may be any scalar value:

    add_soldier('George', 'General', 123456);

Positional parameters may also be specified as arrays or hashes, in which case the corresponding argument must be a reference of the same kind. The contents of the referenced container are (shallow) copied into the array or hash parameter variable.

For example:

    func show_targets (%hash, @targets) {
        for my $target (@targets) {
            for my $key (keys %hash) {
                say "$key: $hash{$key}" if $key ~~ $target;
            }
        }
    }

could be called like so:

    show_targets( \%records, [qr/mad/, 'bad', \&dangerous] );

Positional parameters are required by default, so passing the wrong number of positional arguments (either too few or too many) normally produces a run-time exception. See "Optional and required parameters" to change that behaviour.

If the parameters are specified with types, the values must be compatible as well. You can mix typed and untyped parameters in the same specification:

    func dump_to (IO $fh, $msg, Obj %data, Bool $sort) {
        say {$fh} $msg;
        for my $key ($sort ? sort keys %data : keys %data) {
            say {$fh} "$key => $data{$key}";
        }
    }

As in Dios::Types, a type applied to an array or a hash applies to the individual values stored in that container. So, in the previous example, every value in %data must be an object.

Named parameters

You can also specify parameters that locate their corresponding arguments by name, rather than by position...by prefixing the parameter variable with a colon, like so:

    func add_soldier (:$name, :$rank, :$serial_num) {...}

In this version, the corresponding arguments must be labelled with the names of the parameters, but may be passed in any order:

    add_soldier(serial_num => 123456, name => 'George', rank => 'General');

Each label tells the method or subroutine which parameter the following argument should be assigned to.

You can specify both positional and named parameters in the same signature:

    func add_soldier ($serial_num, :$name, :$rank) {...}

and in any order:

    func add_soldier (:$name, $serial_num, :$rank) {...}
    func add_soldier (:$rank, :$name, $serial_num) {...}

but the positional arguments must be passed to the call first:

    add_soldier(123456, rank => 'General', name => 'George');

although the named arguments can still be passed in any order after the final positional.

Named parameters can also have types specified, if you wish, in which case the type comes before the colon:

    func add_soldier ($serial_num, Str :$name, Str :$rank) {...}

You can also specify a named parameter whose label is different from its variable name. This is achieved by specifying the label immediately after the colon (with no sigil), and then the variable (with its sigil) inside a pair of parentheses immediately thereafter:

    func add_soldier (:$name, :designation($rank), :ID($serial_num)) {...}

This mechanism allows you to use labels that make sense in the call, but variable names that make sense in the body. For example, now the function would be called like so:

    add_soldier(ID => 123456, designation => 'General', name => 'George');

Named parameters can be of any variable type (scalar, array, or hash). As with positional parameters, non-scalar parameters expect a reference of the appropriate kind, whose contents they copy. For example:

    func show_targets (:@targets, :from(%hash),) {
        for my $target (@targets) {
            for my $key (keys %hash) {
                say "$key: $hash{$key}" if $key ~~ $target;
            }
        }
    }

which would then be called like so:

    show_targets( from => \%records, targets => [qr/mad/, 'bad', \&dangerous] );

Note that, unlike positional parameters, named parameters are optional by default (but see "Optional and required parameters" to change that).

Slurpy parameters

Both named and positional parameters are intrinsically "one-to-one": for every parameter, the method or subroutine expects one argument. Even array or hash parameters expect exactly one reference.

But often you need to be able to create methods or functions that take an arbitrary number of arguments. So Dios allows you to specify one extra parameter that is specially marked as being "slurpy", and which therefore collects and stores all remaining arguments in the argument list.

To specify a slurpy parameter, you prefix an array parameter with an asterisk (*), like so:

    func dump_all (*@values) {
        for my $value (@values) {
            dump_value($value);
        }
    }

    # and later...

    dump_all(1, 'two', [3..4], 'etc');

Alternatively, you can specify the slurpy parameter as a hash, in which case it the list of arguments is assigned to the hash (and should therefore be a sequence of key/value pairs). For example:

    func dump_all (*%values) {
        for my $key (%values) {
            dump_value($values{$key});
        }
    }

...which would be called like so:

    dump_all(seq=>1, name=>'two', range=>[3..4], etc=>'etc');

and would collect all four labelled arguments as key/value pairs in %value.

Either type of slurpy parameter can be specified along with other parameter types. For example:

    func dump_all ($msg, :$sorted, *@values) {
        say $msg;
        for my $value ($sorted ? sort @values : @values) {
            dump_value($value);
        }
    }

When called, the positional arguments are assigned to the positional parameters first, then any labeled arguments are assigned to the corresponding named parameters, and finally anything left in the argument list is given to the slurpy parameter:

    dump_all('Look at these', sorted=>1,  1, 'two', [3..4], 'etc');
    #         \___________/           V    \___________________/
    #             $msg             $sorted        @values

Slurpy parameters can be specified with a type, in which case each value that they accumulate must be consistent with that type. For example, if you're doing a numeric sort, you probably want to ensure that all the values being (optionally) sorted are numbers:

    func dump_all ($msg, :$sorted, Num *@values) {
        say $msg;
        for my $value ($sorted ? sort {$a<=>$b} @values : @values) {
            dump_value($value);
        }
    }

Named slurpy array parameters

Another option for passing labelled arguments to a subroutine is the named slurpy array parameter.

Unlike a named parameter (which collects just a single labelled value from the argument list), or a slurpy hash parameter (which collects every labelled value from the argument list), a named slurpy array parameter collects every value with a given label from the argument list.

Also unlike a regular slurpy parameter, you may specify two or more named slurpy parameters (as well as one regular slurpy, if you wish).

This allows you to pass multiple separate labelled values and have them collected by name:

    func process_caprinae ( *:@sheep, *:goat(@goats) ) {
        shear(@sheep);
         milk(@goats);
    }

Such a function might be called like this:

    process_caprinae(
        sheep => 'shawn',
         goat => 'billy',
        sheep => 'sarah',
        sheep => 'simon',
         goat => 'nanny',
    );

In other words, you can use named slurpy arrays to partition a sequence of labelled arguments into two or more coherent sets.

Named slurpy array parameters may be given a type, in which case every labelled argument value appended to the parameter array must be compatible with the specified type.

Note that named slurpy parameters can only be declared as arrays, since neither hashes nor scalars make much sense in that context.

Constrained parameters

In addition to specifying any Dios::Types-supported type for any kind of parameter, you can also specify a constraint on the parameter, by adding a where block. For example:

    func save (
        $dataset   where { length > 0 },
        $filename  where { /\.\w{3}$/ },
       :$checksum  where { $checksum->valid },
       *@infolist  where { @infolist <= 100 },
    ) {...}

A where block adds a constraint check to the validation of the variable's type, even if the type is unspecified (i.e. it's the default Any).

The block is treated exactly like the constraint argument to Dios::Types::validate() (see Dios::Types for details).

So in the previous example, any call to save requires that:

  • The value passed to the positional $dataset parameter must be (convertible to) a non-empty string,

  • The value passed to the positional filename parameter must be (convertible to) a string that ends in a dot and three characters,

  • The object passed to the named $checksum parameter must return true when its valid() method is invoked, and

  • The number of trailing arguments collected by the slurpy @infolist parameter must no more than 100.

As the previous example indicates, where blocks can refer to the parameter variable they are checking either by its name or as $_. They can also refer to any other parameter declared before it in the parameter list. For example:

    func set_range (Num $min, Num $max where {$min <= $max}) {...}

Optional and required parameters

By default, all positional parameters declared in a parameter list are "required". That is, an argument must be passed for each declared positional parameter.

All other kinds of parameter (named, or slurpy, or named slurpy) are optional by default. That is, an argument may be passed for them, but the call will still proceed if one isn't.

You may also specify optional positional parameters, by declaring them with a ? immediately after the variable name. For example:

    func add_soldier ($serial_num, $name, $rank?, $unit?) {...}

Now the function can take either two, three, or four arguments, with the first two always being assigned to $serial_num and $name. If a third argument is passed, it is assigned to $rank. If a fourth argument is given, it's assigned to $unit.

You can also specify any other kind of (usually optional) parameter as being required, by appending a ! to its variable name. For example:

    func dump_all ($msg, :$sorted!, *@values!) {...}

Now, in addition to the positional $msg parameter being required, a labelled argument must also be provided for the named $sorted parameter, and there must also be at least one argument for the slurpy @values parameter to be assigned as well.

The ? and ! modifiers can be applied to any parameter, even if the modifier doesn't change the parameter's usual "required-ness". For example:

    func add_soldier ($serial_num!, $name!, :$rank?, :$unit?) {...}

Typed and constrained optional parameters

If no argument is passed for an optional parameter, then the parameter will retain its uninitialized value (i.e. undef for scalars, empty for arrays and hashes).

If the parameter has a type or a where constraint, then that type or constraint is still applied to the parameter, and may not be satisfied by the uninitialized value. For example:

    func dump_data(
        Int  $offset?,
        Str :$msg,
        Any *@data where { @data > 2 }
    ) {...}

    # and later...

    dump_data();
    # Error: Value (undef) for positional parameter $offset is not of type Int

    dump_data(1..10);
    # Error: Value (undef) for named parameter :$msg is not of type Str

    dump_data(-1, msg=>'results:');
    # Error: Value ([]) for slurpy parameter @data
    #        did not satisfy the constraint: { @data > 2 }

The solution is either to ensure the type or constraint can accept the uninitialized value as well:

    func dump_data(
        Int|Undef  $offset,
        Str|Undef :$msg,
        Any       *@data where { !@data || @data > 2 }
    ) {...}

or else to give the optional parameter a type-compatible default value.

Optional parameters with default values

You can specify a value that an optional parameter should be initialized to, if no argument is passed for it. Or if the argument passed for it is undefined. Or false.

To provide a default value if an argument is missing (i.e. not passed in at all), append an = followed by an expression that generates the desired default value. For example:

    func dump_data(
        Int $offset                  = 0,
        Str :$msg                    = get_std_msg_for($offset),
        Any *@data where {@data > 0} = ('no', $data)
    ) {...}

Note that this solves the type-checking problem for optional parameters that was described in the previous section, but only if the default values themselves are type-compatible.

Care must be taken when specifying both optional positional and named parameters. If dump_data() had been called like so:

    dump_data( msg=>'no results' );

then the positional parameter would attempt to bind to the first argument (i.e. the label string 'msg'), which would cause the entire call to fail because that value isn't an Int.

Even worse, if the positional parameter hadn't been typed, then the 'msg' label would successfully be assigned to it, so there would be no labelled argument to bind to the named parameter, and the left-over 'no results' string would be slurped up by @data.

The expression generating the default value must be final component of the parameter specification, and may be any expression that is valid at that point in the code. As the previous example illustrates, the default expression may refer to parameters declared earlier in the parameter list.

The usual Perl precedence rules apply to the default expression. That's why, in the previous example, the default values for the slurpy @data parameter are specified in parentheses. If they had been specified without the parens:

        Any *@data where {@data > 0} = 'no', $data

then Dios would interpret the , $data as a fourth parameter declaration.

A default specified with a leading = is applied only when no corresponding argument appears in the argument list, but you can also specify a default that is applied when there is an argument but it's undef, by using //= instead of =. For example:

    func dump_data(
        Int $offset                  //= 0,
        Str :$msg                    //= get_std_msg_for($offset),
        Any *@data where {@data > 0}   = ('no', $data)
    ) {...}

With the earlier versions of dump_data(), a call like:

    dump_data(undef);

would have failed...because although we are passing a value for the positional $offset parameter, that value isn't accepted by the parameter's type.

But with the $offset parameter's default now specified via a //=, the default is applied either when the argument is missing, or when it's provided but is undefined.

Similarly, you can specify a default that is applied when the corresponding argument is false, using ||= instead of //= or =. For example:

    func save_data(@data, :$verified ||= reverify(@data)) {...}

Now, if the labelled argument for $verify is not passed, or if it is passed, but is false, the reverify() function is automatically called. Alternatively, you could use the same mechanism to immediately short-circuit the call if unverified data is passed in:

    func save_data(@data, :$verified ||= return 'failed') {...}

Defaulting to $_

The parameter default mechanism also allows you to define functions or methods whose argument defaults to $_ (like many of Perl's own builtins).

For example, you might wish to create an function analogous to lc() and uc(), but which randomly uppercases and lowercases its argument (a.k.a. "HoSTagE-cAsE")

    func hc ($str = $_) {
        join  "",
        map   { rand > 0.5 ? uc : lc }
        split //, $str;
    }

    # and later...

    # Pass an explicit string to be hostage-cased
    say hc('Send $1M in small, non-sequential bills!');

    # Hostage-case each successive value in $_
    say hc for @instructions;

Aliased parameters

All the kinds of parameters discussed so far bind to an argument by copying it. That's a safe default, but occasionally you want to pass in variables as arguments, and be able to change them within a function or method.

So Dios allows parameters to be specified with aliasing semantics instead of copy semantics...by adding an is alias modifier to their declaration.

For example:

    func double_chomp ($str is alias = $_) {
        $str =~ s{^\s+}{};
        $str =~ s{\s+$}{};
    }

    func remove_targets (%hash is alias, *@targets) {
        for my $target (@targets) {
            for my $key (keys %hash) {
                delete $hash{$key} if $key ~~ $target;
            }
        }
    }

which would then be called like so:

    # Modify $input
    double_chomp($input);

    # Modify %records
    remove_targets( \%records, qr/mad/, 'bad', \&dangerous );

You can also specify that a named parameter or a slurpy parameter or a named slurpy parameter should alias its corresponding argument(s).

Note that, under Perl versions earlier than 5.022, aliased parameters require the Data::Alias module.

Read-only parameters

You can also specify that a parameter should be readonly within the body of the subroutine or method, by appending is ro to its definition.

For example:

    func link (:$from is ro, :$to is alias) {...}

In this example, the $from parameter cannot be modified within the subroutine body, whereas modifications to the $to parameter are allowed and will propagate back to the argument to which it was bound.

Currently, a parameter cannot be specified as both is ro and is alias. In the future, is ro may actually imply is alias, if that proves to be a performance optimization.

Note the differences between:

is ro

The parameter is a read-only copy

is alias

The parameter is a read-write original

Neither modifier

The parameter is a read-write copy

Note that readonly parameters under all versions of Perl currently require the Const::Fast module.

Declaring submethods

A submethod is a Perl 6 construct: a method that is not inherited, and hence may be called only on objects of the actual class in which it is defined.

Dios provides a submethod keyword to declare such methods. For example:

    class Account {
        method trace_to (IO $fh) {
            carp "Can't trace a ", ref($self), " object";
        }
    }

    class Account::Traceable is Account {
        submethod trace_to (IO $fh) {
            print {$fh} $self->dump();
        }
    }

Now any objects in a class in the Account hierarchy will complain if its trace_to() method is called, except objects in class Account::Traceable, where the submethod will be called instead of the inherited method.

Most unusually, if the same method is called on an object of any class that derives from Account::Traceable, the submethod will not be invoked; the base class's method will be invoked instead.

Submethods are most commonly used to specify initializers and destructors in Perl 6...and likewise under Dios in Perl 5.

Declaring an initializer submethod

To specify the equivalent of an Object::Insideout :Init method in Dios, create a submethod with the special name BUILD and zero or more named parameters. Like so:

    class Account {

        has $.acct_name;
        has $.balance;

        submethod BUILD (:$name, :$opening_balance) {
            $acct_name = verify($name);
            $balance   = $opening_balance + $::opening_bonus;
        }
    }

When the class constructor is called, and passed a hashref with labelled arguments, any arguments matching the named parameters of BUILD are passed to that submethod.

When an object of a derived class is constructed, the BUILD methods of all its ancestral classes are called in top-down order, and can use their respective named parameters to extract relevant constructor arguments for their class.

Declaring a destructor submethod

You can create the equivalent of on Object::InsideOut :Destroy method by creating a submethod with the special name DESTROY. Note that this method is name-mangled internally, so it does not clash with the DESTROY() method implicitly provided by Object::InsideOut.

A DESTROY() submethod takes no arguments (except $self) and it is a compile-time error to specify any.

When an object of a derived class is garbage-collected, the DESTROY methods of all its ancestral classes are called in bottom-up order, and can be used to free resources or do other cleanup that the garbage collector cannot manage automatically. For example:

    class Tracked::Agent {
        shared %.agents is ro;

        submethod BUILD (:$ID) {
            $agents{$self} = $ID;
        }

        submethod DESTROY () {
            delete $agents{$self};  # Clean up a resource that the
                                    # garbage collector can't reach.
        }
    }

Anonymous subroutines and methods

Due to limitations in the behaviour of the Keyword::Simple module (which Dios uses to implement its various keywords), it is not currently possible to use the func or method keywords directly to generate an anonymous function or method:

    my $criterion = func ($n) { 1 <= $n && $n <= 100 };
    # Compilation aborted: 'syntax error, near "= func"'

However, it is possible to work around this limitation, by placing the anonymous declaration in a do block:

    my $criterion = do{ func ($n) { 1 <= $n && $n <= 100 } };
    # Now compiles and executes as expected

DIAGNOSTICS

Invalid invocant specification: %s in 'use Dios' statement

Methods may be given invocants of a name other than $self. However, the alternative name you specified couldn't be used because it wasn't a valid identifier.

Respecify the invocant name as a simple identfier (one or more letters, numbers, and underscores only, but not starting with a number).

Can't specify invocant (%s) for %s

Explicit invocant parameters can only be declared for methods and submethods. You attempted to declare it for something else (probably a subroutine).

Did you mean it to be a regular parameter instead? In that case, put a comma after it, not a colon.

Can't declare two parameters named %s in specification of %s

Each parameter is a lexical variable in the subroutine, so each must have a unique name. You attempted to declare two parameters of the same name.

Did you misspell one of them?

Can't specify more than one slurpy parameter

Slurpy parameters (by definition) suck up all the remaining arguments in the parameter list. So the second one you declared will never have any argument bound to it.

Did you want a non-slurpy array or hash instead (i.e. without the *)?

Can't specify non-array named slurpy parameter (%s)

Slurpy parameters may be named (in which case they collect all the named arguments of the same name). However, they always collect them as a list, and so the corresponding parameter must be declared as an array.

Convert the named slurpy hash or scalar you declared to an array, or else declare the hash or scalar as non-slurpy (by removing the *).

Invalid parameter specification: %s in %s declaration

You specified something in a parameter list that Dios did not understand.

Review the parameter syntax to see the permitted parameter constructs.

'is ro' requires the Const::Fast module (which could not be loaded)

Dios uses the Const::Fast module to ensure "read-only" parameters cannot be modified. You specified a "read-only" parameter, but Dios couldn't find or load Const::Fast.

Did you need to install Const::Fast? Otherwise, remove the is ro from the parameter definition.

'is alias' requires the Data::Alias module (which could not be loaded)

Under Perl versions prior to 5.22, Dios uses the Data::Alias module to ensure "alias-only" parameters are aliased to their arguments. You specified a "aliased" parameter, but Dios couldn't find or load Data::Alias.

Did you need to install Data::Alias? Or migrate to Perl 5.22? Otherwise, remove the is alias from the parameter definition and pass the corresponding argument by reference.

submethod DESTROY cannot have a parameter list

You declared a destructor submethod with a parameter list, but destructors aren't called with any arguments.

%s takes no arguments

The method or subroutine you called was declared to take no arguments, but you passed some.

If you want to allow extra arguments, either declare them specifically, or else declare a slurpy array or hash as a catch-all.

Unexpected extra argument(s) in call to %s

Dios does not allow subroutines or methods to be called with additional arguments that cannot be bound to one of their parameters. In this case it encountered extra arguments at the end of the argument list for which there were no suitable parameter mappings.

Did you need to declare a slurpy parameter at the end of the parameter list? Otherwise, make sure you only pass as many arguments as the subroutine or method is defined to take.

No argument (%s => %s) found for required named parameter %s

You called a subroutine or method which was specified with a named argument that was marked as being required, but you did not pass a name=>value pair for it in the argument list.

Either pass the named argument, or remove the original required status (by removing the trailing ! from the named parameter).

No argument found for %s in call to %s

You called a subroutine or method which was specified with a positional parameter that was marked as being required, but you did not pass a value for it in the argument list.

Either pass the positional argument, or remove the original required status (by adding a trailing ? to the positional parameter).

Missing argument for required slurpy parameter %s

You called a subroutine or method which was specified with a slurpy parameter that was marked as being required, but you did not pass a value for it in the argument list.

Either pass the argument, or remove the original required status (by removing the trailing ! on the slurpy parameter).

Argument for %s is not array ref in call to %s

You called a subroutine or method that specifies a pass-by-reference array parameter, but didn't pass it an array reference.

Either pass an array reference, or respecify the array parameter as a slurpy array.

Argument for %s is not hash ref in call to %s

You called a subroutine or method that specifies a pass-by-reference hash parameter, but didn't pass it a hash reference.

Either pass a hash reference, or respecify the hash parameter as a slurpy hash.

Unexpected second value (%s) for named %s parameter in call to %s

Named parameters can only be bound once, to a single value. You passed two or more named arguments with the same name, but only the first could ever be bound.

Did you misspell the name of the second named argument? Otherwise, respecify the named parameter as a slurpy named parameter.

Dios uses the Dios::Types module for its type-checking, so it may also generate any of that module's diagnostics.

CONFIGURATION AND ENVIRONMENT

Dios requires no configuration files or environment variables.

DEPENDENCIES

Requires Perl 5.14 or later.

Requires the Keyword::Declare, Object::InsideOut, and Data::Dump modules.

If the 'is ro' qualifier is used, also requires the Const::Fast module.

If the 'is alias' qualifier is used under Perl 5.20 or earlier, also requires the Data::Alias module.

INCOMPATIBILITIES

None reported.

BUGS AND LIMITATIONS

This module relies on Keyword::Declare to create its new keywords. That module currently imposes a non-trivial start-up delay on any module that uses it...including Dios.

Due to limitations in the pluggable keyword mechanism, installing a method keyword currently breaks the :method attribute.

Shared array or hash attributes that are public cannot be accessed correctly if the chosen accessor style is 'lvalue', because lvalue subroutines in Perl can only return scalars.

It should be possible to declare per-object attributes as arrays and hashes. This is planned for a future release. At present, declare them as scalars and store an array or hash reference instead.

No bugs have been reported.

Please report any bugs or feature requests to bug-dios@rt.cpan.org, or through the web interface at http://rt.cpan.org.

AUTHOR

Damian Conway <DCONWAY@CPAN.org>

LICENCE AND COPYRIGHT

Copyright (c) 2015, Damian Conway <DCONWAY@CPAN.org>. All rights reserved.

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

DISCLAIMER OF WARRANTY

BECAUSE THIS SOFTWARE IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE SOFTWARE, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE SOFTWARE "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE SOFTWARE IS WITH YOU. SHOULD THE SOFTWARE PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR, OR CORRECTION.

IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE SOFTWARE AS PERMITTED BY THE ABOVE LICENCE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE SOFTWARE (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE SOFTWARE TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.