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

NAME

Zydeco::Lite - Zydeco without any magic

SYNOPSIS

  use strict;
  use warnings;
  use Zydeco::Lite;
  
  app "Local::MyApp" => sub {
    
    role "Greeting" => sub {
      
      method "greeting" => sub {
        return "Hello";
      };
    };
    
    role generator "Location" => [ "Str" ] => sub {
      my ( $gen, $arg ) = @_;
      
      method "location" => sub {
        return $arg;
      };
    };
    
    class "Hello::World" => sub {
      with "Greeting";
      with "Location" => [ "world" ];
      
      method "do_it" => [] => sub {
        my $self = shift;
        print $self->greeting, " ", $self->location, "\n";
      };
    };
  };
  
  my $obj = "Local::MyApp""->new_hello_world;
  $obj->do_it();

DESCRIPTION

Zydeco::Lite is a Zydeco-like module, but without using any parsing tricks. Zydeco requires Perl 5.14 or above, but Zydeco::Lite will run on any version of Perl since 5.8.8.

It's intended to be a happy medium between Zydeco and MooX::Press.

Syntax Examples

Apps

Apps:

  app "MyApp" => sub {
    # definition
  };

Anonymous apps:

  my $app = app sub {
    # definition
  };

As of Zydeco::Lite 0.69, classes and roles no longer need to be defined within an app block, but bundling them into an app block has the advantage that the app is able to define all its classes and roles together, cross-referencing them, and setting them up in a sensible order. (Which becomes important if you define a role after defining a class that consumes it.)

Classes, Roles, Interfaces, and Abstract Classes

Classes:

  class "MyClass" => sub {
    # definition
  };

Anonymous classes:

  my $class = class sub {
    # definition
  };
  
  my $obj = $class->new();

Class generators:

  class generator "MyGen" => sub {
    my ( $gen, @args ) = ( shift, @_ );
    # definition
  };
  
  my $class = $app->generate_mygen( @args );
  my $obj   = $class->new();

  class generator "MyGen" => [ @signature ] => sub {
    my ( $gen, @args ) = ( shift, @_ );
    # definition
  };

Anonymous class generators:

  my $gen = class generator sub {
    my ( $gen, @args ) = ( shift, @_ );
    # definition
  };
  
  my $class = $gen->generate_package( @args );
  my $obj   = $class->new();

Roles, interfaces, and abstract classes work the same as classes, but use keywords role, interface, and abstract_class.

Inheritance:

  class "Base" => sub { };
  
  class "Derived" => sub {
    extends "Base";
  };

Inheritance using nested classes:

  class "Base" => sub {
    ...;
    
    class "Derived" => sub {
      ...;
    };
  };

Inheriting from a generated class:

  class generator "Base" => sub {
    my ( $gen, @args ) = ( shift, @_ );
    ...;
  };
  
  class "Derived" => sub {
    extends "Base" => [ @args ];
  };

Composition:

  role "Named" => sub {
    requires "name";
  };
  
  class "Thing" => sub {
    with "Named";
    has "name" => ();
  };

Composing an anonymous role:

  class "Thing" => sub {
    with role sub {
      requires "name";
    };
    
    has "name" => ();
  };

Composing a generated role:

  role generator "Thingy" => sub {
    my ( $gen, @args ) = ( shift, @_ );
    ...;
  };
  
  class "Derived" => sub {
    with "Thingy" => [ @args ];
  };

Package Settings

Class version:

  class "Foo" => sub {
    version "1.000";
  };

  class "Foo" => ( version => "1.0" )
              => sub {
    ...;
  };

Class authority:

  class "Foo" => sub {
    authority "cpan:TOBYINK";
  };

  class "Foo" => ( version => "1.0", authority => "cpan:TOBYINK" )
              => sub {
    ...;
  };

Using non-Moo toolkits:

  class "Foo" => sub {
    toolkit "Mouse";
  };

  class "Bat" => sub {
    toolkit "Moose" => ( "StrictConstructor" );
  };

The version, authority, and toolkit keywords can be used within app, class, role, interface, or abstract_class definitions.

Attributes

Attributes:

  has "myattr" => ( ... );
  
  has [ "myattr1", "myattr2" ] => ( ... );

Private attributes:

  has "myattr" => ( is => "private", ..., accessor => \(my $accessor) );

Methods

Methods:

  method "mymeth" => sub {
    my ( $self, @args ) = ( shift, @_ );
    ...;
  };

Methods with positional signatures:

  method "mymeth" => [ 'Num', 'Str' ]
                  => sub
  {
    my ( $self, $age, $name ) = ( shift, @_ );
    ...;
  };

Methods with named signatures:

  method "mymeth" => [ age => 'Num', name => 'Str' ]
                  => ( named => 1 )
                  => sub
  {
    my ( $self, $args ) = ( shift, @_ );
    ...;
  };

Anonymous methods:

  my $mymeth = method sub {
    my ( $self, @args ) = ( shift, @_ );
    ...;
  }

  method \(my $mymeth) => sub {
    my ( $self, @args ) = ( shift, @_ );
    ...;
  }

Anonymous methods may have signatures.

Required methods in roles:

  requires "method1", "method2";
  requires "method3";

Method modifiers:

  before "somemethod" => sub {
    my ( $self, @args ) = ( shift, @_ );
    ...;
  };

  after [ "method1", "method2"] => sub {
    my ( $self, @args ) = ( shift, @_ );
    ...;
  };

  around "another" => sub {
    my ( $next, $self, @args ) = ( shift, shift, @_ );
    ...;
    $self->$next( @_ );
    ...;
  };

Constants:

  constant "ANSWER_TO_LIFE" => 42;

Overloading:

  method "to_string" => sub {
    my $self = shift;
    ...;
  };
  
  overload(
    q[""]    => "to_string",
    fallback => 1,
  );

Factory methods:

  factory "new_foo" => \"new";

  factory "new_foo" => sub {
    my ( $factory, $class, @args ) = ( shift, shift, @_ );
    return $class->new( @args );
  };

Factory methods may include signatures like methods.

Indicate you want a class to have no factories:

  factory();

The keywords multi_method and multi_factory exist for multimethods.

Types

Setting the type name for a class or role:

  class "Foo::Bar" => sub {
    type_name "Foobar";
     ...;
  };

Coercion:

  class "Foo::Bar" => sub {
    method "from_arrayref" => sub {
      my ( $class, $aref ) = ( shift, @_ );
      ...;
    };
    coerce "ArrayRef" => "from_arrayref";
  };

  class "Foo::Bar" => sub {
    coerce "ArrayRef" => "from_arrayref" => sub {
      my ( $class, $aref ) = @_;
      ...;
    };
  };

Hooks

Hooks for classes:

  begin {
    my ( $class ) = ( shift );
    # Code that runs early during class definition
  };

  end {
    my ( $class ) = ( shift );
    # Code that runs late during class definition
  };

Hooks for roles:

  begin {
    my ( $role ) = ( shift );
    # Code that runs early during role definition
  };

  end {
    my ( $role ) = ( shift );
    # Code that runs late during role definition
  };

  before_apply {
    my ( $role, $target, $targetkind ) = ( shift, @_ );
    # Code that runs before a role is applied to a package
  };

  after_apply {
    my ( $role, $target, $targetkind ) = ( shift, @_ );
    # Code that runs after a role is applied to a package
  };

Utilities

Booleans:

  my $truth = true;
  my $truth = false;

Exceptions:

  confess( 'Something bad happened' );
  confess( 'Exceeded maximum (%d)', $max );

Formal Syntax

Scope ANY means the keyword can appear anywhere where Zydeco::Lite is in scope. Scope CLASS means that the keyword may appear only within class or abstract class definition blocks. Scope ROLE means that the keyword may appear only in role/interface definition blocks. Scope APP means that the keyword may appear only within an app definition block.

 # Scope: ANY
 app(
   Optional[Str]      $name,
   Hash               %args,
   Optional[CodeRef]  $definition,
 );
 
 # Scope: ANY
 class(
   Optional[Str]      $name,
   Hash               %args,
   Optional[CodeRef]  $definition,
 );
 
 # Scope: ANY
 class generator(
   Optional[Str]      $name,
   Optional[ArrayRef] $signature,
   Hash               %args,
   Optional[CodeRef]  $definition,
 );
 
 # Scope: ANY
 role(
   Optional[Str]      $name,
   Hash               %args,
   Optional[CodeRef]  $definition,
 );
 
 # Scope: ANY
 role generator(
   Optional[Str]      $name,
   Optional[ArrayRef] $signature,
   Hash               %args,
   Optional[CodeRef]  $definition,
 );
 
 # Scope: ANY
 interface(
   Optional[Str]      $name,
   Hash               %args,
   Optional[CodeRef]  $definition,
 );
 
 # Scope: ANY
 interface generator(
   Optional[Str]      $name,
   Optional[ArrayRef] $signature,
   Hash               %args,
   Optional[CodeRef]  $definition,
 );
 
 # Scope: ANY
 abstract_class(
   Optional[Str]      $name,
   Hash               %args,
   Optional[CodeRef]  $definition,
 );
 
 # Scope: ANY
 abstract_class generator(
   Optional[Str]      $name,
   Optional[ArrayRef] $signature,
   Hash               %args,
   Optional[CodeRef]  $definition,
 );
 
 # Scope: CLASS
 extends(
   List[Str|ArrayRef] @parents,
 );
 
 # Scope: CLASS or ROLE
 with(
   List[Str|ArrayRef] @parents,
 );
 
 # Scope: ANY
 method(
   Optional[Str]      $name,
   Optional[ArrayRef] $signature,
   Hash               %args,
   CodeRef            $definition,
 );
 
 # Scope: CLASS
 factory(
   Str|ArrayRef       $names,
   Optional[ArrayRef] $signature,
   Hash               %args,
   CodeRef|ScalarRef  $definition_or_via,
 );
 
 # Scope: ANY
 constant(
   Str                $name,
   Any                $value,
 );
 
 # Scope: ANY
 multi_method(
   Str                $name,
   ArrayRef           $signature,
   Hash               %args,
   CodeRef            $definition,
 );
 
 # Scope: CLASS
 multi_factory(
   Str                $name,
   ArrayRef           $signature,
   Hash               %args,
   CodeRef            $definition,
 );
 
 # Scope: ANY
 before(
   Str|ArrayRef       $names,
   Optional[ArrayRef] $signature,
   Hash               %args,
   CodeRef            $definition,
 );
 
 # Scope: ANY
 after(
   Str|ArrayRef       $names,
   Optional[ArrayRef] $signature,
   Hash               %args,
   CodeRef            $definition,
 );
 
 # Scope: ANY
 around(
   Str|ArrayRef       $names,
   Optional[ArrayRef] $signature,
   Hash               %args,
   CodeRef            $definition,
 );
 
 # Scope: CLASS or ROLE
 has(
   Str|ArrayRef       $names,
   Hash               %spec,
 );
 
 # Scope: ROLE
 requires(
   List[Str]          @names,
 );
 
 # Scope: ANY
 confess(
   Str                $template,
   List               @args,
 );
 
 # Scope: APP or CLASS or ROLE
 toolkit(
   Str                $toolkit,
   Optional[List]     @imports,
 );
 
 # Scope: CLASS or ROLE
 coerce(
   Object|Str         $type,
   Str                $via,
   Optional[CodeRef]  $definition,
 );
 
 # Scope: CLASS
 overload(
   Hash               %args,
 );
 
 # Scope: APP or CLASS or ROLE
 version(
   Str                $version,
 );
 
 # Scope: APP or CLASS or ROLE
 authority(
   Str                $authority,
 );
 
 # Scope: CLASS or ROLE
 type_name(
   Str                $name,
 );
 
 # Scope: CLASS or ROLE
 begin {
   ( $package ) = @_;
   ...;
 };
 
 # Scope: CLASS or ROLE
 end {
   ( $package ) = @_;
   ...;
 };
 
 # Scope: ROLE
 before_apply {
   ( $role, $target, $targetkind ) = @_;
   ...;
 };
 
 # Scope: ROLE
 after_apply {
   ( $role, $target, $targetkind ) = @_;
   ...;
 };

Scopes are dynamic rather than lexical. So although extends can only appear in a CLASS, this will work:

 use Zydeco::Lite;
 
 class "Base";
 
 sub foo { extends "Base" }
 
 class "Derived" => sub { foo() };

Keywords used within a before_apply or after_apply block execute in the scope of the package they're being applied to. They run too late for type_name to work, but most other keywords will work okay. In the following example, Derived will be a child class of Base.

 use Zydeco::Lite;
 
 class "Base";
 
 role "ChildOfBase" => sub {
   after_apply {
     my ( $role, $target, $kind ) = @_;
     extends "Base" if $kind eq "class";
   };
 };
 
 class "Derived" => sub {
   with "ChildOfBase";
 };

Import

Zydeco::Lite uses Exporter::Tiny, so you can choose which keywords to import, rename them, etc.

  use Zydeco::Lite { -prefix => 'zy_' };
  
  my $app = zy_app {
    zy_class 'Foo' => sub {};
  };
  
  my $obj = $app->new_foo();

BUGS

Please report any bugs to http://rt.cpan.org/Dist/Display.html?Queue=MooX-Press.

SEE ALSO

Zydeco, MooX::Press.

AUTHOR

Toby Inkster <tobyink@cpan.org>.

COPYRIGHT AND LICENCE

This software is copyright (c) 2020 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.