Skip to content

Instantly share code, notes, and snippets.

@happy-barney
Last active July 16, 2021 14:52
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save happy-barney/d94d3a6d30b4529ab86ef5ea6c78a043 to your computer and use it in GitHub Desktop.
Save happy-barney/d94d3a6d30b4529ab86ef5ea6c78a043 to your computer and use it in GitHub Desktop.

Purpose of this document

Reuse existing idea (Cor slots) and show how this new citizen can provide interesting advantages for developers (and language PR).

Motivation

I think that perl5 should focus on future of programming and emerging trends. Personally I believe future lies in

  • domain driven development for providing domain specific terms to solve domain problems

  • context oriented programming for providing data driven scoping as a counterpart to code structure driven scoping

Slot

I'm using term slot as used by Cor / Object::Pad - data-context controlled named memory place (intentionally not using term variable).

(examples refers to Object::Pad synopsis)

Although OOP is obvious to use them there are also other patterns where improved implementation of slots can provide another commonly used patterns (and possibly more).

Although anything described here can be achieved with variables as available right now, I'd let variables their current role being code-controlled.

Slot address / data-context

With slots having different address syntax (as LeoNerd already started to contemplate about on #perl5) we can provide a mechanism to address any slot to provide alternative default value.

Example (used Object::Pad's synopsis), syntax borrowed from xpath

	${: / Point / x }
	${: / Point / y }
	${: / Point / move / dX }
	${: / Point [ ${:x} == 0 ] / move / dX }

Same syntax can / should be used in catch expression as well

	CATCH (X::Foo [ ${:name} =~ ... ] | X::Bar) ...

Altering slot

Capability to address slot allows to locally alter slot's behaviour.

Operator := used in examples should read as "extend meta of LHS"

Alter slot's default value

	local ${: / Point / x } := 0;
	local ${: / Point / x } := default => 0;
	local ${: / Point / x } := default => sub { ${:y} };

Apply additional constraint

	local ${: / Point / x } := constraint => Limit (min => 0, max => 10);

Apply additional trait (mimics jsonproperty used in java world)

	local ${: / Point / x } := trait => JsonProperty ('point-x');
	local ${: / Github / login / username } := trait => Disabled;

Idea - Sub signatures

Sub arguments can be considered slots as well.

Motivation:

  • consistency - context input parameters will be defined same way as classes
  • slot allows more complex usage than signatures including optional dependencies

Imagine sub as some private class with single method so

	# so method call like this
	$point->move (1, 2);

	# can be understand as
	class Point::move isa Method {
		has ($x, $y) :new :type(Int);
		returns :type(Int);
	}
	Point::move->new (self := $point, x := 1, y := 2)->execute;

Modified example of method move with slots (how it may look like):

	method move {
		returns :type (Int);
		has $dX :type (Int);
		has $dY :type (Int);
	}

Using this mental model we can provide roles for methods

	Getopt::Long (
		'opt1=s' => sub does Getopt::Long::Callback { say "got ${:option} = ${:value}" },
	);

Idea - Named arguments convention

Introducing new operator := for assigning default value to slot it's possible to support mixed argument convention (how it may look like):

	# every call will do the same
	$point->move (1, 2);
	$point->move (dX := 1, dY := 2);
	$point->move (1, dY := 2);
	$point->move (2, dX := 1);
	$point->move (dY := 2, 1);

Although last two examples may look confusing such possibility will simplify parser as well as it may be handy for code generators.

Idea - Dependency injection

Probably most valuable feature from language propagation perspective.

Example - create multiple 1D points

	local ${: / Point / x } := 0;

	Point->new (1);     # x = 0; y = 1
	Point->new (2);     # x = 0; y = 2
	Point->new (3);     # x = 0; y = 3

Pattern can be applied also on methods (some kind of anonymous Curring)

	local ${: / Point / &move / dX } := 1;

	$point->move (2); # dX = 1, dY = 2
	$point->move (3); # dX = 1, dY = 3

Idea - Currying

Curring is in fact dependency injection as described before.

Example:

	method move_little_bit_left {
		local ${: &move / dX } = -1;

		goto &move;
	}
  • move_little_bit_left signature will contain only dY

Delegation

Delegation is special case of currying

Example: move method of polar vector is just move of its starting point

	class Vector::Polar {
		has $start => :type (Point);
		has $radius;
		has $angle;

		method move extends ${:start}::move;
	}

Example: move method of vector is move of both its point

	class Vector::Polar {
		has ($start, $end) => :type (Point);

		# Variant - multiple "inheritance"
		method move extends ${:start}::move, ${:end}::move;

		# Variant - bind using slot constraint
		method move {
			has $dx :typeof (${: / Point / move / dx });
			has $dy :typeof (${: / Point / move / dy });

			${:start}->move;
			${:end}->move;
		}
	}

Idea - Internal slots

There should be also possibility to define internal slots so developer can apply every declaration capability (traits, dependencies, multisub) on entity which is not publicly exposed.

Such slot still should be queried (makes sense at least in tests)

Example (how it may look like):

	sub foo {
		has $argument;
		my has $internal;
	}

Idea - Block slots

Slot mechanism on subs can be used by any block as well, for example in foreach cycle it can be used to iterate over multiple items at once

Example: As far as block has two slots, iterate over two elements at once

	foreach (@list) {
		has $foo; # $list[0], $list[2], ...
		has $bar; # $list[1], $list[3], ...
	}

Example: Conditional slots (just mentioning possibility)

	foreach (qw[ aaa --bbb ccc ) {
		has $foo; # aaa (iteration 1) and --bbb (iteration 2)
		if ($foo =~ m/^--) next;
		has $bar; # ccc (iteration 2)
	}

Idea - Thunk / lazy values

In functional programming thunk describes a variable which value is not computed until required.

Slot's default value can provide such mechanism if return SLOT will return slot by reference and not by value.

Example (how it may look like)

	sub connection {
		has $username;
		has $password;
		my has $connection
			:default { Resource->connect (${:username}, ${:password}) }
			;

		return $connection;
	}

	my $connection = connection; # still not connected
	random_sub ($connection);    # still not connected - argument not used
	$connection->request (...)   # connects before calling method

Idea - Multisubs

Based on an expectation that every slot has unique name we can treat it as unique constraint as well (in context of multisubs).

All we need is a query to check whether slot is provided

  • can be provided with undef
  • can be provided via thunk

Example (how it may look like):

	when (has? $slot1 and has? $slot2) { ... }

Combined with default values (how it may look like):

	 my has $foo
		:default
			:when (has? slot1) { ... }
			:when (has? slot2 and ${:slot2} > 0) { ... }
			:otherwise { ... }
		;

Single argument providing multiple internal slots (how it may look like):

	has $foo
		:when (isa Foo) :provides { $slot1 }
		:when (isa Bar) :provides { $slot2 via ->build } :provides { $slot1 via 10 }
		;

Idea - Slot dependencies

Availability of slots may depend on external conditions (how it may look like):

	sub foo {
		has $email
			:when (${: / API / version } == 1) { :required }
			:otherwise :provides { ${:username} }
			;
		has $username =>
			when (${: / API / version } == 1) { :not-available }
			otherwise { :required }
			;
	}

Idea - Lifecycle management

As your code base as well as used libraries evolve, some parts may become deprecated or security risks (hm, nice term possible "security injection").

For example, github API. Although it supports login / password you want to prohibit them and use only token based authentication.

Your code may then look like:

	local ${: / Github / username } := :disabled;
	local ${: / Github / password } := :disabled;

	use Github;

	# code will die
	my $connection = Github->new (
		username := ...,
		password := ...,
	);

Similary there can be a deprection mechanism:

	class Github v3.0 {
		${: / Github / username } := :deprecated (since => v2.0);
	}

Followup - Extended OOP

Based on fact that

  • every information has its own type
  • there are no two instances of same type in processing context

we can extend currying on class definitions as well.

Example (how it may look like):

class Country { }
class Country::City { has $country, isa => 'Country' }

my $CZ = Country->new ();

class CZ::City extends Country::City, $CZ { }

my $Prague = CZ::City->new;

Follow-up - Context oriented programming

Described extended usage of slots allows perl to provide decent support for context oriented programming.

Additional features:

  • data frame - capability to specify new context
    • including hierarchy
  • write once - slot value can be populated only once

Limited COP is already provided:

  • global / class / class instance - acts like data frames
  • local - acts like creation of data frame
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment