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

NAME

Object::Pad - a simple syntax for lexical slot-based objects

SYNOPSIS

   use Object::Pad;

   class Point {
      has $x = 0;
      has $y = 0;

      method BUILD {
        ($x, $y) = @_;
      }

      method move($dX, $dY) {
         $x += $dX;
         $y += $dY;
      }

      method describe {
         print "A point at ($x, $y)\n";
      }
   }

   Point->new(5,10)->describe;

DESCRIPTION

WARNING This is an experimental proof-of-concept. Please don't actually use this in production unless you are crazy :)

This module provides a simple syntax for creating object classes, which uses private variables that look like lexicals as object member fields.

Automatic Construction

Classes are automatically provided with a constructor method, called new, which helps create the object instances.

By default, this constructor will invoke the BUILD method of every component class, passing the list of arguments the constructor was invoked with. Each class should perform its required setup behaviour in a method called BUILD, but does not need to chain to the SUPER class first; this is handled automatically.

   $class->BUILD( @_ )

If the class provides a BUILDALL method, that is used to implement the setup behaviour instead. It is passed the entire parameters list from the new method. It should perform any SUPER chaining as may be required.

   $class->BUILDALL( @_ )

KEYWORDS

class

   class Name {
      ...
   }

   class Name;

Behaves similarly to the package keyword, but provides a package that defines a new class. Such a class provides an automatic constructor method called new.

As with package, an optional block may be provided. If so, the contents of that block define the new class and the preceding package continues afterwards. If not, it sets the class as the package context of following keywords and definitions.

As with package, an optional version declaration may be given. If so, this sets the value of the package's $VERSION variable.

   class Name VERSION { ... }

   class Name VERSION;

A single superclass is supported by the keyword extends

   class Name extends BASECLASS {
      ...
   }

   class Name extends BASECLASS VERSION {
      ...
   }

If a package providing the superclass does not exist, an attempt is made to load it by code equivalent to

   require Animal ();

and thus it must either already exist, or be locatable via the usual @INC mechanisms.

The superclass must either be implemented by Object::Pad, or be some class whose instances are blessed hash references.

In the latter case, all Object::Pad-based subclasses derived from it will store their instance data in a key called "Object::Pad/slots", which is fairly unlikely to clash with existing storage on the instance. The exact format of the value stored here is not specified and may change between module versions, though it can be relied on to be well-behaved as some kind of perl data structure for purposes of modules like Data::Dumper or serialisation into things like YAML or JSON.

An optional version check can also be supplied; it performs the equivalent of

   BaseClass->VERSION( $ver )

has

   has $var;
   has $var = CONST;
   has @var;
   has %var;

Declares that the instances of the class have a member field of the given name. This member field (called a "slot") will be accessible as a lexical variable within any method declarations in the class.

Array and hash members are permitted and behave as expected; you do not need to store references to anonymous arrays or hashes.

Member fields are private to a class. They are not visible to users of the class, nor to subclasses. In order to provide access to them a class may wish to use "method" to create an accessor.

A scalar slot may provide a expression that gives an initialisation value, which will be assigned into the slot of every instance during the constructor before BUILDALL is invoked. For ease-of-implementation reasons this expression must currently be a compiletime constant, but it is hoped that a future version will relax this restriction and allow runtime-computed values.

method

   method NAME {
      ...
   }

   method NAME (SIGNATURE) {
      ...
   }

   method NAME :attrs... {
      ...
   }

Declares a new named method. This behaves similarly to the sub keyword, except that within the body of the method all of the member fields ("slots") are also accessible. In addition, the method body will have a lexical called $self which contains the invocant object directly; it will already have been shifted from the @_ array.

The signatures feature is automatically enabled for method declarations. In this case the signature does not have to account for the invocant instance; that is handled directly.

   method m($one, $two) {
      say "$self invokes method on one=$one two=$two";
   }

   ...
   $obj->m(1, 2);

A list of attributes may be supplied as for sub. The most useful of these is :lvalue, allowing easy creation of read-write accessors for slots.

   class Counter {
      has $count;

      method count :lvalue { $count }
   }

   my $c = Counter->new;
   $c->count++;

IMPLIED PRAGMATA

In order to encourage users to write clean, modern code, the body of the class block acts as if the following pragmata are in effect:

   use strict;
   use warnings;
   no indirect ':fatal';
   use feature 'signatures';

This list may be extended in subsequent versions to add further restrictions and should not be considered exhaustive.

Further additions will only be ones that remove "discouraged" or deprecated language features with the overall goal of enforcing a more clean modern style within the body. As long as you write code that is in a clean, modern style (and I fully accept that this wording is vague and subjective) you should not find any new restrictions to be majorly problematic. Either the code will continue to run unaffected, or you may have to make some small alterations to bring it into a conforming style.

WITH OTHER MODULES

Syntax::Keyword::Dynamically

A cross-module integration test asserts that dynamically works correctly on object instance slots:

   use Object::Pad;
   use Syntax::Keyword::Dynamically;

   class Container {
      has $value = 1;

      method example {
         dynamically $value = 2;
         ,..
         # value is restored to 1 on return from this method
      }
   }

Future::AsyncAwait

As of Future::AsyncAwait version 0.38 and Object::Pad version 0.15, both modules now use XS::Parse::Sublike to parse blocks of code. Because of this the two modules can operate together and allow class methods to be written as async subs which await expressions:

   use Future::AsyncAwait;
   use Object::Pad;

   class Example
   {
      async method perform($block)
      {
         say "$self is performing code";
         await $block->();
         say "code finished";
      }
   }

These three modules combine; there is additionally a cross-module test to ensure that object instance slots can be dynamically set during a suspended async method.

DESIGN TODOs

The following points are details about the design of pad slot-based object systems in general:

  • Is multiple inheritence actually required, if role composition is implemented including giving roles the ability to use private slots?

  • Consider the visibility of superclass slots to subclasses. Do subclasses even need to be able to see their superclass's slots, or are accessor methods always appropriate?

    Concrete example: The $self->{split_at} access that Tickit::Widget::HSplit makes of its parent class Tickit::Widget::LinearSplit.

IMPLEMENTATION TODOs

These points are more about this particular module's implementation:

  • Implement roles, including required method checking and the ability to have private slots.

  • Consider multiple inheritence of subclassing, if that is still considered useful after adding roles.

  • Some extensions of the has syntax:

    Non-constant default expressions

       has $var = EXPR;

    A way to request generated accessors - ro or rw.

  • Work out why no indirect doesn't appear to work properly before perl 5.20.

  • Work out why we don't get a Subroutine new redefined at ... warning if we

      sub new { ... }
  • The local modifier does not work on slot variables, because they appear to be regular lexicals to the parser at that point. A workaround is to use Syntax::Keyword::Dynamically instead:

       use Syntax::Keyword::Dynamically;
    
       has $loglevel;
    
       method quietly {
          dynamically $loglevel = LOG_ERROR;
          ...
       }

AUTHOR

Paul Evans <leonerd@leonerd.org.uk>