http://www.perlmonks.org?node_id=1078677

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 (Patriarch) 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 (Saint) 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)

    update

    ¹) 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.

    Steve
    --
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 (Saint) 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.
A reply falls below the community's threshold of quality. You may see it by logging in.