Jason Nowak
Jason Nowak

Reputation: 11

How do I use if/then/else or guards in a case statement in Haskell?

The "Guards vs. If-Then-Else" helped a little but I still want to know if this can work somehow. I need to take a list and return every other element of the list. For even length lists I got it just if length (xs) mod 2 == 1 to start is there an issue so I want to break up the initial length of list case like this:

everyOther:: [Int] -> [Int] 
everyOther [] = [] 
everyOther (x:xs) = case length (xs) 'mod' 2 of 0 ->  
if (length (xs) `mod` 2 == 0)   
then  x:everyOther (take 1 xs) 
else x:everyOther (drop 1 xs) 
1 -> if (length (xs) `mod` 2 == 1) 
then  x:everyOther (take 1 xs) 
else x:everyOther (drop 1 xs)

It's telling me there is a possible "spacing error" at the "if" but is this functionally correct. Can I do this?

Upvotes: 1

Views: 2977

Answers (1)

Daniel Wagner
Daniel Wagner

Reputation: 152837

There seem to be a few different errors in this code, varying through the whole range from syntactic to conceptual to algorithmic. Let's start with the syntax errors and move up the chain.

Code in Haskell is organized into blocks. Each element of a block must start at the same indentation level; and for beginners, nested blocks should use a deeper indentation level than its surrounding block. At the top level, modules are a block whose elements are equations. case/of also begins a block, whose elements are pattern -> expression matches.

Together, these rules mean that the patterns 0 and 1 in your case statement should be aligned with each other and indented deeper than the first e of everyOther. The expression inside should be deeper still to avoid confusion. Here's one popular style for satisfying these constraints:

everyOther (x:xs) = case length (xs) 'mod' 2 of
    0 -> if (length (xs) `mod` 2 == 0)
         then x:everyOther (take 1 xs)
         else x:everyOther (drop 1 xs)
    1 -> if (length (xs) `mod` 2 == 1)
         then x:everyOther (take 1 xs)
         else x:everyOther (drop 1 xs)

Next: backticks (` — usually located to left of the row of numbers at the top of the keyboard) and forward ticks (') mean different things in Haskell. It's important to use backticks for converting prefix functions to infix ones; so length (xs) 'mod' 2 should be length (xs) `mod` 2. You also have a lot of redundant parentheses, notably around your argument to length and in the coditions of your if expressions. Though not an error, it is worthwhile to learn the precedence rules, as you'll need to understand code that doesn't litter them everywhere.

everyOther (x:xs) = case length xs `mod` 2 of
    0 -> if length xs `mod` 2 == 0
         then x:everyOther (take 1 xs)
         else x:everyOther (drop 1 xs)
    1 -> if length xs `mod` 2 == 1
         then x:everyOther (take 1 xs)
         else x:everyOther (drop 1 xs)

Next up is a conceptual error. In an expression like case foo of 0 -> a; 1 -> b, we will only ever evaluate expression a when foo == 0, and will only ever evaluate expression b when foo == 1. This makes the tests in your conditions redundant; in both branches of the case, we will definitely take the then branch. So if we were to take this code literally, it would be simpler and equivalent to write this instead:

everyOther (x:xs) = case length xs `mod` 2 of
    0 -> x:everyOther (take 1 xs)
    1 -> x:everyOther (take 1 xs)

Since we now have the same code in both branches of the case, it seems clear this wasn't your intention; but it isn't super clear to me what your intention was.

There is another suspicious (though again, not technically wrong) detail: keep in mind that in the foo of everyOther (x:xs) = foo, the list xs does not include the first element of the list given to everyOther. So calls like length xs will be off by one compared to the length of the input given to everyOther. Take that into account as you write the expressions for the 0 and 1 patterns of your case statement.

Returning to your intention: since in both patterns you write everyOther (take 1 xs), I assume that you wanted that expression to be evaluated sometimes. This makes me think you have an algorithmic error; take 1 xs will throw away almost all of the input list, which doesn't match my understanding of what you want everyOther to do.

Hopefully this discussion guides you in improving your attempt, and you can make more progress towards your goals.

Upvotes: 5

Related Questions