Search code examples
haskellglobal-variables

Global Variable “total” Does Not Update Properly


I'm working on an assignment to calculate the sum of all integers in a list. I am supposed to do so without help from any standard library functions other than the standard addition operator.

I assume this means I cannot use length. I'm understanding that correct, right? This becomes and issue as I'm not sure how I'd know when to stop my recursive function to iterate through the array.

The input's a should be expected to deal with are [] and [0-X] where x is any integer. The example has X as 10, so don't expect anything huge to be tested.

The assignment mentioned it should be in the following format, but I'm not sure I follow:

sum1 []     = ...
sum1 (x:xs) = ...

Here's what I have utilizing length. It works correctly, and I don't really care if it's inefficient. It's my first time using Haskell:

iterate_list :: [Int] -> Int -> Int -> IO()
iterate_list func_list index total = do
    if index < length func_list
    then do
        let new_total = total + (func_list !! index)
        let new_index = index + 1
        iterate_list func_list new_index new_total
    else 
        print(total)
        

sum1 :: [Int] -> IO()
sum1 list = do
    if length list < 1
    then do
        print(0)
    else
        iterate_list list 0 0

update: Based on comments, here is the code I've produced.

total :: Int
total = 0

sum1 :: [Int] -> IO()
sum1 (x:xs) = do
    if xs == []
    then do
        print(total)
    else do
        let total = total + x
        sum1 xs

However, the issue I'm having now is total returns 0, almost like it's a constant. I might be programming it that way, but I'm not too sure what's going on.

Based on the assignment description, I cannot pass a variable through the recursive function to store this value. I've done it that way before. Does anyone know if there is a way to have a "total" variable outside of the function.


Solution

  • total :: Int
    total = 0
    
    sum1 :: [Int] -> IO()
    sum1 (x:xs) = do
        if xs == []
        then do
            print(total)
        else do
            let total = total + x
            sum1 xs
    

    What this code says:

    • The global total is a constant integer, equal to 0

    • sum1 takes a list of integers and produces an IO action that produces no result

    • If sum1 is given a non-empty list, then:

      • If the tail of that list is empty (i.e., the whole list has 1 element), then print the global variable total

      • Otherwise:

        • Create a new local variable named total, hiding the global one, and define it as x plus itself (an infinite loop)

        • Recursively call sum1 on the tail of the list

    • If sum1 is given an empty list, it will throw an error

    This shows that you’re thinking very imperatively. Rather than trying to define a bottom-up procedure for updating the total incrementally until it builds up to the final result, you need to think in terms of how to compute the total as a value by breaking down the input. Variables in Haskell are immutable; when you write =, it means equal, never “assign” or “update”.

    First, sum1 should return Int because you don’t need IO or do notation for this.

    sum1 :: [Int] -> Int
    

    If you want to print the result of sum1 applied to some list someList (for example from main), use print there, i.e., print (sum1 someList).

    Next, the function should be defined in terms of the the two possible cases of the input: an empty list and a non-empty list.

    sum1 [] = …
    
    sum1 (x : xs) = …
    

    You need to define these cases so that an input like sum1 [1, 2, 3, 4], which you’ll recall is syntactic sugar for sum1 (1 : (2 : (3 : (4 : [])))) produces something equivalent to 1 + 2 + 3 + 4.

    First, in the case of an empty list, what should the result be? You can deduce this from the fact that the sum of two lists appended together should be the same as the sum of each of them separately; that is, for any lists xs and ys, these expressions should produce the same result:

    sum1 xs + sum1 ys
    sum1 (xs ++ ys)
    

    Supposing xs or ys is empty, it should not change the sum:

    • sum1 [] + sum1 ys = sum1 ([] ++ ys) = sum1 ys
    • sum1 xs + sum1 [] = sum1 (xs ++ []) = sum1 xs

    Second, as for the non-empty case: you’re given an element x :: Int and a list xs :: [Int], and you need to compute the total of the two. For example, given [1, 2, 3, 4], x is set to 1 and xs is [2, 3, 4]. Suppose you had the sum of xs already; what is the result in terms of that and x? And how can you obtain the sum of xs?