5

I'm trying to sort 3 elements in a list. But i"m having trouble translating it to haskell. Is it possible to do nested if statements in haskell? I've been trying pattern matching, but it is taking me forever.

if (x < y) {
    if (z < x) swap(x,z);
} else {
   if (y < z) swap(x,y);
else swap(x,z);
} 
  if(z<y) swap(y,z);

this is what I have tried

intCMP :: Int -> Int -> Ordering
intCMP a b | a == b =EQ
           | a < b = LT
           | otherwise = GT

sort3 :: Ord a => (a -> a -> Ordering) -> [a] -> [a]
sort3 cmp [a,b,c] = if cmp a b == LT then
                   if cmp a c == Lt then
                      if cmp b c == LT then
                         [a,b,c]
                      else
                         [a,c,b]
                   else
                      [c,a,b]
                else if cmp b c == LT then
                        if cmp a c == LT then
                             [b,a,c]
                        else
                             [b,c,a]
                else
                     [c,b,a]
7
  • 1
    "Yes" Furthermore, those nested if statements can be flattened. Commented Oct 16, 2017 at 16:14
  • what do you mean flattened? Commented Oct 16, 2017 at 16:21
  • 4
    Haskell doesn't really have if statements. It has if expressions. What makes you doubt their ability to nest? Commented Oct 16, 2017 at 16:25
  • 1
    What went wrong when you tried that? Other than a minor typo (Lt for LT) that code works just fine when I try it. Commented Oct 16, 2017 at 17:33
  • 2
    See also the MultiWayIf extension Commented Oct 17, 2017 at 0:49

3 Answers 3

9

This trick is that in Haskell if is not a statement, but an expression. It returnes a value from one of the branches, not executes the code there. In fact, if can be though to be just a syntactic sugar to a function if :: Bool -> a -> a -> a (of course, no such function can exist, because if is a keyword; still, one can trivially implement such a function, if it is named differently, like this).

So, yes, nested if statements are possible, just as any expression, like in

max x y z = if x < y then (if y < z then z else y) else (if x < z then z else x)

However, this is not directly applicable to your case, since you cannot do swap that easy: all values are immutable in Haskell. So, if you don't want to use monads or something like that, a solution may be to return the sorted list:

sort [x,y,z] =
    if x < y then
        (if y < z then
            [x,y,z]
        else
            (if x < z then
                [x,z,y]
                    else
                [z,x,y]
            )
        )
    else
        undefined -- implement other cases here
Sign up to request clarification or add additional context in comments.

2 Comments

I don't understand what you're trying to say about being syntactic sugar for an if function. There is no such function, of course, and cannot be because if is a reserved token. You might say that it's a syntactic alternative to Data.Bool.bool :: a -> a -> Bool -> a, although of course the arguments are in a different order.
@amalloy Thank you, I'll change the wording. I am sorry if that sounds confusing. What I mean is that if is not something magical and can be thought of as a function if :: Bool -> a -> a -> a. Of course, there is not, and cannot be such a function, you are right. Still, one can just name it differently, effectively implementing the if operator in a function.
8

As lisyarus said, you can do this. However, if is usually a bit awkward in Haskell; usually, pattern matching is a better option — this avoids the boolean bottleneck and allows you to directly deconstruct meaningful values. In your case, the most obvious thing would be to replace the ugly == LT checks with case expressions:

sort3 cmp [a,b,c] = case cmp a b of
      LT -> case cmp a c of
         LT -> ...

Since you always check all three anyway though, there's not really a need to nest the checks; you might as well check them all once:

sort3 cmp [a,b,c] = case (cmp a b, cmp a c, cmp b c) of
       (GT, _ , GT) -> [c,b,a]
       (GT, LT, _ ) -> [b,a,c]
       (_ , LT, GT) -> [a,c,b]
       (_ , GT, _ ) -> [c,a,b]
       (GT, _ , _ ) -> [b,c,a]
       _            -> [a,b,c]

Comments

0

For three elements, it doesn't matter, but once you get to four, it really helps to have a notion of swapping, although I wouldn't go with your swapping regime. You can do this by giving each phase of computation its own function.

data Triple a = Triple a a a
sort3, sort3', sort3''
  :: Ord a => Triple a -> Triple a

sort3 t@(Triple x y z)
  | x <= y = sort3' t
  | otherwise = sort3' (Triple y x z)

-- Precondition : the first two elements are in order
sort3' t@(Triple x y z)
  | x <= z = sort3'' t
  | otherwise = Triple z x y

-- Precondition: The smallest element is first
sort3'' t@(Triple x y z)
  | y <= z = t
  | otherwise = Triple x z y

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.