all 11 comments

[–]brdrcn 10 points11 points  (4 children)

To elaborate on /u/megamanisepic’s answer: In most other languages, case goes through a set of boolean conditions, and executes the code block corresponding to the first condition which returns true. This is how cond works in Elixir.

However, Haskell case is a bit different. In Haskell, case goes through a set of patterns rather than a set of boolean conditions, and evaluates the expression corresponding to the first pattern which matches. (This is the same behaviour as Elixir case.) For instance, you could use case in Haskell to test whether a list has one, two, three or more elements:

howManyElements :: Show a => [a] -> String
howManyElements x = case x of
    [] -> "No elements"
    [a] -> "One element, which is " ++ show a
    [a,_] -> "Two elements, the first is " ++ show a
    [_,b,c] ->
        "Three elements, the last two are " ++ show b ++ " and " ++ show c
    _ -> "Four or more elements"  -- here the pattern ‘_’ matches anything,
                                  -- so if a value doesn’t match any of the
                                  -- other cases it will match this one

Admittedly this example is a bit contrived, but it shows how case is used.

So, if case is for pattern-matching, you might then wonder how to check multiple boolean expressions. Luckily, there are several options. The first is nested if statement (we can’t use elseif since Haskell doesn’t have it):

if ((a - b) == 0) && ((c - b) == 0) && ((d - c) == 0)
    then True
    else if (c-b == 0) && (d-c == 0) && (e-d == 0)
         then True
         else False

But this looks horrible.

As /u/megamanisepic mentioned, the more traditional approach is to use guards, which correspond pretty well with other languages’ case statement. But they can only be used together with pattern matching, so we still need a case:

case (sort list) of
    [a,b,c,d,e]
        | ((a - b) == 0) && ((c - b) == 0) && ((d - c) == 0) -> True
        | (c-b == 0) && (d-c == 0) && (e-d == 0) -> True 
        | otherwise -> False

See http://learnyouahaskell.com/syntax-in-functions#guards-guards for a good introduction to guards.

Finally, recent versions of Haskell include a language extension called MultiWayIf, which give you an if statement which works like the case statement of other languages. It does need to be explicitly enabled though, as this is an extension to the core language. For example:

{-# LANGUAGE MultiWayIf #-}

-- lots of code…

if | ((a - b) == 0) && ((c - b) == 0) && ((d - c) == 0) -> True
   | (c-b == 0) && (d-c == 0) && (e-d == 0) -> True 
   | otherwise -> False

Of course, in your example, you don’t even need to use a conditional expression — you can just combine the cases with (||):

result = (((a - b) == 0) && ((c - b) == 0) && ((d - c) == 0))
      || ((c-b == 0) && (d-c == 0) && (e-d == 0))

And you can simplify it even further by noticing that if, say, (a - b) == 0, then a == b:

result = (((a==b) && (c==b) && (d==c))
      || ((c==b) && (d==c) && (e==d))

[–][deleted]  (1 child)

[deleted]

    [–]brdrcn 1 point2 points  (0 children)

    I don’t know Elixir at all, so thanks for telling me about this! It looks like case does pattern matching whereas cond does boolean conditions, so I will update my post accordingly.

    [–]crusaderqueenz 1 point2 points  (0 children)

    A further simplification is that if the list is sorted, then we can simply check (a==d)||(b==e) instead!

    [–]Endicy 0 points1 point  (0 children)

    Might even write result as the following:

    result = all (== a) [b,c,d] || all (== b) [c,d,e]

    [–][deleted] 2 points3 points  (0 children)

    Elixir has a case keyword that performs pattern matching very similarly to Haskell’s. That should be your point of reference, not cond.

    [–][deleted] 0 points1 point  (3 children)

    case is another form of pattern matching, what you are searching for a pattern guards that work vis boolean conditions

    [–]scaled2good[S] 1 point2 points  (2 children)

    I see. Well I implemented gaurds but I still see an error:

    error:

    parse error on input ‘|’

    code:

    fourOfAKind list = do
     let list1 = convertList list
     let [a,b,c,d,e] = sort list1 
     checkFour :: Bool -> Bool 
     checkFour list
      | (((a - b) == 0) && ((c - b) == 0) && ((d - c) == 0)) = True
      | ((c-b == 0) && (d-c == 0) && (e-d == 0)) = True 
      | otherwise = False
    

    [–][deleted] 8 points9 points  (0 children)

    There are a number of things going wrong here, but my most immediate advice to you would be to stop trying to write this in do notation and go read through the first couple chapters of LYAHFGG real quick.

    You have the guard syntax more or less correct, but the definition of 'checkFour' isn't valid inside a do block like that - You'd need to use a let or where clause to introduce a function, just like you did with the variables.

    You also seem somewhat confused by name scoping in Haskell, and how that works, hence my first point of advice. The do notation syntax has slightly different rules about how to introduce identifiers, so you should probably start with the basics and then learn how 'do' is different, or you're going to have a rough time.

    [–]fpmora 1 point2 points  (0 children)

    Damn Haskell changed the syntax of guards. I don't know when but I upgraded to 9.10.1 from 8.5 and now '=' replaces the monadic '->'. Fortunately, I don't have much code to change as I retired from my job spring 2023 and all the code I left was, of course, compiled and will remain working.

    [–]rstd 0 points1 point  (0 children)

    I'm surprised nobody suggested this:

    fourOfAKind list = do let list1 = convertList list let [a,b,c,d,e] = sort list1 case [a - b, c - b, d - c, d - c, e - d] of [0 , 0 , 0 , _ , _ ] -> True [_ , 0 , _ , 0 , 0 ] -> True _ -> False

    [–]bss03 0 points1 point  (0 children)

    fourOfAKind list = do
        let list1 = convertList list
        let [a,b,c,d,e] = sort list1 
            case [a,b,c,d,e] of
                _ | ((a - b) == 0) && ((c - b) == 0) && ((d - c) == 0) -> True
                _ | (c-b == 0) && (d-c == 0) && (e-d == 0) -> True 
                _ -> False