1

I'm very new to Haskell and am trying to write a simple function that will take an array of integers as input, then return either the product of all the elements or the average, depending on whether the array is of odd or even length, respectively.

I understand how to set a base case for recursion, and how to set up boolean guards for different cases, but I don't understand how to do these in concert.

arrayFunc :: [Integer] -> Integer                                                                                       
arrayFunc [] = 1                                                                                                        
arrayFunc array                                                                                                           
| (length array) % 2 == 1 = arrayFunc (x:xs) = x * arrayFunc xs                                                     
| (length array) % 2 == 0 = ((arrayFunc (x:xs) = x + arrayFunc xs) - 1) `div` length xs 

Currently I'm getting an error

"parse error on input '='
Perhaps you need a 'let' in a 'do' block?"

But I don't understand how I would use a let here.

Will Ness
  • 70,110
  • 9
  • 98
  • 181
Watakashi
  • 13
  • 2
  • Your parse error is on the second `=` in `= arrayFunc (x:xs) = x * arrayFunc xs`, in fact I can't really tell what you meant by this. As for the more general problem, I recommend writing separate functions for the product and the average, which you can use recursion for if you want to (although that would be slightly tricky for the average), and then just use the guards in `arrayFunc` to decide which of the two functions to use. – Robin Zigmond Oct 15 '19 at 23:14

3 Answers3

1

The reason you have guards is because you are trying to determine the length of the list before you actually look at the values in the list.

Rather than make multiple passes (one to compute the length, another to compute the sum or product), just compute all of the values you might need, as you walk the list, and then at the end make the decision and return the appropriate value:

arrayFunc = go (0, 1, 0, True)
  where go (s, p, len, parity) [] = 
               if parity  then  (if len /= 0 then s `div` len else 0) 
                          else  p
        go (s, p, len, parity) (x:xs) = 
               go (s + x, p * x, len + 1, not parity) xs

There are various things you can do to reduce memory usage, and the recursion is just reimplementing a fold, but this gives you an idea of how to compute the answer in one pass.

Will Ness
  • 70,110
  • 9
  • 98
  • 181
chepner
  • 497,756
  • 71
  • 530
  • 681
  • I know the question is about the syntax; I'm pointing out that you can be more efficient *and* avoid the syntax issue altogether. – chepner Oct 16 '19 at 12:54
0

Define an auxiliary inner function like that:

arrayFunc :: [Integer] -> Integer
arrayFunc [] = 1
arrayFunc array
  | (length array) % 2 == 1  =  go1 array
  | (length array) % 2 == 0  =  go2 array
  where
    go1 (x:xs)  =  x * go1 xs
    go2 (x:xs)  =  ((x + go2 xs) - 1) `div` length xs 

This deals only with the syntactical issues in your question. In particular, [Integer] is not an array -- it is a list of integers.

But of course the name of a variable doesn't influence a code's correctness.

Will Ness
  • 70,110
  • 9
  • 98
  • 181
  • Thank you very much for this response, I was unaware of "where" but it seems like exactly what I wanted. I implemented these changes and am now getting a "couldn't match type 'Integer' with 'Int'" error on the call of "go2 array". I don't think so, but could that have to do with what you were saying about [Integer] being a list rather than an array? I wasn't sure how to declare an array as an input for a function so [Integer] was my best guess. – Watakashi Oct 15 '19 at 23:44
  • yes, it is possible to code this with `let` but with `where` it is much easier because it does the required transformation by itself. – Will Ness Oct 15 '19 at 23:46
  • if you weren't told to use `Integer`, then make the input `[Int]` instead and that type error will go away. It's slightly annoying that the `length` function returns an `Int`, and `div` expects its two arguments to be the same type. – Robin Zigmond Oct 16 '19 at 00:10
  • I was told to use Integer, but thank you for pointing out that it was length that was returning an Int. Now I can go look into how I can maybe recast that as an Integer. Does Haskell do recasting? I don't know, guess I'll see. – Watakashi Oct 16 '19 at 00:12
  • 1
    Not automatically, but there are usually functions to do so. In this case you would want [fromIntegral](https://hackage.haskell.org/package/base-4.12.0.0/docs/Prelude.html#v:fromIntegral). (There is also [genericLength](https://hackage.haskell.org/package/base-4.12.0.0/docs/Data-List.html#v:genericLength) to compute the length of a list as an arbitrary numeric type - you have to import the `Data.List` module to use that one.) – Robin Zigmond Oct 16 '19 at 00:31
  • 1
    `genericLength` is a bit controversial as it could be [quite inefficient](https://hackage.haskell.org/package/base-4.12.0.0/docs/src/Data.OldList.html#genericLength). so it'll be ``((x + go2 xs) - 1) `div` fromIntegral (length xs)``. – Will Ness Oct 16 '19 at 05:26
  • 1
    BTW `%` is not the mod operator in Haskell. It is the `mod` function (which can be written infix as ``x `mod` y``) – luqui Oct 16 '19 at 08:12
0

Without focus on recursion this should be an acceptable solution:

arrayFunc :: (Integral a) => [a] -> a
arrayFunc ls
    | n == 0     = 1
    | even n     = (sum ls) `div` (fromIntegral n)
    | otherwise  = product ls
    where
    n     = length xs 
Elmex80s
  • 3,428
  • 1
  • 15
  • 23