views:

338

answers:

7

I am totally new in Haskell and when writing small programs i normally end up with too many where clauses to check many things in the function, so it is good practice to write where clauses or is there any other good alternatives for this ?

for example in the code below i tried to find if there is ant duplicate elements in each row of the two dimensional list, it works and every thing is content in same function but i am not satisfied with how code looks and i find it more imperative style of approaching to the problem so i am looking for any suggestion or thought on this from the experienced people out there.

noDups :: [[a]] -> Bool
noDups du = and (checkSu du)
       where
       checkDup []     = []
       checkDup (x:xs) = checkRow x ++ checkDup xs
             where
             checkRow []     = [] 
             checkRow (x:xs) = [x /= y | y <- xs] ++ checkRow xs

once again this code is just to illustrate one problem, i am looking for approach to formulate problem in functional style. your suggestions or articles, links would be much helpful.

Thanks

+3  A: 
noDups :: [[a]] -> Bool
noDups = and . checkDup
  where
    --checkDup
    checkDup []     = []
    checkDup (x:xs) = checkRow x ++ checkDup xs
    --alternatively
    checkDup xs = concat $ map checkRow xs
    --alternatively
    checkDup = concat . map checkRow
    --alternatively
    checkDup = concatMap checkRow
    --checkRow
    checkRow []     = [] 
    checkRow (x:xs) = [x /= y | y <- xs] ++ checkRow xs
Justice
+18  A: 

Try to write abstract, reusable functions You'll be able to compose them much easier

isUnique :: Eq a => [a] -> Bool
isUnique [] = True
isUnique (x:xs) = all (/= x) xs && isUnique xs

noDups :: Eq a => [[a]] -> Bool
noDups = all isUnique
Dario
+1 for suggesting to split into smaller functions instead of trying to juggle all the complexity at once.
Joren
+2  A: 

Though there are exceptions, in general you may want to define "positive" functions, i.e. in this case define a function which returns True if the argument does contain some duplicate data. You could write that like this:

has_nested_duplicate :: (Eq a) => [[a]] -> Bool
has_nested_duplicate = any has_duplicate
  where
    has_duplicate []     = False
    has_duplicate (x:xs) = x `elem` xs || has_duplicate xs

This uses pattern matching, any, elem and (||). To get the negation, use not:

noDups :: (Eq a) => [[a]] -> Bool
noDups = not . has_nested_duplicate
Stephan202
+4  A: 

You don't need the second where clause. you can put multiple functions under the same where clause. All of the function names in the same where clause are in scope in the bodies of those functions. Think about how top-level functions work. So you could write:

noDups :: [[a]] -> Bool
noDups du = and (checkSu du)
   where checkDup []     = []
         checkDup (x:xs) = checkRow x ++ checkDup xs

         checkRow []     = [] 
         checkRow (x:xs) = [x /= y | y <- xs] ++ checkRow xs

In fact this is much more clear, because in your version when you bind x in checkDup, that x is still in scope in the second where clause, yet you are binding the arguments of checkRow to the same name. I think this will probably make GHC complain, and it's certainly confusing.

jberryman
+2  A: 

Leaving aside some of the details of your particular example (the names are not expecially well chosen), I'm a big fan of where clauses:

  • A function defined in a where clause can be better than a top-level function because a reader knows that the scope of the function is limited---it can be used in just a few places.

  • A function defined in a where clause can capture parameters of an enclosing function, which often makes it easier to read

In your particular example, you don't need to nest the where clauses---a single where clause will do, because functions defined in the same where clause are all mutually recursive with one another. There are other things about the code that could be improved, but with the single where clause I like the large-scale structure fine.

N.B. It is not necessary to indent the where clauses as deeply as you are doing.

Norman Ramsey
+1  A: 

Haskell allows you to refer to things defined in a where clause from within the where clause (same thing as a let binding). In fact a where clause is merely syntactic sugar for a let binding which allow multiple definitions and mutual references among other things.

an example is in order.

noDups :: [[a]] -> Bool
noDups du = and (checkDup du)
    where
       checkDup []     = []
       checkDup (x:xs) = checkRow x ++ checkDup xs
           where
               checkRow []     = [] 
               checkRow (x:xs) = [x /= y | y <- xs] ++ checkRow xs

becomes

noDups :: [[a]] -> Bool
noDups du = and (checkDup du)
    where
        checkDup []     = []
        checkDup (x:xs) = checkRow x ++ checkDup xs
        --checkDup can refer to checkRow
        checkRow []     = [] 
        checkRow (x:xs) = [x /= y | y <- xs] ++ checkRow xs

becomes

noDups :: [[a]] -> Bool
noDups du = 
    let checkDup []     = []
        checkDup (x:xs) = checkRow x ++ checkDup xs
        --checkDup can refer to checkRow
        checkRow []     = [] 
        checkRow (x:xs) = [x /= y | y <- xs] ++ checkRow xs

    in  and (checkDup du)
barkmadley
+1  A: 

I feel the same as Norman about keeping the global scope clean. The more functions you expose within your module, the more clumsy that namespace becomes. On the other hand, having a function within the global scope of your module makes it reusable.

I think you can make a clear distinction. Some functions are principal to a module, they contribute directly to the api. There are also functions that when they show up in the module documentation leaves the reader wondering what that particular function has to do with the purpose of the module. That's clearly a helper function.

I would say that such an helper function should on first hand be a subordinate of the calling function. If this helper function is to be reused within the module, decouple this helper function from the calling function by making it a directly accessible function of the module. You will likely not export this function in the module definition though.
Let's call this a FP-style refactoring.

It's a pity that there doesn't exist a "code complete"-like book for functional programming. I think the reason is there is too little industry practice. But let us collect the wisdom on stackoverflow :D

Exception e