Skip to content

Commit

Permalink
Merge pull request #379 from perladvent/2023-12-17
Browse files Browse the repository at this point in the history
2023-12-17
  • Loading branch information
oalders authored Dec 16, 2023
2 parents 34ebfbe + d394c88 commit eee5c1f
Show file tree
Hide file tree
Showing 2 changed files with 383 additions and 404 deletions.
383 changes: 383 additions & 0 deletions 2023/articles/2023-12-17.pod
Original file line number Diff line number Diff line change
@@ -0,0 +1,383 @@
Author: oodler@cpan.org
Title: Sequentially Consistent Santa
Topic: Sub::Genius

=encoding utf8

Santa is finally introducing automation into his toy making operation,
and this largely manifests itself as a large, black box that he or his
elves are supposed to program with the build steps.

These magic boxes are programmed to follow a specific set of steps using
a domain specific language (DSL) provided for by the mysterious makers
of the boxen.

Nobody really knows how they work, not even Santa. Or so he claims.
But provided they are able to trust the plan they give the machine,
all is good in the North Pole and everyone gets their fill of eggnog.

Working with Lucas, his chief programmer elf, Santa wrote down the list
of steps that he's perfected by manually building bikes over the last
century. These instructions are superficially clear, but because there
were lots of dependent steps, it was not so clear at all how they were
going specify the proper ordering.

The steps that he gave his chief programming elf, Lucas, consisted of the
following plan for building the perfect bike:

=over 4

=item 1. the bike frame must be forged, welded, and painted

=item 2. the rear wheel may be attached to the frame once #1 is complete

=item 3. the front wheel may be attached to the frame once #1 is complete

=item 4. the seat may be added at any time after #1

=item 5. handle bars must be attached before the front wheel (#3) is attached

=item 6. the front brakes must be added after the front wheel (#3) is added

=item 7. the rear brakes must be added after the rear wheel is added in #2

=item 8. pedals and gears can be added at any time after #1

=item 9. the chain must be added after the pedals and rear wheel (#8)

=item 10. send bike to Buddy the Elf for testing

=back

The work plans are fed into the box using the DSL format that is then read
and processed by an internal module called C<Sub::Genius>. A module aptly
named since its original author is certainly not the brightest bulb in the
shed. But the module is a good example of the many useful tools that exist
on the Internet.

After being processed by L<Sub::Genius>, the plan is turned into a set of
actionable steps that the machine blindly follows. If the plan is correct,
the end result will be a bicycle.

The plan is thusly:

ForgeBike
InitialPaint
(
( ( RearWheel & Pedals & Gears ) ( RearBrakes & Chain))
&
( Seat)
&
( HandleBars FrontWheel FrontBrakes )
)
Buddy2TestBike

So the elves do not need to list out every possible correct ordering, only
ensure that dependent steps are expressed using a nested form that resemble
a tree (similar in a lot of ways to I<LISP>-y I<S-expressions>).

Given the program, the magic box provides a way to serialize the execution
of the plan into a sequential series of steps, all by using a Perl script:

#!perl
use strict;
use warnings;
use Sub::Genius;

my $plan =<<EOP;
ForgeBike
InitialPaint
(
( ( RearWheel & Pedals & Gears ) ( RearBrakes & Chain))
&
( Seat)
&
( HandleBars FrontWheel FrontBrakes )
)
Buddy2TestBike
EOP

my $sg = Sub::Genius->new(preplan => $plan)->init_plan;

# print all sequential orderings of the steps described above
while (my $preplan = $sg->next()) {
print qq{$preplan\n};
}

When run, this prints all 588 possible correct orders of operations. A
sequential set of steps is I<correct> if it respects the partial ordering
among dependent steps described in the plan above.

For example, C<HandleBars> must always be installed before the
C<RearBrakes>. But the C<Pedals> may be installed before or after the
C<FrontBrakes>. And in this way all independent tasks may be interleaved,
i.e., may occur in any order; leaving the dependent steps occuring in the
order in which they are constrained.

For example, some of the 588 possible correct orderings follow:

ForgeBike InitialPaint Pedals Gears RearWheel HandleBars FrontWheel Chain RearBrakes Seat FrontBrakes Buddy2TestBike
ForgeBike InitialPaint Pedals Gears RearWheel HandleBars FrontWheel Chain RearBrakes FrontBrakes Seat Buddy2TestBike
ForgeBike InitialPaint Pedals Gears RearWheel Chain HandleBars RearBrakes FrontWheel Seat FrontBrakes Buddy2TestBike
ForgeBike InitialPaint Pedals Gears RearWheel Chain HandleBars RearBrakes FrontWheel FrontBrakes Seat Buddy2TestBike
ForgeBike InitialPaint Pedals Gears RearWheel Chain HandleBars RearBrakes Seat FrontWheel FrontBrakes Buddy2TestBike
ForgeBike InitialPaint Pedals Gears RearWheel Chain HandleBars FrontWheel RearBrakes Seat FrontBrakes Buddy2TestBike
ForgeBike InitialPaint Pedals Gears RearWheel Chain HandleBars FrontWheel RearBrakes FrontBrakes Seat Buddy2TestBike
...
ForgeBike InitialPaint HandleBars Gears FrontWheel RearWheel Pedals RearBrakes Chain Seat FrontBrakes Buddy2TestBike
ForgeBike InitialPaint HandleBars Gears FrontWheel RearWheel Pedals RearBrakes Chain FrontBrakes Seat Buddy2TestBike
ForgeBike InitialPaint HandleBars Gears FrontWheel RearWheel Pedals Chain RearBrakes Seat FrontBrakes Buddy2TestBike
ForgeBike InitialPaint HandleBars Gears FrontWheel RearWheel Pedals Chain RearBrakes FrontBrakes Seat Buddy2TestBike
ForgeBike InitialPaint HandleBars Gears FrontWheel Pedals RearWheel RearBrakes Chain Seat FrontBrakes Buddy2TestBike
ForgeBike InitialPaint HandleBars Gears FrontWheel Pedals RearWheel RearBrakes Chain FrontBrakes Seat Buddy2TestBike
ForgeBike InitialPaint HandleBars Gears FrontWheel Pedals RearWheel Chain RearBrakes Seat FrontBrakes Buddy2TestBike
ForgeBike InitialPaint HandleBars Gears FrontWheel Pedals RearWheel Chain RearBrakes FrontBrakes Seat Buddy2TestBike

It would still be useful, one may suppose, if Lucas could only enumerate
the steps in a properly ordered way. But alas, C<Sub::Genius> is far more
useful than that!

It so happens that if the C<sub>s with the same names as the I<steps> of the
plan are defined within the scope, then the plan may be actually executed. This
allows for each step in the plan to have actions associated with them (even
generating additional nested plans).

Because creating scripts to be executed according to the plan is a tedious
task, one of the useful tools L<Sub::Genius> provides is called C<stubby>.
This utility can be used to I<stub> another Perl script with the C<sub> already
in place, just waiting to be filled with useful purpose.

E.g., if the I<plan> above is saved to a file, let's call it C<./bike.plan>,
we're able to generate a Perl script that contains subroutine stubs for each of
the steps that appear in the plan at least once.

shell> stubby init -f ./bikes.plan --run once > bikes.pl

The file C<bikes.pl> is now filled with subroutines stubs that are waiting to
be filled in with meaningful code, the script will look something like the following:

#!perl
use strict;
use warnings;
use Sub::Genius;

my $plan = <<EOP;
ForgeBike
InitialPaint
(
( ( RearWheel & Pedals & Gears ) ( RearBrakes & Chain))
&
( Seat)
&
( HandleBars FrontWheel FrontBrakes )
)
Buddy2TestBike
EOP

my $sg = Sub::Genius->new( preplan => $plan )->init_plan;

my $final_scope = $sg->run_once;

sub ForgeBike {
my $scope = shift;
print qq{Bike forged!\n};
return $scope;
}

sub InitialPaint {
my $scope = shift;
print qq{Bike painted!\n};
return $scope;
}

sub HandleBars {
my $scope = shift;
print qq{Handle bars on!\n};
return $scope;
}

sub Gears {
my $scope = shift;
print qq{Gears on!\n};
return $scope;
}

sub RearWheel {
my $scope = shift;
print qq{Rear wheels on!\n};
return $scope;
}

sub Pedals {
my $scope = shift;
print qq{Pedals on!\n};
return $scope;
}

sub RearBrakes {
my $scope = shift;
print qq{Rear brakes on!\n};
return $scope;
}

sub FrontWheel {
my $scope = shift;
print qq{Front wheels on!\n};
return $scope;
}

sub Chain {
my $scope = shift;
print qq{Chain on!\n};
return $scope;
}

sub FrontBrakes {
my $scope = shift;
print qq{Front brakes on!\n};
return $scope;
}

sub Seat {
my $scope = shift;
print qq{Seat on!\n};
return $scope;
}

sub Buddy2TestBike {
my $scope = shift;
print qq{Ready to test, Buddy!\n};
return $scope;
}

And each time it's run, the overall ordering may change, but the ordering
will always be correct based on the plan description:

shell> perl bike.pl

Bike forged!
Bike painted!
Handle bars on!
Front wheels on!
Pedals on!
Rear wheels on!
Gears on!
Rear brakes on!
Chain on!
Seat on!
Front brakes on!
Ready to test, Buddy!

$ perl bike.pl
Bike forged!
Bike painted!
Pedals on!
Handle bars on!
Front wheels on!
Gears on!
Rear wheels on!
Rear brakes on!
Chain on!
Front brakes on!
Seat on!
Ready to test, Buddy!

$ perl bike.pl
Bike forged!
Bike painted!
Pedals on!
Rear wheels on!
Gears on!
Chain on!
Handle bars on!
Rear brakes on!
Seat on!
Front wheels on!
Front brakes on!
Ready to test, Buddy!

Note: it is worth looking more into the raw stub file created since it
give some nice hints about all the interesting things one may do by giving
the C<sub>s C<state> and passing along a C<$scope> reference from one C<sub>
to another, as the sequential form of the plan is being executed.

And to make a very long story short, Perl saved Yet Another Christmas!

B<Prologue>

Lucas ended up having such a joyful time working with coding the plan
of the steps for creating the bikes, that he left us this nice little
program as a parting gift.

But don't try to run it until the clock strikes midnight on December 25!

#!perl
use strict;
use warnings;
use feature 'state';

use Sub::Genius ();

my $preplan = q{
(
HO &
HO &
HO
)
Merry Christmas To All
};

my $sq = Sub::Genius->new(preplan => qq{$preplan} );
$sq->init_plan;
my $final_scope = $sq->run_once( scope => {}, ns => q{main}, verbose => 0);

#
# S U B R O U T I N E S
#

sub HO {
state $mystate = 0;
if ($mystate == 0) {
print qq{Merry Christmas to All,\nAnd };
++$mystate;
}
elsif ($mystate == 1) {
print qq{to };
++$mystate;
}
elsif ($mystate == 2) {
print qq{All, };
++$mystate;
};
}

sub Merry {
print qq{A };
}

sub Christmas {
print qq{Good };
}

sub To {
print qq{Night};
};

sub All {
print qq{!\n};
};

The story does not end here, but it is time to conclude this chapter. Santa
and Lucas successfully programmed the magick black box, and countless
children received their bikes. A lucky few received a scooter and fez
cap instead, but that's a story for a different Advent.

=over 4

=item L<https://metacpan.org/pod/Sub::Genius> - includes info on how this all works

=item L<https://metacpan.org/pod/FLAT> - processing engine

=item L<https://en.wikipedia.org/wiki/S-expression>

=back
Loading

0 comments on commit eee5c1f

Please sign in to comment.