Beefy Boxes and Bandwidth Generously Provided by pair Networks
Don't ask to ask, just ask

Complex if/else or case logic

by cunningrat (Acolyte)
on Mar 17, 2014 at 20:28 UTC ( #1078677=perlquestion: print w/replies, xml ) Need Help??

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

Disclaimer: I'm not a perl monk. Oblate, at best.

I'm writing a subroutine to parse a mail message according to multiple criteria, and figure out what "kind" of message it is. The criteria can be message senders, presence of particular text in the subject line, or presence of particular text in the body of the email.

The obvious way to do it is a very, very complex block of nested if/else's. Is there a better way to do it?

Example pseudocode:

if ($sender = "sender A") { if ($subject =~ "some text") { if ($body =~ "some other text") { return "type 1"; } elsif ($body =~ "some other other text) {return "type 2"; } else {return "type 3";} } elsif ($subject =~ "some other subject") ...

You get the idea.

Replies are listed 'Best First'.
Re: Complex if/else or case logic
by graff (Chancellor) on Mar 18, 2014 at 02:59 UTC
    Let's suppose you could structure each email message as a hash, where the keys are things like "sender", "subject", "body", etc (as needed to handle whatever conditions you use to identify "kinds" of messages).

    Then, you could structure your inventory of "kinds" as lists of conditions to be satisfied - e.g.:

    my %kinds = ( type1 => { sender => qr/^me$/, subject => qr/this/, body => qr/tha +t/ }, type2 => { sender => qr/^you$/, subject => qr/these/, body => qr/t +hose/ }, # ... ); sub classify_msg { my ( $msg ) = @_; # $msg is expected to be a hash ref my @matches = (); for my $type ( keys %kinds ) { my $matched = 1; for my $test ( @{$kinds{$type}} ) { $matched &= ( $$msg{$test} =~ $kinds{$type}{$test} ); last unless $matched; } push @matches, $type if ( $matched ); } return \@matches; }
    (not tested - updated to add a missing close-curly in the second "for" statement, and to use curlies instead of square brackets when assigning anon.hashes to keys in %kinds)

    This assumes a given message could meet the conditions for more than one "kind". It also assumes that regex matching will always be an appropriate tool for testing the various conditions (but you could elaborate the "kinds" structure to handle other types of tests).

Re: Complex if/else or case logic
by BrowserUk (Pope) on Mar 17, 2014 at 20:56 UTC

    If it is all constants:

    my %actions = ( 'sender a' => { 'some text' => { 'some other text' => 'type 1', 'some other other text' => 'type 2', }, 'some other subject' => { ... }, }, 'sender b' => { ... }, ); sub action { my( $sender, $subject, $body ) = @_; my $rv = $action{$sender} // return 'unknown sender'; $rv = $rv->{$subject} // return 'unknown subject'; $rv = $rv->{$body} // return 'unknown body'; return $rv; }

    With the rise and rise of 'Social' network sites: 'Computers are making people easier to use everyday'
    Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
    "Science is about questioning the status quo. Questioning authority".
    In the absence of evidence, opinion is indistinguishable from prejudice.
Re: Complex if/else or case logic
by LanX (Archbishop) on Mar 17, 2014 at 22:08 UTC
    > You get the idea.

    not really, does this $subject =~ "some other subject" mean you wanna match on regexes?

    You could use a nested hashes to hold all criterias and easily loop over them, but I would stick with real code to stay flexible.

    Perl can be very compact, see all the examples in Given When Syntax, especially Re^2: Given When Syntax.

    Instead of nesting switch $body => HASHREF statements you could just write three subs if_body ARRREF and so on and define your own little domain specific language. (array cause order seems to matter)

    if_sender [ "sender A" => if_subject [ "some text" => if_body [ "some other text" => sub {return "type 1";}, ... ], ... ], ... ];

    Of course you are free to just use nested arrays and to assign each level to one fixed criteria when looping.¹

    A simple string would mean test string equality, a qr/regex/ test for match. HTH! :)

    Cheers Rolf

    ( addicted to the Perl Programming Language)


    ¹) then you might wanna use YAML as input format to simplify writing

Re: Complex if/else or case logic
by skx (Parson) on Mar 17, 2014 at 23:05 UTC

    This will depend on your use-case, (replacing procmail, or just filtering mails intelligently..), but you might enjoy a peek at Mail::Audit and Email::Filter, both of which have the easy parts done.

Re: Complex if/else or case logic
by Anonymous Monk on Mar 17, 2014 at 22:30 UTC

    As always TIMTOWTDI... It really depends on how complex your logic will get. I'd say if-elsif-else's are fine for relatively simple logic, where your definition of "simple" will vary based on what you consider readable and maintainable.

    If you find yourself repeating conditions in your if()'s, factor them out. (e.g. my $is_sender_x = $sender=~/.../; if ($is_sender_x) {...} ...)

    The same goes for the bodies of the conditions, if those repeat themselves, factor them out into subs. In your case simple returns are fine, as long as you don't make any typos ;-) (consider using constants).

    If you find the branches of the if-elsif-else's to be repeating themselves, or things are just getting too complicated, consider a truth table type approach, like this one: Case Exhaustion Tree

Re: Complex if/else or case logic
by thezip (Vicar) on Mar 17, 2014 at 20:40 UTC

    Perhaps you could implement a state machine?

    *My* tenacity goes to eleven...
Re: Complex if/else or case logic
by GrandFather (Sage) on Mar 20, 2014 at 09:18 UTC

    Nested code like that is nasty to write correctly, nasty to validate and nasty to maintain. Maybe you can achieve what you want as a two pass thing. Set a bunch of boolean variables to start:

    my $isA = $sender eq "sender A"; my $isSpam = $subject =~ /\[SPAM\]/; my $hasSig = $body =~ /^--/; ...

    then handle interesting cases as a second stage:

    if ($isA && $isSpam && $hasSig) { blowRaspberry($sender); } elsif ($isA && !$isSpam) { sendThankyou(); } ...

    which makes new cases much easier to deal with and existing code easy to write and understand.

    If the code changes take longer than the time saved, it's fast enough already.
Re: Complex if/else or case logic
by Lennotoecom (Pilgrim) on Mar 17, 2014 at 20:54 UTC
    Do you want to tipify some random mail?
    Like classify them?
    (that assumption was made due to your return things)
    If that's the case I suggest some coding system.
    Like prime numbers or create alphabet.
    In any case you haven't provided enough data.
Re: Complex if/else or case logic
by pvaldes (Chaplain) on Mar 20, 2014 at 13:03 UTC
    Looks like a work for Mail::SpamCannibal or some related module. Don't know if this is what you want but can take a look. Of course you should use && and or to group conditions as said.
Re: Complex if/else or case logic
by sundialsvc4 (Abbot) on Mar 19, 2014 at 16:07 UTC

    This is very much a judgment call.   On the one hand, a complex if/then structure can become unwieldy.   But, on the other hand, it is clear, and if at some future date a new kind of requirement “sails in from left-field,” such code can readily be changed to accommodate it.   (And, as we all well know, such foul-balls do come splattering in, always at the most inconvenient times.)

    Personally, I think that I would continue with this if/then structure ... with subroutines to help keep it from all becoming “a gigantic run-on sentence,” and maybe using hashes and so-forth within some of those subroutines.   I know that I could put any sort of logic that was called-for in any of those if-statements, even if other if-statements nearby needed to make differing decisions.   I would consider this approach, “awkward” though it may seem, to be more maintainable in the long run.

    To put it another way, sometimes “elegance” produces a house of cards or a stack of dominoes.   Suddenly, that foul-ball requirement topples everything, forcing the maintainer either to start putting his fingers into the dike, or to do a massive reorganization of a large block of code, thereby de-stabilizing all of it, just to handle the unanticipated “special case.”   Partly since I’ve spent much of my career dealing with legacy code to which this sort of thing has happened umpteen times, I get very sensitive about maintainability versus elegance.   Code for the future, as best you reasonably can at the present time.   If the code takes a few more milliseconds, but is maintainable in the long run, I can cost-justify throwing silicon at it.

      So, the take aways are–

      • "unwieldy" eq "clear";
      • “It was like that when I got here!”
      • This one is too good to not quote verbatim: I know that I could put any sort of logic that was called-for in any of those if-statements, even if other if-statements nearby needed to make differing decisions.
      • Hashes and subroutines and so-forth, oh my!
      • "awkward" eq "maintainable";
      • To mix another metaphor, is that a dyke in your pocket or are you just glad to put a finger in my eye?
      • "elegance" ne "maintainability"; # <- Self-analysis, eat your heart out!
      • …will no one think of the children?!?

Log In?

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://1078677]
Approved by ww
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others romping around the Monastery: (2)
As of 2019-08-17 20:46 GMT
Find Nodes?
    Voting Booth?
    If you were the first to set foot on the Moon, what would be your epigram?

    Results (134 votes). Check out past polls.