Reputation: 185
I have the following function which should adds up all numbers in a given list up to the current position. For example, subtotal [1, 2, 3, 4] = [1, 3, 6, 10]
, because 1 = 1, 1 + 2 = 3, 1 + 2 + 3 = 6 and 1 + 2 + 3 + 4 = 10.
Here's my code:
subtotal :: Num a => [a] -> [a]
subtotal [] = []
subtotal xs = [y | n <- [1..length xs], y <- sum(take n xs)]
The problem is Im getting this error:
cw.hs:3:46: error:
* Couldn't match expected type `[a]' with actual type `a'
`a' is a rigid type variable bound by
the type signature for:
subtotal :: forall a. Num a => [a] -> [a]
at cw.hs:1:1-31
* In the expression: sum (take n xs)
In a stmt of a list comprehension: y <- sum (take n xs)
In the expression:
[y | n <- [1 .. length xs], y <- sum (take n xs)]
* Relevant bindings include
xs :: [a] (bound at cw.hs:3:10)
subtotal :: [a] -> [a] (bound at cw.hs:2:1)
|
3 | subtotal xs = [y | n <- [1..length xs], y <- sum(take n xs)]
| ^^
How can I fix it?
Upvotes: 3
Views: 851
Reputation: 370455
In a list comprehension, the statement y <- ys
means that ys
is a list and y
iterates over the elements of that list. So y <- sum (take n xs)
means that sum
produces a list and you iterate over that list. But sum
only produces a single number, not a list; so you get an error.
You want to use let
instead of <-
to assign simple expressions:
subtotal xs = [y | n <- [1..length xs], let y = sum(take n xs)]
Or just put the expression directly into the head of the comprehension:
subtotal xs = [sum (take n xs) | n <- [1..length xs]]
PS: Your first case (for the empty list) is redundant. It's just a special case of the second.
Upvotes: 5
Reputation: 477804
sum(take n xs)
is not a list, so you can not iterate over it. In order to add the result of an expression to a list you put the expression in the head of the list comprehension:
subtotal :: Num a => [a] -> [a]
subtotal xs = [sum (take n xs) | n <- [1..length xs]]
But you here use length
. length
is a bit a dangerous function since lists can be infinite, and then your function will simply keep looping, and never return an element. You can use inits
however to obtain all the lists:
Prelude Data.List> inits [1,4,2,5]
[[],[1],[1,4],[1,4,2],[1,4,2,5]]
So you can use:
import Data.List(inits)
subtotal :: Num a => [a] -> [a]
subtotal xs = [sum sub | sub <- tail (inits xs)]
But this is still not very effectively: now we calculate the sum of every sublist. But a problem here is that we calculate the sum of every list separately. This is weird since the sum of the i-th result is simply the sum of the i-1-th result plus the i-th element of xs
. We can use scanl1 :: (a -> a -> a) -> [a] -> [a]
:
subtotal :: Num a => [a] -> [a]
subtotal xs = scanl1 (+) xs
or even more simple and elegant:
subtotal :: Num a => [a] -> [a]
subtotal = scanl1 (+)
Upvotes: 8