bolts
bolts

Reputation: 155

How to handle nested conditionals

I'm writing a function where I would like to return True if a number of conditions are met. For example, consider this function which compares two strings:

test st1 st2
    | st1 == st2
        | "foo" `isInfixOf` st1 = True
        | "baz" `isInfixOf` st2 = True
        | otherwise = False
    | otherwise = False

Obviously, this function is incorrect. However, I'm looking for a way to test several conditions and I wanted to know:

A) what is the clearest way to do this? B) is there an approach similar to what i have generated that uses guards?

For clarity, in my simple example above, the output of test should be the following:

test "foobuz" "foobuz" = True
test "foobutter" "foobuz" = False
test "buz" "buz" = False

N.B. Chaining conditions together might be an option, but it gets very unreadable after only two or three tests:

test st1 st2 = st1 == st2 && "foo" `isInfixOf` s1 || "baz" `isInfixOf` s2

I was thinking there might be a way to use the Endo Monoid to test a chain of several conditionals?

Upvotes: 5

Views: 229

Answers (6)

J. Abrahamson
J. Abrahamson

Reputation: 74334

While case is usually overkill for matching on booleans, it does give you a new context for guards.

test st1 st2 = case (st1 == st2) of
  True  | "foo" `isInfixOf` st1 -> True
        | "baz" `isInfixOf` st2 -> True
        | otherwise             -> False
  False                         -> False

You can also use pairing to form more exotic case-based nested ifs.

foo a b c = case (p1 a b, p2 b c, p3 a c) of
  (True,  _    , _   ) -> val1
  (False, False, True) -> val2
  (False, True , _   ) -> val3

Upvotes: 4

Joachim Breitner
Joachim Breitner

Reputation: 25763

You can combine a case expression and patterns guards:

test st1 st2 = case st1 == st2 of
    True | "foo" `isInfixOf` st1 -> ...
         | "baz" `isInfixOf` st2 -> ...
         |  otherwise            -> ...
    False -> ...

Upvotes: 4

leftaroundabout
leftaroundabout

Reputation: 120711

The closest you can get to the way it looks in your original code is with the MultiWayIf extension:

{-# LANGUAGE MultiWayIf #-}

test st1 st2
   | st1 == st2  = ( if
      | "foo" `isInfixOf` st1 -> True
      | "baz" `isInfixOf` st2 -> True
      | otherwise             -> False )
   | otherwise                =  False


Of course, for this particular example it can be done better with other means – the other answers already gave a couple of suggestions, here's one more (since you've tested st1==st2 anyway)

test st1 st2 = st1==st2 && any (`isInfixOf`st1) ["foo", "baz"]

Upvotes: 4

danidiaz
danidiaz

Reputation: 27771

Using and and or:

test st1 st2 = and [ st1 == st2
                   , or ["foo" `isInfixOf` st1
                        ,"baz" `isInfixOf` st2] ]

Using the All and Any monoids:

test' st1 st2 = getAll . foldMap All $
    [ st1 == st2
    , getAny . foldMap Any $ ["foo" `isInfixOf` st1
                             ,"baz" `isInfixOf` st2]] 

The ala function from Control.Lens.Wrapped can sometimes simplify the wrapping/unwrapping of newtypes, but here it doesn't save us much:

test'' st1 st2 = ala All foldMap 
    [st1 == st2
    ,ala Any foldMap  ["foo" `isInfixOf` st1
                      ,"baz" `isInfixOf` st2 ]] 

Upvotes: 2

mhwombat
mhwombat

Reputation: 8136

Sebastian's suggestion of local helpers is a good one. However, the particular example you've given essentially boils down to if CONDITION then True else False, which can be replaced with CONDITION. So you could simply write:

main = print (fac 5)
test st1 st2 = st1 == st2 && ("foo" `isInfixOf` st1' || "baz" `isInfixOf` st2)

Another way to write this is:

main = print (fac 5)
test st1 st2
    | st1 == st2 && "foo" `isInfixOf` st1 = True
    | st1 == st2 && "baz" `isInfixOf` st2 = True
    | otherwise                           = False

Upvotes: 6

Sebastian Redl
Sebastian Redl

Reputation: 71899

I would use local helpers.

test st1 st2
    | st1 == st2 = test_after_eq st1 st2
    | otherwise = False
  where test_after_eq st1' st2'
            | "foo" `isInfixOf` st1' = True
            | "baz" `isInfixOf` st2' = True
            | otherwise = False

I think that should work.

Upvotes: 4

Related Questions