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
Kleenestar;init_planwilldieafter it compiles the PRE into a min DFA. It checks this using theFLAT::DFA::is_finitesubroutine, 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
newbut calling theplan_neinmethods. 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 callingnewon the instance without having to recapture it. init_plan-
This takes the PRE provided in the
newconstructure, 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
scopeas 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,
scopeis 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
nextis 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 thestatekeyword. See more aboutPERL's UNIPROCESS MEMORY MODEL AND ITS EXECUTION ENVIRONMENTin 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.
$scopeis 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_scopesAt this time
Sub::Geniusonly 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: 42A formulation of 26 subroutines operating over shared memory in which all cooperative execution of all 26! orderings reduces to
42is 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.