tags:

views:

118

answers:

6

I need a function that works like this:

foo :: Integer -> Integer -> [Integer]
foo a b = do
           let result = []
           let Coord x y = boo a b
           if x > 0
              let result = result ++ [3]
           if y > 0 
              let result = result ++ [5]
           if x < a
              let result = result ++ [7]
           if y < b
              let result = result ++ [9]
           result

I can not use the guards because the result can have more then one element. But as I see I can not use 'let' in the 'if' expression:

all_possible_combinations.hs:41:14: parse error on input `let'

How can I check multiple expressions and add new elements in the list? I search not only imperative solution, but the functional one.

+5  A: 

EDIT: My thanks to sepp2k for pointing out what was intended. It was even more imperative than I could grok with my Haskell-colored glasses on...

Here's a quick-n-dirty solution:

result = (if x > 0 then [3] else []) ++
         (if y > 0 then [5] else []) ++
         (if x < a then [7] else []) ++
         (if y < b then [9] else [])

(Old answer deleted.)

Marcelo Cantos
Actually that doesn't work for his code. He wants result to contain `[3,5,7,9]` if all four conditions are true. Note that the conditions are not mutually exclusive.
sepp2k
Yes, i wrote about this: "result can have more then one element".
demas
+4  A: 

Three things:

  1. You can use let inside an if expression, but it needs to be followed by in or inside a do block. Either way the expression which uses the name you just bound with let, needs to be in the same if. I.e. you can do:

    if x > 0
    then let a = 42 in a+x
    else 23      
    

    but not

    if x > 0
    then let a = 42 in ()
    else let a = 23 in ()        
    print a
    
  2. You can not use let to change the value of an existing variable. As a matter of fact, you can not change the value of a variable, ever.

  3. If you use the name bound on the LHS of = on the RHS of the = it will refer recursively to itself - not to any previous binding. In other words let x = x+1, causes infinite recursion, it does not increase the value of x by one.

To do what you want to, you'd need to restructure your code to something like this:

let result1 = if x > 0 then [3] else []
let result2 = if y > 0 then result1 ++ [5] else result1
let result3 = if x < a then result2 ++ [7] else result2
if y < b then result3 ++ [9] else result3

Note that it's usually a bad idea, performance wise, to use ++ to append at the end, instead of using : and then reverseing the list at the end.

sepp2k
+2  A: 

You have also an issue in the way you think if should be used. In Haskell if is an expression, i.e. it evaluates to a value. Because of this, it must always be accompanied by an else branch. Marcelo's example should clarify this. If you want something like the if statement in an imperative language, then you have Control.Monad.when or Control.Monad.unless.

Ionuț G. Stan
+1  A: 

There are a number of things wrong with your code, unfortunately. The first problem is the do on the top line: you aren't using any monads, so you shouldn't have it at all. The rest of the code is trying to work in an imperative manner, but this (unsurprisingly) doesn't work in Haskell. In Haskell, every expression needs to have a result; for this reason, all if statements must be of the form if ... then ... else .... Similarly, every let binding must be of the form let ... in ...; it's impossible to change the value of a variable, so if you left off the in, nothing would happen. And because of this, each line let result = result ++ [3], if it could be executed, would try to make a list result which consisted of all of its elements with a three at the end—in other words, a self-referential list! (The canonical example of such a thing is let ones = 1:ones in ones to create a list whose tail is itself and thus contains only 1s.)

So now the question is, what does your function want to do? You have four boolean conditions, and want to add a different element to your list for each one. There are a number of ways you could write this instead. One approach is

foo :: Integer -> Integer -> Integer
foo a b = let Coord x y = boo a b
              checkAdd cond elem = if cond then (elem :) else id
          in foldr (uncurry checkAdd) []
                   [(x > 0, 3), (y > 0, 5), (x > a, 7), (y > b, 9)]

First, we call boo and look at the results. We then define a checkAdd function, which has type checkAdd :: Bool -> a -> ([a] -> [a]). If its first argument is true, then it returns a function which prepends its second argument to a list; otherwise, it returns a function which does nothing to a list. So checkAdd True 1 x == 1:x, and checkAdd False 1 x == x. The last line is admittedly a little mysterious, but it's not too bad. If you have a list fns = [f1,f2,...,fn] of functions, and want to produce f1(f2(...(fn(x))...)), then you can use foldr ($) x fns. This fold is similar, except we have uncurry checkAdd instead of $; uncurry causes a function to take a tuple instead of two arguments, and so when we foldr it over the list, checkAdd produces a function for each element. Then, each function is applied, and you arrive at the list you wanted. The reason we don't have ($) . uncurry checkAdd is that $ is really just the identity function with low precedence; this makes sense, since $'s only purpose is to have really low precedence but not do anything.

Of course, this code looks completely different from what you had above, but that's part of what's interesting about trying out completely different languages :-)

Antal S-Z
+6  A: 

Other people have said why what you tried doesn't work. Here is a concise way of doing what you want:

foo :: Integer -> Integer -> [Integer]
foo a b = map snd (filter fst [(x > 0, 3),
                               (y > 0, 5),
                               (x < a, 7),
                               (y < a, 9)])
  where Coord x y = boo a b

So how does this work?

We start with a list of pairs. The second element of each pair is the value we might want to include in the return value from foo. The first element of each pair tells us whether we actually want to include it.

Then we call filter fst, which throws away all the pairs where the condition is false.

Finally, we call map snd, which throws away the conditions (all the ones that are left are true), and keeps the values we are interested in.

Dave Hinton
A: 

As already mentioned you are looking at this the wrong way. You can't change the value of an variable in Haskell, and if-then-else expressions always have an else clause. I have made some code that looks and feels like what you were trying to do, but as you have seen, there are many better ways of doing this.

foo a b = let Coord x y = boo a b in
        (if x > 0 then (3 :) else id) $
        (if y > 0 then (5 :) else id) $
        (if x < a then (7 :) else id) $
        (if x < b then (9 :) else id)
         []
HaskellElephant