The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.

NAME

Dot - The beginning of a Perl universe

SYNOPSIS

There's no synopsis since while there is indeed some code in Dot, it's for convenience only and not mandatory at all, you don't even have to install this module and then say use Dot; to use Dot, you could just use it (after reading this documentation). So providing a synopsis here would change the focus on the wrong subject, so I decided not to.

DESCRIPTION

Dot is an object system, where an object is a hash, a method is a closure, and a class is a subroutine. An object always starts as nothing, i.e. {}, and it could then be modified by one or more classes at any time by adding/removing/modifying methods to/from/inside it, with their lexical environment behind the scene, when they're called ($obj->{method}(@arg)) they automatically know to which object they're bind, on which data they should operate, so this or self is not needed, and anything that's not intended to be public could reside in its own lexical scope, in other words each method minds its own business at its own place, and yet methods of the same class could also cooperate by closing over the same data, and all the methods of an object could cooperate by closing over the object itself. It's a perfect demonstration of encapsulation, since no coordination is needed between each method on where to store its own data, in comparison to storing everything inside the object itself, whenever you choose a place to store something you have to make sure it's not used by any other method, one way or the other, otherwise you could end up overwriting somebody else's data.

sub class {
	my $obj = shift;
	# instance properties shared by all the methods of this class.
	my %limited;
	$obj->{method} = do {
		# instance properties that's only related to this method.
		my %secret;
		sub {
			# do something with %secret, %limited.
		};
	};
	$obj;
}
my $obj = class({});
$obj->{method}(...);

Classes act like hash modifiers, although you could write a class so that it always returns a brand new object, it's best to write it as a modifier since then not only you can create a new object from it you could apply it to an existing object as well, like my $obj = class3(class2(class1({}))). And that's just how multiple inheritance looks like (more on that later). One situation that you should always keep in mind is when a method closes over this object itself, like:

sub class {
	my $obj = shift;
	$obj->{method1} = sub {
		# I need to access another public method called method0 of
		# this object, but oops, circular reference.
		$obj->{method0}(...);
	};
	$obj;
}

You must say weaken(my $obj = shift) when this happens, and weaken (from Scalar::Util) is the only thing that's necessary (sometimes) to use Dot, everything else is optional, you don't even need Dot itself.

Inheritance

In Dot, when a class has been called to modify an object then this object inherits from that class, and when a class calls another class to help it modify an object then the former class inherits from the latter. And multiple inheritance happens when more than one classes are called directly. As you will see, inheritance in Dot is pretty different, it's dividable, trivial, efficient, at runtime and without ambiguity.

First, by using closures as methods, they do not belong to a package anymore, they're per object and thus disposable, which is not possible at all when using package as class since you could easily break other objects of the same class if you delete methods at your will. And because of this, the smallest unit of inheritance is not class but a single method or attribute, since you could always inherit from a class and delete every method and attribute it provides except the ones you want. That's why we say inheritance is dividable in Dot.

In addition to that, since methods are just closures, you don't need to create a package and put a subroutine inside it and then setup the inheritance and then re-bless the object in order to override or add a new method to an object, all you need to do is a single assign statement:

$obj->{method} = sub {
	# my new implementation.
};

or in the case of a method modifier:

$obj->{method} = do {
	my $old = $obj->{method};
	sub {
		# do something else, or not.
		&$old;
		# do something else, or not.
	};
};

So inheritance is not necessarily changed by a class, it could be changed by any statement that modifies the object (In a way that its current inheritance does not anticipate). Inheritance of an object is just the summary of all these modifications that's been applied to this object through its lifetime, you could group these modifications and turn them into one or multiple subroutines (i.e. classes), only when you feel convenient to do so. And thus the triviality, it's literally that easy. (Example: method-modifier.t)

Also, since all the public methods are stored inside the object itself and it's only a hash look up away to call them, and all the private methods are accessed through the lexical environment enclosed, method dispatching is not needed at all, which means inheritance is efficient this way.

Inheritance in Dot is not static but dynamically controllable at runtime, since inheritance is just which subroutines a class chooses to call to help itself, for example:

sub class {
	my $obj = shift;
	if ($some_condition) {
		classA($obj);
	} elsif ($some_other_condition) {
		classB($obj);
		classC($obj);
	} else {
		classD($obj);
		classE($obj);
		classF($obj);
	}
	$obj;
}

As shown by the previous class, the inheritance of the objects of the same class could be different, and they wouldn't interfere with each other, since there's no method dispatching.

Finally, the so called diamond problem is easily solved when using mulitple inheritance, suppose you're creating a class that inherts another two classes which both provide a method with the same name:

sub class {
	my $obj = shift;
	# Inherit from class1.
	class1($obj);
	# Make a backup of the method this class provides since it will
	# be overwritten.
	my $method1 = $obj->{method};
	# Inherit from class2.
	class2($obj);
	# Implementation of the method from class2.
	my $method2 = $obj->{method};
	# Now you have all the options.
	$obj->{method} = $method1;
	$obj->{method} = $method2;
	$obj->{method} = sub {
		&$method1;
		&$method2;
	};
}

As you can see, Dot gives you the ability to be explicit, and you could always get ambiguity out of the way with explicitness. (Example: mi-wo-diamond.t)

Attribute

As for attributes, you could just use methods for them, if you don't buy the always use methods for attributes since you could change the implementation without breaking the interface argument, you could just store them inside the object and access them directly, and tie them when more complex behavior is desired. See attribute-method.t and attribute-tie.t respectively for a detailed example.

Metaclass

Since in Dot, a class is just a subroutine which modifies a hash, by consequence a metaclass is just a subroutine which returns a subroutine which modifies a hash, and thus really easy to create, for example:

sub metaclass {
	my @classes = @_;
	sub {
		my $obj = shift;
		$_->($obj) for @classes;
		$obj;
	};
}

This metaclass takes a list of classes as its argument and returns a new class which inherits from them all. A metaclass does the same thing to a class as what a class does to an object, it hides the details and provides the simplest way for others to interact, just like when you call a method you don't have to know what data structure it uses, what lexical environment it resides, what other private methods it calls, when you call a class you don't have to worry about whether it's created from a metaclass, if yes what arguments are passed to it, what private variables it closes over, you only have to know that it's a class and if you call it with an object as the first argument it will modify this object according to its own definition.

As you have probably guessed, a metametaclass is just a subroutine which returns a subroutine which returns a subroutine which modifies a hash, it's just as easy to create, but it may be hard to find concrete uses for it.

Dot

As mentioned previously, Dot is an object system without code, but it does provides two functionalities for convenience: a class named mod and an import subroutine to be used by other modules.

Dot::mod

Dot::mod is a Dot class, we call it Dot for short, it's convenient to always inherit from it since it provides three methods that're used frequently:

weaken

Not a method exactly, but as explained previously, weaken is the only thing that's sometimes necessary to use Dot, thus it's important, so instead of loading Scalar::Util everywhere, Dot exports it and when you have an object that's been modified by Dot you can just call weaken using $obj->{weaken}($obj).

add

Add key-value pairs to this object so that they could be accessed publicly. It just does hash assignment and entirely for readability purpose:

$obj->{add}(... => ...,
	    ... => ...,
	    ...);

It's usually used to add a bunch of methods.

del

Delete from this object and return key-value pairs:

# returns 'method1', $obj->{method1}, 'method2', $obj->{method2}...
my %parent = $obj->{del}(qw/method1 method2 .../);
$obj->{add}(method1 => sub {
		    # modifier time.
		    &{$parent{method1}};
		    # modifier time.
	    },
	    ...);

It's mostly used to delete a bunch of methods from a parent class and install new methods that're modifiers of them.

import

Dot is designed to be the only module you ever have to use, and every other module is loaded by it when necessary. There're three requests that it provides:

sane

When you say use Dot 'sane';, it's equivalent to say the following:

use strict;
use warnings qw/all FATAL uninitialized/;
use feature qw/state say/;

That's also what Dot uses itself, it exports this for convenience, so that you can use this request if you happen to agree with this, instead of typing those pragma all over the places.

iautoload

iautoload takes an array (reference) as its argument and installs an AUTOLOAD subroutine inside the caller's package. The array is just a specification of all the modules in which Dot should search when an undefined subroutine is called in this package. In its simplest form, it's just a list of modules. You can also specify from which module should a subroutine be loaded, by using an array with the first element as the module's name and the rest as the names of the subroutines that should be loaded from it, for example:

use Dot iautoload => ['Module::A', [qw'Module::B sub1 sub2 sub3']];

Where undefined subroutines named sub1, sub2 or sub3 will be loaded from Module::B only, others will first be attempted to be loaded from Module::A, then Module::B if that fails, Dot will throw an exception if a subroutine couldn't be loaded anywhere.

If you prepend 0 to a subroutine's name then Dot will try to load it at compile time. That's often useful if its prototype matters when compiling.

As mentioned earlier, Dot is designed to be the only module you ever use. I never use other modules since I don't want my namespace polluted, and I don't want every module loaded at compile time, since some modules may only be needed occasionally, yet I also don't want add a require statement everytime I call a subroutine inside a module that's not always loaded. You may have seen the following idiom many times:

if ($something_bad_happens) {
	require Carp;
	Carp::confess('whatever');
}
...
if ($something_bad_happens_elsewhere) {
	require Carp;
	Carp::confess('not again');
}

And with Dot you could just say:

use Dot iautoload => ['Carp'];
confess('whatever') if $something_bad_happens;
...
confess('not again') if $something_bad_happens_elsewhere;

where module loading is handled automatically and only when necessary.

iautoload is used for conditional inheritance where classes of different name are spread across multiple modules and thus included.

oautoload

Like iautoload, but instead of installing an AUTOLOAD subroutine inside the caller's package, oautoload installs it inside the package of the module to be loaded, for example:

use Dot oautoload => [qw'ClassA ClassB ClassC'];
sub class {
	my $obj = shift;
	if ($some_condition) {
		ClassA::mod($obj);
	} elsif ($some_other_condition) {
		ClassB::mod($obj);
	} else {
		ClassC::mod($obj);
	}
	$obj;
}

i.e. the module is automatically loaded when you call a subroutine inside its package, so that you never have do a mannual require.

Since a class in Dot never creates but always modifies an object, I always use mod (in comparison to new) as its name (which means I cannot use iautoload), and as mentioned previously, inheritance in Dot is a runtime property, oautoload is especially handy when handling conditional inheritance where classes of the same name are spread across multiple modules and thus included here.

LICENSE

GPLv3.

AUTHOR

Yang Bo <rslovers@yandex.com>