My Perl Weekly Challenge

Really, how hard could it be?

All this talk about types, objects, and systems, got me to thinking, "what would it take to create a 100% backwards-compatible pure Perl proof-of-concept for optionally typable subroutine signatures". I mean really, how hard could it be? So I started sketching out some ideas and here's what I came up with:

use typing;

sub greet :Function(string, number) :Return() {
  my ($name, $count) = &_;

  print "Hi $name, you have $count messages waiting ...\n";
}

This final sketch felt very Perlish and I had an inkling that I might be able to tie all the concepts being used together, and I did. It actually works, and it's fast. Let's break down what's actually happening here.

use typing;

I don't particularly like or care about the name, I had to call it something, the code is about resolving types, so I called it "typing". When you import the package the framework installs two magic symbols. Oh, yes, btw, once I made it all work, I decided to extend it so that any type or object system could hook into it to allow the resolution of their own types using this system, so yes, it's a framework.

sub greet :Function(string, number) :Return();

The "greet" subroutine is just a plain ole subroutine. No source filter, no Perl keyword API, no XS, no high-magic. Here we're using old school "attributes" to annotate the subroutine and denote whether it's a function or method, and whether it has a return value or not.

sub greet :Method(object, string) :Return(object);

Declaring a subroutine as a method doesn't do anything special. No automagic unpacking, no implied/inferred first argument. The same is true for the "return" declaration. In fact, the annotations aren't involved in the execution of your program unless to you use the magic "unpacker" function.

# use this ...
my ($name, $count) = &_;

# instead of this ...
my ($name, $count) = @_;

This works due to a little-known Perl trick that only the most neck-beardiest of Perl hackers understand, and me, which is what happens when you call a subroutine using the ampersand without any arguments, i.e. you can operate on the caller's argument list. By naming the function _, and requiring the use of the ampersand, we've created a cute little synonym for the @_ variable.

sub greet :Function(string, number) :Return() {
  my ($name, $count) = &_;
  # ...
}

Here's what's happening. The "unpacker" function gets the typed argument list for the calling subroutine, i.e. it gets its signature, then it iterates over the arguments calling the configured validator for each type expression specified at each position in the argument list.

greet() # error
greet({}) # error
greet('bob') # error
greet('bob', 2) # sweet :)

But what happens if you provide more arguments than the signature has type expressions for? The system is designed to use the last type specified to validate all out-of-bounds arguments, which means greet('bob', 2..10) works and passes the type checks, but greet('bob', 2, 'bob', 2) doesn't because the second 'bob' isn't a number. Make sense? Right, but what about the framework bit? First, let's see what it looks like to hook into the framework as a one-off:

use typing;

typing::set_spaces('main', 'main');

sub main::string::make {
  # must return (value, valid?, explanation)
  ($_[0], 1, '')
}

sub greet :Function(string) {
  my ($name) = &_;
  print "Hi $name\n";
}

This example is one of the simplest hooks. The set_spaces function says, the caller is the "main" package, and we should look for types under the "main" namespace by prefixing the type name with "main" and looking for a subroutine called "make". The "make" subroutine must return a list in the form of (value, valid?, explanation). This approach expects each type to correspond to a package (with a "make" routine), but maybe you like the type library/registry approach. Another way to hook into the system is to bring your own resolver:

use typing;

typing::set_resolver('main', ['main', 'resolver']);

sub resolver {
  # accepts (package, type-expr, value)
  # returns (value, valid?, explanation)
  # maybe you'll do something like $registry->get(type-expr)->validate(value)
  ($_[0], 1, '')
}

sub greet :Function(string) {
  my ($name) = &_;
  print "Hi $name\n";
}

This example uses the set_resolver function and says, the caller is the "main" package, and we should resolve all types in the "main" package using the main::resolver subroutine. The resolver must return a list in the form of (value, valid?, explanation). But wait, there's more, ... we can use the framework's API to get the subroutine metadata to further automate our programs, for example:

use typing;

typing::retrieve('main', 'method', 'greet')
# [
#   'MyApp::Object',
#   'string',
#   'number'
# ]

typing::retrieve('main', 'return', 'greet')
# [
#   'MyApp::Object'
# ]

sub greet :Function(MyApp::Object, string, number) :Return(MyApp::Object) {
  # ...
}

I'm supposed to be working on a completely different project right now, but this idea captivated me and so I lost a couple of days of productivity. Maybe others will find this idea interesting. If you're interested in the source code for this concept you can find it here!

Sources

Type libraries

MooseX::Types

Type::Tiny

Specio

Subroutine signatures

Function::Parameters

Method::Signatures

registry/routines

End Quote

"Software is like art and the only justification you need for creating it is, 'because I felt like it'" - Andy Wardley

Authors

Awncorp, awncorp@cpan.org

Types, Objects, and Systems, Oh my!

Inextricably bound

Perl isn't a strongly typed language, and its built-in types are limited and not generally accessible to the engineer, however, Perl supports various classes of data and in recent years has flirted with various ways of enabling runtime type checking.

In a strongly typed language the tl;dr; case for declaring data types is memory management, compile-time code optimization, and correctness. To this day I'm both impressed and horrified by the number of errors caught when I implement some kind of type checking in my programs. When it comes to runtime type checking we're only concerned with enforcing correctness.

Types, values, objects, signatures, and the systems that tie these all together, are all inextricably bound. They are necessarily interdependent in order to present/provide a cohesive and consistent system. Peeling back the layers a bit, types are merely classifications of data. Any given piece of data can be classified as belonging to a particular type whether implicit or explicit.

Types are instantiated (i.e. have concrete representations, i.e. instances) whenever data is created and/or declared as conforming to the type's criteria. Runtime types are arbitrary. A value of 1 can be said to be of type number where the value "1" can be said to be of the type string. Also in Perl, an object is a specific kind of reference; a reference tied to a particular namespace.

Runtime type libraries

Currently, as per the CPAN, there are a few different ways to declare and use runtime type checking in your application. The three most popular libraries, in no particular order, are, MooseX::Types, Type::Tiny, and Specio. All of these type libraries have Moo/se compatibility as their stated goal.

MooseX::Types (2009) was designed to address the Moose global registry (and type conflict) problem.

package MyLibrary;

use MooseX::Types -declare => [
  'PositiveInt',
  'NegativeInt',
];

use MooseX::Types::Moose 'Int';

subtype PositiveInt,
  as Int,
  where { $_ > 0 },
  message { "Int is not larger than 0" };

subtype NegativeInt,
  as Int,
  where { $_ < 0 },
  message { "Int is not smaller than 0" };

1;

Type::Tiny (2013), inspired by MooseX::Types, was designed to have a small footprint, a single non-core dependency, a set of "core" types as a standard library, and to have first-class support for use with Moo.

package MyLibrary;

use Type::Library -base;
use Type::Utils -all;

BEGIN { extends "Types::Standard" };

declare 'PositiveInt',
  as 'Int',
  where { $_ > 0 },
  message { "Int is not larger than 0" };

declare 'NegativeInt',
  as 'Int',
  where { $_ < 0 },
  message { "Int is not smaller than 0" };

1;

Specio (2013) is meant to be a replacement for Moose's built-in types, MooseX::Types, and the Moose global registry (and type conflict) problem.

package MyLibrary;

use Specio::Declare;
use Specio::Library::Builtins;

declare(
  'PositiveInt',
  parent => t('Int'),
  where  => sub { $_[0] > 0 },
  message_generator => sub { "Int is not larger than 0" },
);

declare(
  'NegativeInt',
  parent => t('Int'),
  where  => sub { $_[0] < 0 },
  message_generator => sub { "Int is not smaller than 0" },
);

1;

What these libraries have in common is the concept of declaring custom types using a DSL and organizing and exporting types from one or more registries. They also (in practice) produce registries that act as exporters that litter the calling package with use-once functions which require namespace::autoclean to get rid of. To be fair, both Type-Tiny and Specio have object-oriented interfaces that allow you to build types and registries without using the DSL.

Introducing Venus::Assert

Meet Venus::Assert, a simple data type assertion class that could serve as the foundation for a future object-oriented type assertion and coercion system for Perl 5.

Venus, the non-core object-oriented standard library, by necessity needs to be able to distinguish between different types of data. It's how the library is capable of distinguishing between the number 1, the string 1, and the conventional boolean 1.

Venus::Assert wraps that know-how in an intuitive utility class that behaves in the tradition of its siblings and provides the foundations for a future Venus-based unified type checking system.

Because Venus::Assert is a Venus utility class it can, without any additional code, complexity, or effort, throw itself, catch itself, try itself, etc.

Throws itself

Venus::Assert->new('PositiveNum')->number(sub{$_->value > 0})->throw;

Catches itself

my ($result, $error) = Venus::Assert->new('NegativeNum')->number(sub{$_->value < 0})->catch('validate', $value);

Tries itself

my $tryable = Venus::Assert->new('PositiveNum')->number(sub{$_->value > 0})->try('validate', $value);

Venus::Assert doesn't have a DSL, doesn't support or encourage type registries, doesn't concern itself with inlining, and doesn't declare parent types to be extended. Venus::Assert instances are simple code-convertible objects built on Venus::Match for powerful object-oriented case/switch operations. Code-convertible custom types can be declared as plain ole packages which conform to a particular interface:

package MyApp::Type::PositiveNumber;

use base 'Venus::Assert';

sub conditions {
  my ($self) = @_;

  $self->number(sub{$_->value > 0});
}

1;

Extending custom types with proper names and explanations (on failure) by doing something like the following:

package MyApp::Type::PositiveNumber;

use base 'Venus::Assert';

sub name {
  my ($self) = @_;
  return $self->class;
}

sub message {
  my ($self) = @_;
  return 'Type assertion (%s) failed, expects a number > 0, received (%s)';
}

sub conditions {
  my ($self) = @_;
  return $self->number(sub{$_->value > 0});
}

Types::Standard via Venus::Assert

We could easily use Venus::Assert to approximate 90% of what the Type::Tiny Types::Standard library does, with a few lines of code. For example:

Any

Venus::Assert->new->any

... or

Venus::Assert->new(on_none => sub{true})

Bool

Venus::Assert->new->boolean

Maybe[a]

Venus::Assert->new->maybe($a)

... or

Venus::Assert->new->maybe->$a

... or

Venus::Assert->new->undef->$a

Undef

Venus::Assert->new->undef

Defined

Venus::Assert->new->defined

... or

Venus::Assert->new->when(sub{defined $_->value})->then(sub{true})

Value

Venus::Assert->new->value

... or

Venus::Assert->new->when(sub{defined $_->value && !ref $_->value})->then(sub{true})

Str

Venus::Assert->new->string

Num

Venus::Assert->new->number

ClassName

Venus::Assert->new->package

... or

Venus::Assert->new->string->constraints->where->defined(sub{
  Venus::Space->new($_->value)->loaded
})

Ref[a]

Venus::Assert->new->reference

... or

Venus::Assert->new->defined(sub{
  ref($_->value) eq $a
})

ScalarRef[a]

Venus::Assert->new->scalar

... or

Venus::Assert->new->scalar(sub{
  Venus::Assert->new->$a->check($_->value)
});

ArrayRef[a]

Venus::Assert->new->array

... or

Venus::Assert->new->array(sub{
  Venus::Assert->new->$a->check($_->value)
});

HashRef[a]

Venus::Assert->new->hash

... or

Venus::Assert->new->array(sub{
  Venus::Assert->new->$a->check($_->value)
});

CodeRef

Venus::Assert->new->code

RegexpRef

Venus::Assert->new->regexp

GlobRef

Venus::Assert->new->reference(sub{ref $_->value eq 'GLOB'})

Object

Venus::Assert->new->object

Tuple[a]

Venus::Assert->new->tuple(@a)

InstanceOf[a]

Venus::Assert->new->identity($a)

ConsumerOf[a]

Venus::Assert->new->consumes($a)

HasMethods[a]

Venus::Assert->new->routines(@a)

StrMatch[a]

Venus::Assert->new->string(sub{
  $_->deduce->contains($a)
})

Enum[a]

Venus::Assert->new->enum(@a)

The state of the art

Again, types, objects, signatures, and systems, are inextricably bound which means that a good system will provide both architecture and abstractions to support interoperability, or at least declare its way as the one true way. What we have today is an assortment of libraries that tackle a particular aspect of the "runtime type checking" system. Your mileage may vary with regard to interoperability.

Moo/se allows you to type constrain class attributes using MooseX::Types, Type::Tiny, and/or Specio, etc. Moose (last time I check) uses a single global type registry and isn't designed to play nice with others. Moo, to its credit, provides a simple system-agnostic interface, i.e. accepts code-convertible values, which Type::Tiny takes full advantage of. Type::Tiny goes through some pains (IMHO) to make itself Moose-aware and interoperable. None of these libraries prescribed a methodology for reusing the declared types in function/method signatures. Method::Signatures provides function and method signatures but only supports Moose types (as per its global registry). Function::Parameters goes a bit further and does provide an abstraction for hooking into the type resolution mechanism as well as the ability to use local functions in signature type notations.

The Perl "signatures" feature provided bare-bones untyped/untypable signatures, and is little more than syntactic sugar for argument unpacking. The registry/routines pragmas attempt to bring together Function::Parameters and Type::Tiny to provide a unified system for runtime type checking. All of the type libraries support parameterized type declarations, and yet none of the signature pragmas/parsers do.

The future, hopefully

To have proper compile-time types (which are extendable), they need to be built into the language, in which case you'll likely end up with something like Raku.

To have proper runtime types which feel natural and legit in Perl 5 we need to nail the interoperability problem, and to do that we need to devise a standard that allows systems to cooperate.

We need package fields, objects, properties, values, and subroutine signatures to be capable of using the same vocabulary and type notation to declare type constraints, thus recognizing and accepting the same types and data.

Here's an idea

Simple architecture:

  • A type is simply a package with "check" and "make" routines
  • The "check" routine accepts any value and returns a tuple, i.e. (valid?, explanation)
  • The "make" routine accepts any value and returns the value (possibly modified) and the result of the "check" routine, i.e. a tuple of (value, valid?, explanation)
  • A "types" statement, appends the currently scoped @TYPES global, allowing the use of shortnames when referring to types
  • The "type" function accepts a string-based type expression, and any value, resolving the type expression and executing the type's "make" function

Declare custom type

package MyApp::Type::String;

sub check {
  my ($value) = @_;
  (false, 'Not a string')
}

sub make {
  my ($value) = @_
  ($value, check($value));
}

1;

Resolve custom type

package MyApp;

use the-idea-materialized;

types 'MyApp::Type';

my ($value) = type('String', 'hello');

1;

Governance rules and notation:

  • No parameterized types
  • Type resolution fallback to some "core" namespace if local @TYPES global is empty

Sources

Object-systems

Moo

Moose

Type libraries

MooseX::Types

Type::Tiny

Specio

Type::Library

Types::Standard

Subroutine signatures

Function::Parameters

Method::Signatures

registry/routines

End Quote

"Check yo' self before you wreck yo' self" - Ice Cube

Introducing Venus, a new world for Perl 5

Abstract

Programming in Perl is choices all the way down. Perl is a multi-paradigm programming language which means that Perl can support the development of software using different programming paradigms, e.g. functional programming, object-oriented programming, and more.

Programming languages are culture, and culture follows philosophy. Philosophy is how every culture provides itself with justification for its decisions, values, beliefs, and worldview.

Perl's philosophy

Perl's philosophy is TIMTOWTDI, i.e. “there’s more than one way to do it”. You might even say that Perl takes the position of not taking a position, and this disposition applies to the topic of a standard library as well.

To be clear, what some will call Perl's standard library is little more than a grab bag of functions, pragmas, and packages shipped together with the core. This collection is a mix of functional and object-oriented styles and is intentionally lightweight.

It’s probably worth mentioning that the Raku team has also adopted this posture (and tradition).

What if Perl had a standard library?

Not so fast. Let’s say we agree to provide a robust standard library, what paradigm should that library use? Should that library be written in C to maximize performance, or be written in Perl itself? Should the library be readable and serve as an example of how to Perl, or simply be a means to an end? Decisions, decisions.

CPAN in lieu of a standard library

Perl and its CPAN are overwhelmingly object-oriented, and here's where the TIMTOWTDI motto is on full display. This is both a gift and curse, simultaneously the source of Perl's greatest strengths and its greatest weaknesses. Although, even in the face of such an abundance of choice there have been a handful of CPAN distributions that have become de facto standards for the community. Still, the paradox of choice is real and paralyzing, and it’s really hard to find and/or create unity amongst all of the diversity.

Sustained object-orientation in Perl is difficult because the concept and mechanisms were bolted onto the language as an afterthought, and because it's optional, so one has to oscillate between this paradigm and others, i.e. some things are objects, most things are not, so when using Perl you have to constantly reaffirm your object-orientation.

The TL;DR

An OO standard library would make it a lot easier to write Perl in a way that avoids having to come up with similar or the same solutions to common computing tasks. It would make code easier to read and write and eliminate a lot of decision fatigue, especially that which comes with the "search for a library" or "write this library myself" obstacle, which is exactly the type of situation the NIH (not invented here) crowd enjoy. It would make downstream applications more performant and have fewer dependencies. So, is TIMTOWTDI a gift or a curse? Is convention over configuration a straightjacket? Is the Python "batteries included" motto best or bloat?

What's in a standard library

Typically a standard library will have routines to access the file system, and perform I/O operations; routines that work with the data types of the language; routines that allow manipulating memory; routines that enable concurrency/parallelism; routines to handle math, and date and time operations; and routines for error handling and assertions. Often, to ensure optimal performance, these routines are written in very low-level code, e.g. C.

What makes a good standard library

I believe a good standard library should have all of the standard fares, but should also be well organized, be dogfooding itself, and be written as to be readable in ways that help to enforce that derivative programs are idiomatic. In other words, be prescriptive, which Perl tries not to be.

A good standard library should have the goal of helping to reduce the number of decisions that an engineer is required to make without compromising flexibility or the don't repeat yourself (DRY) principles.

A standard library should answer the call and respond to what are people building (in the here and now), or what they're trying to build. What they don't have that they wish they did; what they want to adopt from other languages and frameworks which are successful in ways that we desire our software to be. To facilitate developer ergonomics.

Enter Venus

Introducing Venus, an attempt at establishing an opinionated idiomatic non-core object-oriented standard library for Perl 5, without years of navel-gazing, committee legislation, infighting, or stalling.

Venus has a simple modular architecture, robust library of classes, methods, and traits (roles), supports pure-Perl autoboxing, advanced exception handling, backward-compatible "true" and "false" keyword functions, simple package introspection, command-line options parsing, and much more.

The project motto and ethic is to "be a compliment, not a cudgel". This ethic is a kind of guiding principle that governs decision-making. The Venus system is strictly complimentary, all features are optional and opt-in (even autoboxing; especially autoboxing). This system can be easily extended without monkey-patching (i.e. via plugins), and useful behaviors can be reused outside of the system via traits (roles).

Some Features

  • Supports Perl 5.18.0+
  • Backward-compatible Booleans
  • Composable Standards
  • Exception Handling
  • Fast Object-Orientation
  • Package Reflection
  • Perl-Perl Autoboxing
  • Pluggable Standard Library
  • Robust Documentation
  • Utility Classes
  • Value Classes
  • Zero Dependencies

Guiding Principles

  • The standard library should be a standard
  • The library should have zero-dependencies
  • The library should be prescriptive yet malleable
  • The library should service two major categories of need: values and utilities
  • The library should support wrapping all major native data types
  • The value class methods should use idiomatic pure-Perl algorithms
  • The library should leverage roles and interfaces to maximize behavior-portability
  • The library should expose and make use of the raw materials in the Perl core
  • The library classes can be easily extended (i.e. subclassed)
  • The library should allow plugins and discourage monkey-patching
  • The library should provide mechanisms for error handling (throwing, catching, etc)
  • The library should provide DMMT support for JSON and YAML
  • The library should be consistent in namings and conventions to increase predictability
  • The library should ease the multi-paradigm identity crisis
  • The library should provide robust documentation with maximal test coverage

The World Tour

All Venus packages are classes and can usually be thought to belong in one of four categories; core classes, value classes, utility classes, or abstract behaviors (roles).

CORE CLASSES

Venus - The main module which exports a handful of useful functions that come standard with most other languages.

package main;

use Venus qw(
  catch
  error
  raise
);

# the "catch" function for trapping exceptions, e.g. the "error" function
my ($error, $result) = catch {
  error;
};

# the "true" and "false" keyword functions
if ($result and $result eq false) {
  true;
}

# the "raise" function for raising custom exceptions
if (false) {
  raise 'MyApp::Error';
}

# and much more!
true ne false;

Venus::Class - The class builder module which uses the Mars architecture and supports integrating superclasses, mixins, roles, and interfaces.

package Person;

use Venus::Class 'attr';

attr 'fname';
attr 'lname';

package User;

use Venus::Class 'attr', 'base';

base 'Person';

attr 'email';

package main;

my $user = User->new(
  fname => 'Elliot',
  lname => 'Alderson',
);

# bless({fname => 'Elliot', lname => 'Alderson'}, 'User')

Venus::Mixin - The mixin builder module which uses the Mars architecture and supports is essentially an exporter builder that supports dynamic exports.

package Access;

use Venus::Mixin;

sub login {
  # ...
}

sub logout {
  # ...
}

sub EXPORT {
  ['login', 'logout']
}

package User;

use Venus::Class 'attr', 'mixin';

mixin 'Access';

attr 'email';
attr 'password';

package main;

my $user = User->new(
  fname => 'Elliot',
  lname => 'Alderson',
);

# bless({fname => 'Elliot', lname => 'Alderson'}, 'User')

# $user->login;

# undef

# $user->logout;

# undef

Venus::Role - The role builder module which uses the Mars architecture and supports integrating superclasses, mixins, roles, and interfaces.

package Authenticable;

use Venus::Role 'error';

sub AUDIT {
  my ($self, $from) = @_;

  error "${from} missing the email attribute" if !$from->can('email');
  error "${from} missing the password attribute" if !$from->can('password');
}

package User;

use Venus::Class 'attr', 'with';

with 'Authenticable';

attr 'email';

package main;

# Exception! "User missing the password attribute"

VALUE CLASSES

Venus::Array - The Array class provides methods for manipulating array references.

package main;

use Venus::Array;

my $array = Venus::Array->new([1..4]);

# bless({'value' => [1, 2, 3, 4]}, 'Venus::Array')

$array->count;

# 4

# $array->all(sub{ $_ > 0 });
# $array->any(sub{ $_ > 0 });
# $array->each(sub{ $_ > 0 });
# $array->grep(sub{ $_ > 0 });
# $array->map(sub{ $_ > 0 });
# $array->none(sub{ $_ < 0 });
# $array->one(sub{ $_ == 0 });
# $array->random;

Venus::Boolean - The Boolean class provides methods for representing and operating on boolean values.

package main;

use Venus::Boolean;

my $boolean = Venus::Boolean->new(1);

# bless({'value' => 1}, 'Venus::Boolean')

$boolean->negate;

# 0

Venus::Code - The Code class provides methods for manipulating code references.

package main;

use Venus::Code;

my $code = Venus::Code->new(sub {1});

# bless({'value' => sub {...}}, 'Venus::Code')

$code->call;

# 1

Venus::Float - The Float class provides methods for manipulating floating-point numbers.

package main;

use Venus::Float;

my $float = Venus::Float->new(1.23);

# bless({'value' => '1.23'}, 'Venus::Float')

$float->int;

# 1

Venus::Hash - The Hash class provides methods for manipulating hash references.

use Venus::Hash;

my $hash = Venus::Hash->new({1..8});

# bless({'value' => {'1' => 2, '3' => 4, '5' => 6, '7' => 8}}, 'Venus::Hash')

$hash->count;

# 4

# $hash->all(sub{ $_ > 0 });
# $hash->any(sub{ $_ > 0 });
# $hash->each(sub{ $_ > 0 });
# $hash->grep(sub{ $_ > 0 });
# $hash->map(sub{ $_ > 0 });
# $hash->none(sub{ $_ < 0 });
# $hash->one(sub{ $_ == 0 });
# $hash->random;

Venus::Number - The Number class provides methods for manipulating numeric values.

package main;

use Venus::Number;

my $number = Venus::Number->new(1_000);

# bless({'value' => 1000}, 'Venus::Number')

$number->abs;

# 1000

Venus::Regexp - The Regexp class provides methods for manipulating regular expression references.

package main;

use Venus::Regexp;

my $regexp = Venus::Regexp->new(
  qr/(?<greet>\w+) (?<username>\w+)/u,
);

# bless({'value' => qr/(?<greet>\w+) (?<username>\w+)/u}, 'Venus::Regexp')

$regexp->search('hello venus')->captures;

# ['hello', 'venus']

Venus::Scalar - The Scalar class provides methods for manipulating scalar references.

package main;

use Venus::Scalar;

my $scalar = Venus::Scalar->new;

# bless({'value' => \''}, 'Venus::Scalar')

${$scalar}

# ""

Venus::String - The String class provides methods for manipulating string values.

package main;

use Venus::String;

my $string = Venus::String->new('hello world');

# bless({'value' => 'hello world'}, 'Venus::String')

$string->camelcase;

# "helloWorld"

Venus::Undef - The Undef class provides methods for representing undefined values.

package main;

use Venus::Undef;

my $undef = Venus::Undef->new;

# bless({'value' => undef}, 'Venus::Undef')

$undef->defined;

# 0

UTILITY CLASSES

Venus::Args - The Args class provides methods for accessing and manipulating @ARGS values.

package main;

use Venus::Args;

my $args = Venus::Args->new(
  named => { flag => 0, command => 1 }, # optional
  value => ['--help', 'execute'],
);

# bless({....}, 'Venus::Args')

$args->flag;

# "--help"

# $args->get(0); # $ARGV[0]
# $args->get(1); # $ARGV[1]
# $args->action; # $ARGV[1]
# $args->exists(0); # exists $ARGV[0]
# $args->exists('flag'); # exists $ARGV[0]
# $args->get('flag'); # $ARGV[0]

Venus::Box - The Box class is a proxy class provides a mechanism for autoboxing the return values of its proxied objects.

package main;

use Venus::Box;

my $box = Venus::Box->new(
  value => {},
);

# bless({'value' => bless({'value' => {}}, 'Venus::Hash')}, 'Venus::Box')

$box->keys->count->unbox;

# 0

Venus::Data - The Data class provides methods for accessing and manipulating POD data (and data sections) in the file or the __DATA__ token.

package main;

use Venus::Data;

my $data = Venus::Data->new;

# bless({...}, 'Venus::Data')

$data->value($data->space->format('label', 't/%s.t'));

# /path/to/t/Venus_Data.t

$data->find(undef, 'name');

# {
#   'data' => ['Venus::Data'],
#   'index' => 1,
#   'list' => undef,
#   'name' => 'name'
# }

Venus::Date - The Date class provides methods for manipulating date and time values.

package main;

use Venus::Date;

my $date = Venus::Date->new(570672000);

# bless({...}, 'Venus::Date')

$date->string;

# '1988-02-01T00:00:00Z'

Venus::Error - The Error class provides methods for throwing exceptions.

package main;

use Venus::Error;

my $error = Venus::Error->new;

# bless({...}, 'Venus::Error')

$error->throw;

# Exception!

Venus::Json - The Json class provides methods for encoding and decoding JSON data.

package main;

use Venus::Json;

my $json = Venus::Json->new(
  value => { name => ['Ready', 'Robot'], version => 0.12, stable => !!1, }
);

# bless({...}, 'Venus::Json')

$json->encode;

# {"name": ["Ready", "Robot"], "stable": true, "version": 0.12}

Venus::Match - The Match class provides an object-oriented switch mechanism (or dispatch table).

package main;

use Venus::Match;

my $match = Venus::Match->new(5);

# bless({...}, 'Venus::Match')

$match->when(sub{$_ < 5})->then(sub{"< 5"});
$match->when(sub{$_ > 5})->then(sub{"> 5"});
$match->none(sub{"?"});

my $result = $match->result;

# "?"

Venus::Name - The Name class provides methods for parsing and formatting package namespace strings.

package main;

use Venus::Name;

my $name = Venus::Name->new('Foo/Bar');

# bless({'value' => 'Foo/Bar'}, 'Venus::Name')

$name->package;

# "Foo::Bar"

Venus::Opts - The Opts class provides methods for accessing and manipulating @ARGS values that are passed as command-line options.

package main;

use Venus::Opts;

my $opts = Venus::Opts->new(
  value => ['--resource', 'users', '--help'],
  specs => ['resource|r=s', 'help|h'],
  named => { method => 'resource' } # optional
);

# bless({...}, 'Venus::Opts')

$opts->method;

# "users"

# $opts->method; # $resource
# $opts->get('resource'); # $resource

# $opts->help; # $help
# $opts->get('help'); # $help

Venus::Path - The Path class provides methods for operating on paths.

package main;

use Venus::Path;

my $path = Venus::Path->new('t/data/planets');

# bless({'value' => 't/data/planets'}, 'Venus::Path')

my $planets = $path->files;

# [
#   bless({...}, 'Venus::Path'),
#   bless({...}, 'Venus::Path'),
#   ...,
# ]

# my $mercury = $path->child('mercury');
# my $content = $mercury->read;

Venus::Process - The Process class provides methods for forking and managing processes.

package main;

use Venus::Process;

my $parent = Venus::Process->new;

# bless({'value' => 2179356}, 'Venus::Process')

my $process = $parent->fork;

if ($process) {
  # do something in child process ...
  $process->exit;
}
else {
  # do something in parent process ...
  $parent->wait(-1);
}

# $parent->exit;

Venus::Random - The Random class provides an object-oriented interface for Perl's pseudo-random number generator (or PRNG) which produces a deterministic sequence of bits which approximate true randomness.

package main;

use Venus::Random;

my $random = Venus::Random->new(42);

# bless({'value' => 42}, 'Venus::Random')

my $bit = $random->bit;

# 1

my $number = $random->range(10, 50);

# 24

Venus::Replace - The Replace class provides methods for manipulating regular expression replacement data.

package main;

use Venus::Replace;

my $replace = Venus::Replace->new(
  string => 'hello world',
  regexp => '(world)',
  substr => 'universe',
);

# bless({...}, 'Venus::Replace')

$replace->captures;

# "world"

Venus::Search - The Search class provides methods for manipulating regexp search data.

package main;

use Venus::Search;

my $search = Venus::Search->new(
  string => 'hello world',
  regexp => '(hello)',
);

# bless({...}, 'Venus::Search')

$search->captures;

# "hello"

Venus::Space - The Space class provides methods for parsing and manipulating package namespaces.

package main;

use Venus::Space;

my $space = Venus::Space->new('foo/bar');

# bless({'value' => 'Foo::Bar'}, 'Venus::Space')

$space->package;

# Foo::Bar

Venus::Template - The Template class provides minimalist template rendering functionality.

package main;

use Venus::Template;

my $template = Venus::Template->new(
  'From: <{{ email }}>',
);

# bless({...}, 'Venus::Template')

$template->render;

# "From: <>"

Venus::Throw - The Throw class provides a mechanism for generating and raising errors (exception objects).

package main;

use Venus::Throw;

my $throw = Venus::Throw->new;

# bless({'parent' => 'Venus::Error'}, 'Venus::Throw')

$throw->error;

# Exception!

Venus::Try - The Try class provides an object-oriented interface for performing complex try/catch operations.

package main;

use Venus::Try;

my $try = Venus::Try->new;

# bless({...}, 'Venus::Try')

$try->call(sub {
  my (@args) = @_;

  # try something

  return 2 * 3 * 4;
});

$try->catch('Example::Error', sub {
  my ($caught) = @_;

  # caught an error (exception)

  return;
});

$try->default(sub {
  my ($caught) = @_;

  # catch the uncaught

  return;
});

$try->finally(sub {
  my (@args) = @_;

  # always run after try/catch

  return;
});

my @args;

my $result = $try->result(@args);

# 24

Venus::Type - The Type class provides methods for casting native data types to objects and the reverse.

package main;

use Venus::Type;

my $type = Venus::Type->new([]);

# bless({'value' => []}, 'Venus::Type')

my $object = $type->deduce;

# bless({'value' => []}, 'Venus::Array')

my $code = $type->code;

# "ARRAY"

Venus::Vars - The Vars class provides methods for accessing %ENV items.

package main;

use Venus::Vars;

my $vars = Venus::Vars->new(
  value => { USER => 'awncorp', HOME => '/home/awncorp', },
  named => { iam => 'USER', root => 'HOME', },
);

# bless({....}, 'Venus::Vars')

$vars->iam;

# "awncorp"

# $vars->root; # $ENV{HOME}
# $vars->home; # $ENV{HOME}
# $vars->get('home'); # $ENV{HOME}
# $vars->get('HOME'); # $ENV{HOME}

# $vars->iam; # $ENV{USER}
# $vars->user; # $ENV{USER}
# $vars->get('user'); # $ENV{USER}
# $vars->get('USER'); # $ENV{USER}

Venus::Yaml - The Yaml class provides methods for encoding and decoding YAML data.

package main;

use Venus::Yaml;

my $yaml = Venus::Yaml->new(
  value => { name => ['Ready', 'Robot'], version => 0.12, stable => !!1, }
);

# bless({...}, 'Venus::Yaml')

$yaml->encode;

# "---\nname:\n- Ready\n- Robot\nstable: true\nversion: 0.12\n"

Why Try Venus

  • You're doing OO in Perl 5
  • You're interested in modern idiomatic Perl
  • You're looking for an architectural standard
  • You're partial to convention over configuration
  • You're in need of power and performance

Inspiration

  • https://github.com/ruby/ruby
  • https://github.com/crystal-lang/crystal
  • https://github.com/elixir-lang/elixir
  • https://github.com/python/cpython

Sources

Venus: OO Standard Library for Perl 5 - Github

To learn a new language, read its standard library - Hacker News

Is the standard library of any major programming language a liability? - ProgrammingLanguages

End Quote

"Most software today is very much like an Egyptian pyramid with millions of bricks piled on top of each other, with no structural integrity, but just done by brute force and thousands of slaves." - Alan Kay