8

I want to check the condition of the previous if condition to determine the next if condition is to be executed or not. Each if condition may return a value.

Edit: Sorry for that the example I provided before look a bit odd...:( This is my real example, and I want to simplify the if-then-else for goingToMove

goingToMove p routes points w h = 
                        if canMove p points
                            -- the point can be moved in the map 
                            then let r = routes ++ [p]
                                     l = remainList p points
                                in move p r l w h
                            -- the point cannot be moved in the maps
                            else []

move p routes points w h = 
            if (length routes) == 2 
                then routes
                else let one = goingToMove (tallRightCorner p) routes points w h in
                    if (null one)
                        then let two = goingToMove(tallRightBCorner p) routes points w h in
                            if (null two)
                                then let three = goingToMove (tallLeftBCorner p ) routes points w h in
                                    if (null three)
                                        then ....
                                        ...... -- until, let eight = ..
                                        else three
                                else two
                        else one 

Edit:Bad example When this thing is written in java, I may use a mutable boolean flag, and return a mutable data.

public String move (int number){
        // base case
        if (number == 0){
            return "Finished the recursion";
        }
        // general case
        else {
            String result;
            boolean isNull = false;

            if ((result = move(3)) == null){
                isNull = true;
            }
            else {
                return result;
            }

            // continue to execute the if-conditions if the previous condition failed
            if (isNull){
                if((result = move(2)) == null){
                    isNull = true;
                }
                else {
                    return result;
                }
            }

            if (isNull){
                if((result = move(1)) == null){
                    isNull = true;
                }
                else {
                    return result;
                }
            }

            return null;
        }
    }

But in Haskell, there is no mutable data, and only if-then-else condition. Then the code will looks like this, and I want to simplify this because in my real work, there are 8 levels of if-then-else which look terrible and messy....

move 0 = "Finished the recursion"
move n = 
    let one = move 3 in
    if null one
        then let two = move 2 in
            if null two
                then let three = move 1 in
                        then null
                        else three
                else two
        else one
code4j
  • 4,208
  • 5
  • 34
  • 51
  • Is the `move` you're calling in your Java code the same as the `move` you're defining? If so, I don't see how it doesn't loop infinitely for non-zero input. `move(3)` calls `move(3)` calls `move(3)` ... – rampion Feb 06 '13 at 18:41
  • The haskell code you provide has the wrong type, too. You're explicitly returning `null` at some point in the java code, which means the haskell code should be `Int -> Maybe String`, even if it wasn't an infinite loop. Oh, and the haskell code you suggest also has a syntax error (missing a line with an if in it?) making it hard to figure out exactly what you're up to. – Carl Feb 06 '13 at 18:44
  • I have supplied a real example :( sorry – code4j Feb 06 '13 at 19:33
  • 1
    You should start by using a lot more pattern matching and fewer conditionals. You also have a bunch of superfluous `let`s and you don't need to parenthesize the condition of an `if` expression. – C. A. McCann Feb 06 '13 at 19:39

5 Answers5

16

In Java if I wanted to do the following:

result = func1(arg);
if (result == null){
  result = func2(arg);
  if (result == null){
    result = func3(arg);
    if (result == null){
      result = func4(arg);
    }
  }
}
return result;

What I'm essentially doing is finding the first result from func1(args), func2(args), func3(args), func4(args) that returns non-null.

In Haskell, I'd model func1, func2, func3, and func4 as functions that returned a Maybe a value, so that they could return Nothing if they failed.

func1, func2, func3, func4 :: Int -> Maybe Result

Then I can use the <|> operator (from Control.Applicative), which has the following definition for Maybe a:

Nothing <|> x = x
x       <|> _ = x

So I can convert the above Java to

func1 arg <|> func2 arg <|> func3 arg <|> func4 arg

And due to the miracle of lazy evaluation, func2 arg is only evaluated if func1 arg returns Nothing, same as in the Java example.

rampion
  • 87,131
  • 49
  • 199
  • 315
  • it looks nice, does Nothing means nil? – code4j Feb 06 '13 at 19:35
  • `Maybe a` is a data type that has two constructors: `Nothing :: Maybe a` and `Just :: a -> Maybe a`. It can be defined as `data Maybe a = Nothing | Just a`. – rampion Feb 06 '13 at 20:02
  • 1
    This is very nice, but you could also use MonadPlus: `msum [func1 arg, func2 arg]`, or even `msum $ map ($ arg) [func1, func2, func3, func4]`. This is especially useful when you're getting the list of functions from somewhere else. I don't like it quite so much for defining control flow though. – John L Feb 07 '13 at 13:15
12

Apart from the nice employment of <|> that rampion gave and the similar suggestion of sclv, another common way is to use guards, and exploit laziness,

move :: Int -> Maybe String
move n
    | n == 0       = Just "Finished the recursion"
    | isJust move3 = move3
    | isJust move2 = move2
    | isJust move1 = move1
    | otherwise    = Nothing
      where
        move3 = move 3
        move2 = move 2
        move1 = move 1

Due to laziness, move i (i = 3, 2, 1) is only evaluated if it's needed.

In the given case, move 3 <|> move 2 <|> move 1 is much nicer, but in cases where the conditions require evaluating different functions with different return types, the use of guards and lazy bindings in a where clause can be the natural solution to avoid awkward nested ifs.

Community
  • 1
  • 1
Daniel Fischer
  • 181,706
  • 17
  • 308
  • 431
  • 1
    `Just` is a value constructor of `Maybe a`. Since you return and check for `null` in your Java, having the Haskell version return a `Maybe String` is the proper analogy. `isJust` (available from the `Data.Maybe` module) tests whether a `Maybe a` value is a `Just a` or `Nothing`, so `isJust move3` corresponds to `if ((result = move(3)) != null)` [flipping your `if/else`s]. – Daniel Fischer Feb 06 '13 at 19:41
1

edit: Here's some code for the new example:

move p routes points w h 
     | length routes == 2 = routes
     | otherwise = find (not . null) . map gtm [tallRightCorner, tallRightBCorner, tallLeftBCorner]
    where gtm f = goingToMove (f p) routes points w h

Note that this returns a maybe. You can use fromMaybe to stick in a default case.

Here's the old (but typechecking) code from the first proposed example

move 0 = "Finished the recursion"
move n = concat . maybeToList . msum $ map move' [3,2,1]
  where move' x = let mx = move x in if null mx then Nothing else Just mx
sclv
  • 38,665
  • 7
  • 99
  • 204
1

You want routes if its length is 2 or the first non-null result from a series of applications of goingToMove that vary by which corner function is applied to p.

move p routes points w h
  | length routes == 2 = routes
  | otherwise = head
              $ filter (not . null)
              $ map tryMove corners
    where tryMove f = goingToMove (f p) routes points w h
          corners = [ tallRightCorner
                    , tallRightBCorner
                    , tallLeftBCorner
                    -- et cetera
                    ]
Greg Bacon
  • 134,834
  • 32
  • 188
  • 245
0

An option without Maybe could be to add a flag to the recursion (in the example below, you would call the function with the flag set to one):

move p routes points w h 1

move p routes points w h flag 
  | (length routes) == 2 = routes
  | otherwise = 
      if null result then move p routes points w h (flag+1)
      else result
        where result = case flag of 
                        1 -> goingToMove (tallRightCorner p) routes points w h
                        2 -> goingToMove (tallRightBCorner p) routes points w h
                        3 -> goingToMove (tallLeftBCorner p) routes points w h
                        --...etc.
                        _ -> []
גלעד ברקן
  • 23,602
  • 3
  • 25
  • 61