2

I am trying to make a state machine in Perl. To do this I have an array indexed by statenames. I can put subs into this array. Like this:

   use constant {
    stInit          => 0,
    stHeader        => 1,
    stSalesHeader   => 2,
    stCatagory      => 3,
    stData          => 4,
    stTotal         => 5,
    stError         => 6, 
};

my $state = stInit;
my @actions;

$actions[stInit] = [sub{logState("Initial state entered",2) }];
$actions[stHeader] = [sub{logState("Header state entered",2) }];
$actions[stSalesHeader] = [sub{logState("Sales header state entered",2) }];
$actions[stCatagory] = [sub{logState("Category state entered",2) }];
$actions[stData] = [sub{logState("Data state entered",2) }];
$actions[stTotal] = [sub{logState("Total state entered",2) }];

But then I have no Idea how to call the subroutine. I have tried this

$actions[$state]

But that appears not to work. Is this possible or am I completely off?

5 Answers 5

9

You really should add

use strict;
use warnings;

to the start of your code, which will find many simple mistakes for you. In this case your code is fine, and you can call your subroutines by using

$actions[$state][0]();

etc.

But there is no need to put the subroutines within square brackets, which just creates a one-element anonymous array and adds an additional level of indexing (hence the [0] in the above line of code. If you wrote code like this instead

$actions[stInit] = sub { logState("Initial state entered", 2) };

then you would be able to call the subroutines with

$actions[$state]();
Sign up to request clarification or add additional context in comments.

7 Comments

Using () nicely avoids accidentally inheriting @_ as well, using & is a bad habit IMO.
Indeed. The only non-esoteric use for & is to access a reference to a subroutine using \&mysub. It has been this way for many years.
@Borodin, Like $ref->(), &$ref() doesn't inherit @_ either.
@Borodin, & is also useful for disabling prototypes. (e.g. &async($sub)).
@Borodin, & is also used in goto ⊂, defined &sub and exists &sub.
|
2

On an a slightly different note, have you considered using FSA::Rules to write your state machine? It's fairly powerful, has optional GraphViz output and makes state machines rather easy to write.

Comments

1

You should do:

&{$actions[$state][0]}

but I'm not sure why you use an array... If you have just 1 function then

$actions[stData] = sub{ ... }
...
&{$actions[$state]}

will work. If you really want to execute many functions and use the array, then you can do:

map { &{$_}  } @{$actions[$state]};

1 Comment

Yet again, as for the other two answers, use $actions[$state]() in all but very old versions of Perl. Also you are committing the sin of using map for its side-effects and discarding the result. If you want to do this then you just need $_->() foreach @{$actions[$state]};
1

To call a subroutine from a reference:

&{$actions[$state]}();

However, based on your code, @actions does not contain subroutine references, but array references to the declaration of the subroutine.

first, declare the subs as you normally would and then build @actions:

$actions[0] = \&stInit;
$actions[1] = \&stHeader;
...and so on

1 Comment

The use of an ampersand when calling subroutines is old-fashioned and very bad practice. Just $actions[$state]() is fine.
1

Drop the extraneous anonymous array creation by removing the square brackets

$actions[stInit] = sub{logState("Initial state entered",2) };

You can then call the action with

$actions[stInit]();

If you have an action stored in a variable, e.g.

my $action = $actions[$actionID];

then you'll need a bit more syntax to make it actually do the call

$action->();

Then again, you could just use a hash instead of an array

my %actions = (
    stInit        => sub { logState("Initial state entered",2) },
    stHeader      => sub { logState("Header state entered",2) },
    stSalesHeader => sub { logState("Sales header state entered",2) },
    stCatagory    => sub { logState("Category state entered",2) },
    stData        => sub { logState("Data state entered",2) },
);

which would save you from having to set up constants at the top. You could then call actions with

$actions{$state}();

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.