NAME

Function::Runner - Define functions at a higher level and run them

SYNOPSIS

use Function::Runner;

# Hello World
sub greet {
    print "Hello ". ($_[0] || 'World') ."\n";
    return ('ok',$_[0]);
}

my $defn = {                              # Definition is just a hashref
  '/hello' => '&greet'                    #   The /hello step,
};                                        #     calls the &greet function

my $fn = Function::Runner->new($defn);    # Create a greeter
$fn->run('/hello','Flash');               # Hello Flash


my $switch = {                            # Define a switch
  '/checkSwitch' => {
      'run'  => '&checkSwitch',           # Check the switch
      ':on'  => '&bye',                   #   If it's on, leave
      ':off' => '/turnOn',                #   If it's off, turn it on
  },
  '/turnOn'  => {                         # Turn on the switch
      'run'  => '&greet',                 #   Greet the caller
      ':ok' => '/turnOff',                #   Then turn off the switch
  },
  '/turnOff' => '&bye',                   # Turn off the switch and leave
};
sub bye {
  print "Bye ". ($_[0] || 'World') ."\n";
  return ('ok',$_[0]);
}
sub checkSwitch { return @_ }

$fn = Function::Runner->new($switch);     # Create a switch
$fn->run('/checkSwitch', 'on', 'Flash');  # Bye Flash

$fn->run('/checkSwitch', 'off', 'Hulk');  # Hello Hulk
                                          # Bye Hulk

say join ' ', @$_ for @{$fn->steps};      # List steps, function and result
                                          #   /checkSwitch &checkSwitch :off
                                          #   /turnOn &greet :ok

DESCRIPTION

Function::Runner provides a way to define the steps of a function and the logical flow between the steps using just hashrefs. The user then implements the steps that need to be called. The function runner will then run the function.

This module is handy for functions that are naturally composed of many hierarchical steps and flows differently depending on the results of those steps. The function definition helps to clarify the steps and flow at a higher level.

A function definition (funcdef) is composed of three (3) constructs: steps, functions and results. Each construct is a string with a different character prefix to indicate the kind of construct:

/a_step         # Steps are prefixed with /, like directories

&a_function     # Functions prefixed with &, like Perl

:some_result    # Results prefixed with :

The keys of the funcdef hashref is always a step. The value of the funcdef hashref is the step definition (stepdef) defines how that step is to be executed.

A stepdef can be just a function if no further steps follow. For example:

{ '/hello' => '&greet' }

A stepdef can also be a hashref that defines the function to run and the next step to take depending on the results of that function run. For example:

'/checkSwitch' => {
    'run'  => '&checkSwitch',           # Check the switch
    ':on'  => '&bye',                   #   If it's on, leave
    ':off' => '/turnOn',                #   If it's off, turn it on
},

The next step can either be a function:

':on'  => '&bye'            # On "on" result, call the &bye function

or it can be another step:

':off' => '/turnOn'         # On "off" result, run the /turnOn step

METHODS

run($step,@args)

The run() method runs the given $step, checks the results, looks up the function definition to determine the next step to run an calls that until there is nothing left to be done at which point it will return the result of the last function that was called.

Along the way it tracks how deep it is within the function definition. Each step that was ran and the corresponding result is stored in an array.

steps()

The steps() method returns the steps that ran for that function.

NAMING CONVENTIONS

Steps and Functions with the form Verb + Object is rather pleasing to read. For example:

&findStalledWorkers

/find_stalled_workers

Results with the form Object + State or Object + Adjective is also rather pleasing to read. For example:

:queueEmpty

:not_found

Taken together, the step and it's results end up reading like this:

/find_stalled_workers :not_found

NOTES

Defining a function in terms of steps, functions and results has several nice properties.

The valid return values from each function is clearly spelled out.

Having a funcdef makes it easier rearrange the flow of steps within that function or add an additional step in the function's processing.

It is possible to directly call the steps in a function definition.

It is possible to analyze the funcdef hashref to create a reverse dependency graph so that when a function is about to be changed, find all it's dependents.

All in all, it is a rather nice way to design these kinds of hierarchical, multi-step functions where the flow depends on the results of prior steps.

AUTHOR

Hoe Kit CHEW <hoekit@gmail.com>

COPYRIGHT

Copyright 2021- Hoe Kit CHEW

LICENSE

This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.