Deep_Plaid has asked for the
wisdom of the Perl Monks concerning the following question:

I have run across a situation where I need a Switch statement. After reading up on this online, it is my understanding that there really is no native Switch statement for PERL, and for later versions you should use the given/when construct (I'm using PERL v5.18.1 for Windows).

The problem is I can't seem to get the syntax right, even when using a dumbed down example I got from the web. Here's the code I'm trying to run:

The switch (given/when) feature is an experimental feature that should probably be avoided.

From the Perl documentation ( http://perldoc.perl.org/perlsyn.html#Experimental-Details-on-given-and-when) : As previously mentioned, the "switch" feature is considered highly experimental; it is subject to change with little notice. In particular, when has tricky behaviours that are expected to change to become less tricky in the future. Do not rely upon its current (mis)implementation. Before Perl 5.18, given also had tricky behaviours that you should still beware of if your code must run on older versions of Perl.

It is probably not wise to use this feature for anything else than a one-off program that you know will never have to be used again in the future, as your syntax might break in the next Perl version.

Using an array is probably the simplest alternative for your very simple case:

For more complicated things, such as taking actions on the basis of the value of a variable, you might want to use a dispatch table (essentially a hash in which the values are references to subs). For example, the following is using a long list of if /elsif expressions to determine what series of actions to do depending on some parameters received as arguments:

If even that is not OK, then you can also go for either the for/if constructs referred to in the link provided by LanX above (http://www.perlmonks.org/?node_id=826710) or, if worse come to worse, to the series of if/elsif.

Hi Laurent. These are all excellent examples - I think in my case I can get away with the array construct you demonstrated, but I will experiment to see if the hash is more appropriate. I also appreciate your advice on dealing with new features like this one - very good to know. Thanks so much for taking the time - this is great stuff! Cheers, DP.

Hi WW. I did read the manual but did not read it thoroughly enough - you are correct (serves me right - I'm constantly admonishing others for not reading my own documentation carefully enough). When I declare my PERL version, the script does execute, although with "experimental" warnings. This is a good enough reason to avoid it.

That being said, do you have any advice on the best approach from a performance standpoint? Thanks for your input - it's greatly appreciated!"

That need is of your own creation. There is no situation where the lack of a switch statement will prohibit the project from moving forward with efficiency and clarity. Perl5 was a dozen years old before it got a switch statement (called given/when), and the addition of that syntax didn't make any application of the Perl language that previously would have been impossible suddenly become possible. In fact, Perl's switch statement has been mostly repealed (or flagged as experimental) in modern releases of Perl.

given/when is a programmer convenience, not a language necessity. Use if/elsif/else, or a hash table filled with code refs, or if you're concerned about efficiency, use an array-table filled with coderefs, and assign constants that refer to the elements. Even if you're trying to make your decision based on pattern matching, that's not difficult:

In this example I'm populating an array with a series of patterns and actions to take if a given pattern matches. I have control over which match is tested first, because I'm using an array for the dispatch table. And I have control over whether to continue on to the next test, or abort upon success by the use (or omission) of last DISPATCH. I can even add a default by including a regex test as the last element in @dispatch that will always succeed.

Is it more clear than a series of if/elsif/else statements? I guess that depends on how many tests are being conducted. If it's just a small few, chained if/elsif's are probably clearer. If there are a lot of options, a table works well.

If you make the first element a subref instead of a regexp object, you have even more control, and a more generalized solution.

Hi Dave. Thank you very much for the examples and detailed explanations you provided. This was exactly what I was looking for, and although I didn't ask directly, I thought there must be a good reason why PERL doesn't have a native switch and your explanation makes sense in this regard. I greatly appreciate your input! DP

While hashes in other languages are more expensive computationally, a hash lookup in Perl costs the same as an array lookup. Efficiency advantages of arrays manifest when using shift/unshift/push/pop. This is explicitly stated in 'Learning Perl.'

I've got the 2nd and 6th edition of Learning Perl, and haven't been able to find anywhere in either edition where the book states that the efficiency advantages of arrays versus hashes manifest themselves when using shift/unshift/push//pop. Nor have I found anywhere in the book that states that a hash lookup in Perl costs the same as an array lookup. If you know of somewhere in that book I should be reading, please provide the exact quote, along with which edition I might find it in.

I think you're probably referring to this paragraph:

Some implementations of hashes (such as in the original awk language, where Larry
borrowed the idea from) slow down as the hashes get larger and larger. This is not the
case in Perl—it has a good, efficient, scalable algorithm. So, if a hash has only three
key-value pairs, it’s very quick to “reach into the barrel” and pull out any one of those.
If the hash has three million key-value pairs, it should be just about as quick to pull out
any one of those. A big hash is nothing to fear.

That paragraph makes an assertion that the computational complexity of hash inserts or lookups is approximately constant, as the size of the hash grows toward infinity. And the same could be said of arrays. But order of growth in computational complexity says nothing about the amount of computational complexity there is that stays constant as the data set grows.

To understand this concept, look at the following two simple scenarios:

Find the numeric value 100 in an unsorted array of unique integers: We know that as the array grows, the growth in computational complexity is linear.

Find the string "David" in an unsorted array of strings.: We know that as the array grows, the growth in computational complexity is linear.

So those are both O(n) searches. However, to find numeric 100, we do a series of numeric comparisons while iterating over the elements of the array. To find "David", we must go to the first element and see if the first character in the first element is a "D". If it is, then we compare the second character and see if it is an "a". Each letter of the string results in another char comparison... essentially an integer comparison for each character of the string (short-circuiting as soon as there's a mismatch)... repeated for each element in the data set.

So it's pretty easy to see in this example that the string comparisons have an overhead per element that doesn't exist for the numeric comparisons.

Now look at what goes on in hash lookups versus array lookups. To look up a single element in an array, internally, Perl must first start with the base address of the array. Then it must add an integral offset to the array's base address. This offset will be the element number times the size of the element's structure. This is a simple mathematical calculation; base + offset * size. Once that calculation is performed, Perl can dereference a pointer contained in the element, and fetch the element's scalar entity.

Now for a Perl hash. First Perl must examine the string of some arbitrary length, and using a hashing algorithm to compute the bucket. That bucket may contain more than one element in the form of a linked list. The linked list would then be walked to arrive at the specific element being searched for. Fortunately the linked list chains are always kept pretty short, so we don't worry about their growth. But now instead of a simple base+offset*size equation, we feed a (usually) multi-byte string to a hashing algorithm, that gives us an offset, that leads us to a short chain of elements to scan through.

So the array lookup has some constant overhead, and the hash lookup has some constant overhead (overhead that doesn't change as the size of the structure grows). But the amount of constant overhead for the array is smaller than the amount of constant overhead for the hash.

We mostly don't worry about that. If we were looking for bleeding edge performance we would be using C or C++ to begin with. But the point to my mention of efficiency in this case was this: Sometimes function tables are used inside of tight loops. And infrequently, those tight loops become a performance problem. In such cases, switching from a hash based dispatch table to an array one will bring some performance improvement.

The more important point is that if one needs to assure true "switch-like" behavior, where the order of comparisons is predictable, an array is more appropriate; a hash based dispatch table doesn't guarantee a predictable order in which cases will be tested for a match.

Hi HazyLife. I did consider using the Switch module, but I had read that it is being deprecated and that I should be using given/when, and that is is more efficient. No other reason I can't or won't - I was just curious why I'm getting the syntax errors for given/when. Also, this is a part of a much larger script that has a long processing time and I'm trying to be as efficient as possible. Thanks for your input!

Hi Rolf. As I mentioned to HazyLife, I could have used Switch and I also thought of using an array, as you have demonstrated above, but in the actual program I'm running I'm already in the middle of hash. I will try if I can do this there, however. I'm trying to do this the most efficient way and I don't have a whole lot of PERL experience. Thanks for replying!!

At this point, you may have decided not to use experimental features.
They're certainly not a good idea in production code.
Here's some additional information on both Switch and switch: "Re: switch statement".

Finally, here's a version of your code which generates no errors or warnings (I used 5.18.1 — the same version as you're running).
In addition, while I see you've been provided with many alternatives to given/when, here's two more.
[Note: this is just example code that includes no argument checking.]

Hi Ken. Thanks for taking the time for this detailed post . I was so caught up in things yesterday that I missed it and didn't respond sooner. It was very helpful and I learned a great deal - the more examples the better. Cheers, DP.

Hi Marshall. Thanks for including both an if/else and hash example. I wasn't aware of the more efficient way of using IF and that is what I was looking for (hard to find that stuff by just searching online). Also these make it a lot easier to me to try different solutions. Unfortunately I couldn't get your examples to work. I'm sure I'm overlooking something simple, but when I pass a value to the subroutines I'm not getting any output (see example below for hash). I thought the return would print it to the screen. Please pardon my newbness.

Beyond that, the example I created for the question is rather simplified. In real life I have to deal with a string where I am only concerned with the first number. E.g., n.yy.zzz, where "n" is the number I need to map to. I can get this number simply enough with regex, but I'm not sure how to implement that within your example (can't test because of the output issue). Here's an attempt with an illustration of what I'm trying to do:

use strict;
use warnings;
#Using a hash table
#Evaluate on the first digit of the string
{
my ($var2) = @_;
# $var1 = shift; # is slightly faster with one var
# my ($var1, $var2) = @_; # slightly faster than than 2 shifts
# normally this minor difference doesn't matter.
# Perl can implement the if/else idea very code efficiently.
return ("One") if ($var2 =~ /^1/ );
return ("Two") if ($var2 == /^2/ );
return ("Three") if ($var2 == /^3/ );
return undef;
}
# I want to evaluate the number 1.00.000
# I thought I would just have to pass it
# to the subroutine to get a return value.
test2(1.00.000);

You show use of strict and warnings but you should have seen a message that return is not valid as your code presents it: "Can't return outside a subroutine..."

You don't have any mechanism for output -- no print of the return value, were the return actually valid.

Further, inclusion of comments which have no relevance to your problem merely makes your code more verbose, and thus, less likely to be a candidate for a reply by Monks who have other demands on their time. You shouldn't waste it if you really "appreciate (our) time."

So here's an Rx: make sure you have your fundamentals down... and use that knowledge to recognize when you've been given an outline; not a line-for-line code solution. (Offering that kind of response is wholly in keeping with the Monastery ethos: we're here to help you learn; not to spoonfeed you with solutions!)

Updated by addition of thoughts in last para following the ellipsis.

Update2/clarification/correction: (yeah, my remark re comments is too broad, in insufficiently precise.) My intent -- now "better phrased" I hope -- was to point out that non-code info on the problem belongs in the narrative -- not in the code -- and that code that's been commented out should be omitted unless it casts some NEW light on the problem -- which is not the case with OP's reposting of Marshall's well-taken comment on the efficiency of the construct shown in the node to which Deep_Plaid is addressing himself.

Questions containing the words "doesn't work" (or their moral equivalent) will usually get a downvote from me unless accompanied by:

is not correct for input values starting with 2 and 3 and is not very efficient in terms of performance, nor in terms of coding simplicity. Immediate correction of the error is to replace == with =~ for cases 2 and 3:

Note that Marshall corrected these two errors, but I thought it would be useful to point these out to you for your benefit. An additional improvement would be to remove the triple regex and to extract the first digit from the string only once:

Doing the extraction of the first digit only once is cleaner, removes the risk of the error I pointed out just above and is likely to be faster if that matters (although it is true that an anchored regex is pretty fast). And it paves the way for yet another improvement, the use of an array rather than multiple evaluations. The full program may now be this:

Now, assuming you have a very large amount of data and performance matters, we may want to benchmark this against your (corrected) triple regex version and an intermediate solution extracting the first digit only once:

As you can see, the array solution is about twice faster. Having said that, performance is often not so important (it is often fast enough anyway), and I am using quite regularly solutions similar to Marshall's proposals.

Hashes with dispatch tables (slow sub calls) and if-elsif-chains (slow linear testing) shouldn't be as fast as this approach.

Dear Rolf, I know that you are interested in functional programming possibilities in Perl, so please stop taking for granted what HOP opponents are hammering constantly. Subroutine calls of course add some time penalty, but not as much as many people think or say. In fact, as soon as the routine is really doing something, the sub call penalty becomes almost anecdotal or even almost negligible compared to the rest or the processing. In the benchmark below, the dispatch table ranks second best, immediately after direct array search, although it does really nothing more than returning a value.

I have to admit to using the following structure when in need of a switch compatible statement in perl 5.14 and earlier i.e. when feature, as mentioned elsewhere in this discussion, is not available...