NAME
Sub::Genius - module for managing concurrent Perl
semantics in the uniprocess execution model of perl
.
Another way to say this, is that it introduces all the joys and pains of multi-threaded, shared memory programming to the uniprocess environment that is perl
.
One final way to say it, if we're going to fake the funk out of coroutines [1], let's do it correctly. :)
THIS MODULE IS EXPERIMENTAL
Until further noted, this module subject to extreme fluxuations in interfaces and implied approaches. The hardest part about this will be managing all the cool and bright ideas stemming from it.
STATIC CODE STUB GENERATION TOOL
Eventually this module will install a tool called stubby
into your local $PATH
. For the time being it is located in the ./bin
directory of the distribution and on Github.
SYNOPSIS
my $pre = q{( A B & C D ) Z};
# \ / \ / |
#>>>>>>>>>>> L1 <shuff> L2 <cat> L3
my $sq = Sub::Genius->new(pre => $pre);
# sub declaration order has no bearing on anything
sub A { print qq{A} }
sub B { print qq{B} }
sub C { print qq{C} }
sub D { print qq{D} }
sub Z { print qq{\n}}
$sq->run_once();
print qq{\n};
The following expecity execution of the defined subroutines are all valid according to the PRE description above:
# valid order 1
A(); B(); C(); D(); Z();
# valid order 2
A(); C(); B(); D(); Z();
# valid order 3
A(); C(); D(); B(); Z();
# valid order 4
C(); A(); D(); B(); Z();
# valid order 5
C(); D(); A(); B(); Z();
In the example above, using a PRE to describe the relationship among subroutine names (these are just multicharacter symbols
); we are expressing the following constraints:
Sub::Genius
uses FLAT
's functionality to generate any of a number of valid "strings" or correct symbol orderings that are accepted by the Regular Language described by the shuffle
two regular languages.
Meaningful Subroutine Names
FLAT
allows single character symbols to be expressed with out any decorations;
my $pre = q{ s ( A (a b) C & D E F) f };
The concatentaion of single symbols is implied, and spaces between symbols doesn't even matter. The following is equivalent to the PRE above,
my $pre = q{s(A(ab)C&DEF)f};
It's important to note immediately after the above example, that the PRE may contain symbols
that are made up of more than one character. This is done using square brackets ([...]
), e.g.:
my $pre = q{[s]([A]([a][b])[C]&[D][E][F])[f]};
But this is a mess, so we can use longer subroutine names as symbols and break it up in a more familar way:
my $pre = q{
[start]
(
[sub_A]
(
[sub_a]
[sub_b]
)
[sub_C]
&
[sub_D]
[sub_E]
[sub_F]
)
[fin]
};
This is much nicer and starting to look like a more natural expression of concurrent semantics.
PERL
's UNIPROCESS MEMORY MODEL AND ITS EXECUTION ENVIRONMENT
While the language Perl
is not necessarily constrained by a uniprocess execution model, the runtime provided by perl
is. This has necessarily restricted the expressive semantics that can very easily be extended to DWIM
in a concurrent execution model. The problem is that perl
has been organically grown over the years to run as a single process. It is not immediately obvious to many, even seasoned Perl programmers, why after all of these years does perl
not have real threads or admit real concurrency and semantics. Accepting the truth of the uniprocess model makes it clear and brings to it a lot of freedom. This module is meant to facilitate shared memory, multi-process reasoning to perl
's fixed uniprocess reality.
Atomics and Barriers
When speaking of concurrent semantics in Perl
, the topic of atomic primatives often comes up, because in a truly multi-process execution environment, they are very important to coordinating the competitive access of resources such as files and shared memory. Since this execution model necessarily serializes parallel semantics in a sequentially consistent
way, there is no need for any of these things. Singular lines of execution need no coordination because there is no competition for any resource (e.g., a file, memory, network port, etc).
RUNTIME METHODS
A minimal set of methods is provided, more so to not suggest the right way to use this module.
new
-
Constructor, requires a single scalar string argument that is a valid PRE accepted by FLAT.
my $pre = q{ [start] ( [subA] ( [subB_a] [subB_b] ) [subC] & [subD] [subE] [subF] ) [finish] }; my $sq = Sub::Genius->new(pre => $pre);
Note: due to the need to explore the advantages of supporting infinite languages, i.e., PREs that contain a
Kleene
star;init_plan
willdie
after it compiles the PRE into a min DFA. It checks this using theFLAT::DFA::is_finite
subroutine, which simply checks for the presence of cycles. Once this is understood more clearly, this restriction may be lifted. This module is all about correctness, and only finite languages are being considered at this time.The reference, if captured by a scalar, can be wholly reset using the same parameters as
new
but calling theplan_nein
methods. It's a minor convenience, but one all the same. plan_nein
-
Using an existing reference instantiation of
Sub::Genius
, resets everything about the instance. It's effectively link callingnew
on the instance without having to recapture it. init_plan
-
This takes the PRE provided in the
new
constructure, and runs through the conversion process provded by FLAT to an equivalent mininimzed DFA. It's this DFA that is then used to generate the (currently) finite set of strings, or plans that are acceptible for the algorithm or steps being implemented.my $pre = q{ [start] ( [subA] ( [subB_a] [subB_b] ) [subC] & [subD] [subE] [subF] ) [finish] }; my $sq = Sub::Genius->new(pre => $pre); $sq->init_plan;
run_once
-
Returns
scope
as affected by the assorted subroutines.Accepts two parameters, both are optional:
- ns => q{My::Subsequentializer::Oblivious::Funcs}
-
Defaults to
main::
, allows one to specify a namespace that points to a library of subroutines that are specially crafted to run in a sequentialized environment. Usually, this points to some sort of willful obliviousness, but might prove to be useful nonetheless. - scope => {}
-
Allows one to initiate the execution scoped memory, and may be used to manage a data flow pipeline. Useful and consistent only in the context of a single plan execution. If not provided,
scope
is initialized as an empty anonymous hash reference:my $final_scope = $sq->run_once( scope => {}, verbose => undef, );
- verbose => 1|0
-
Default is falsy, or off. When enabled, outputs arguably useless diagnostic information.
Runs the execution plan once, returns whatever the last subroutine executed returns:
my $pre = join(q{&},(a..z)); my $sq = Sub::Genius->new(pre => $pre); $plan = $sq->init_plan; my $final_scope = $sq->run_once;
next
-
FLAT provides some utility methods to pump FAs for valid strings; effectively, its the enumeration of paths that exist from an initial state to a final state. There is nothing magical here. The underlying method used to do this actually creates an interator.
When
next
is called the first time, an interator is created, and the first string is returned. There is currently no way to specify which string (orplan
) is returned first, which is why it is important that the concurrent semantics declared in the PRE are done in such a way that any valid string presented is considered to be sequentially consistent with the memory model used in the implementation of the subroutines. Perl provides the access to these memories by use of their lexical variable scoping (my
,local
) and the convenient way it allows one to make a subroutine maintain persistent memory (i.e., make it a coroutine) using thestate
keyword. See more aboutPERL's UNIPROCESS MEMORY MODEL AND ITS EXECUTION ENVIRONMENT
in the section above of the same name.An example of iterating over all valid strings in a loop follows:
while (my $plan = $sq->next_plan()) { print qq{Plan: $plan\n}; $sq->run_once; }
Note, in the above example, the concept of pipelining is violated since the loop is running each plan ( with no guaranteed ordering ) in turn.
$scope
is only meaningful within each execution context. Dealing with multiple returned final scopes is not part of this module, but can be captured during each iteration for future processessing:my @all_final_scopes = (); while (my $plan = $sq->next_plan()) { print qq{Plan: $plan\n}; my $final_scope = $sq->run_once; push @all_final_scopes, { $plan => $final_scope }; } # now do something with all the final scopes collected # by @all_final_scopes
At this time
Sub::Genius
only permits finite languages, therefore there is always a finite list of accepted strings. The list may be long, but it's finite.As an example, the following admits a large number of orderings in a realtively compact DFA, in fact there are 26! (factorial) such valid orderings:
my $pre = join(q{&},(a..z)); my $final_scope = Sub::Genius->new(pre => $pre)->run_once;
Thus, the following will take long time to complete; but it will complete:
my $ans; # global to all subroutines executed while ($my $plan = $sq->next_plan()) { $sq->run_once; } print qq{ans: $ans\n};
Done right, the output after 26! iterations may very well be:
ans: 42
A formulation of 26 subroutines operating over shared memory in which all cooperative execution of all 26! orderings reduces to
42
is left as an excercise for the reader. run_any
-
For convenience, this wraps up the steps of
plan
,init_plan
,next
, andrun_once
. It presents a simple one line interfaces:my $pre = q{ [start] ( [subA] ( [subB_a] [subB_b] ) [subC] & [subD] [subE] [subF] ) [finish] }; Sub::Genius->new(pre => $pre)->run_any();
STATIC CODE UTILITY METHODS
The stubby
utility is provided for this purpose and is not part of the main module.
SEE ALSO
Pipeworks, Sub::Pipeline, Process::Pipeline, FLAT, Graph::PetriNet
GOOD READINGS
2. Leslie Lamport, "How to Make a Multiprocessor Computer That Correctly Executes Multiprocess Programs", IEEE Trans. Comput. C-28,9 (Sept. 1979), 690-691.
3. https://www.hpl.hp.com/techreports/Compaq-DEC/WRL-95-7.pdf
COPYRIGHT AND LICENSE
Same terms as perl itself.
AUTHOR
OODLER 577 <oodler@cpan.org<gt>
ACKNOWLEDGEMENTS
TEODESIAN is acknowledged for his support and interest in this project, in particular his work lifting the veil off of what passes for concurrency these days; namely, most of the "Async" modules out there are actually fakin' the funk with coroutines.. See https://troglodyne.net/video/1615853053 for a fun, fresh, and informative video on the subject.