user2948358
user2948358

Reputation: 55

Passing a list as Parameter in Haskell

I am a beginner to functional programming and Haskell as a programming language. After given an input of numbers from the command line I want to put those numbers into a list, then pass that list as a parameter to calculate its sum. Here's what I am working on:

import Data.List

iotxt :: IO ()

main :: IO ()

l1 = []


iotxt = do a <- getLine

        -- read in numbers that are not equal to -1
           insert (read a) l1

           if (not ((read a) == -1.0)) 
               then iotxt
           else do return ()
main = do

        putStrLn("Enter a number [-1 to quit]")
        iotxt

        -- size of the list 
        print(length [l1])
        -- sum 

But when I attempt to place the values inside the list I get this error:

Couldn't match expected type `IO a0' with actual type `[a1]'
    In the return type of a call of `insert'
    In a stmt of a 'do' block: insert (read a) l1
    In the expression:
      do { a <- getLine;
           insert (read a) l1;
           if (not ((read a) == - 1.0)) then iotxt else do { return () } }

Upvotes: 0

Views: 1935

Answers (2)

leftaroundabout
leftaroundabout

Reputation: 120751

There are multiple things wrong with you're code.

Starting from the bottom up, first, length [l1] doesn't make sense. Any [ ] with only one item in between is just that: a list with a single item, so the length will always be 1. You certainly mean length l1 here, i.e. length of the list l1, not length of the list ᴄᴏɴᴛᴀɪɴɪɴɢ only l1.

Next, you have this iotxt and try to make it modify the "global variable" l1. You can't do that, Haskell does not have any such thing as mutable global variables – for good reasons; global mutable state is considered evil even in imperative languages. Haskell kind of has local variables, through IORefs, but using those without good reason is frowned upon. You don't really need them for something like this here.

The correct thing to do is to scrap this global l1 binding, forget about mutating variables. Which brings us to the question of how to pass on the information acquired in iotxt. Well, the obvious functional thing to do is, returning it. We need to make that explicit in the type (which is again a good thing, so we actually know how to use the function):

ioTxt :: IO [Int]

Such a function can then nicely be used in main:

main :: IO ()
main = do
    putStrLn "Enter a number [-1 to quit]"
    l1 <- ioTxt
    print $ length l1

You see: almost the same as your approach, but with proper explicit introduction of l1 where you need it, rather than somewhere completely different.

What's left to do is implementing ioTxt. This now also needs a local l1 variable since we have scrapped the global one. And when you implement a loop as such a recursive call, you need to pass an updated version of it to each instantiation. The recursion itself should be done on a locally-defined function.

ioTxt :: IO [Int]
ioTxT = go []  -- Start with empty list. `go` is a widespread name for such simple "loop functions".
 where go l1 = do
         a <- getLine
         let x = read a :: Int
         case x of
          (-1) -> return l1
          _    -> go (insert x l1)

In the last line, note that insert does not modify the list l1, it rather creates a new one that equals the old one except for having the new element in it. And then passes that to the next loop-call, so effectively you get an updated list for each recursive call.

Also note that you probably shouldn't use insert here: that's specifically for placing the new element at the right position in an ordered list. If you just want to accumulate the values in some way, you can simply use

          _    -> go $ x : l1

Upvotes: 3

Javran
Javran

Reputation: 3434

In haskell, there are no variables, everything is immutable. So you shouldn't define l1 and change its value later, which doesn't work.

Instead you should think how to write iotxt properly and let it collect elements and pass the input list back to your main.

In your iotxt, you can think about these two situations:

  • if the input is -1, then we can just pass an empty list [] back, wrap it inside the IO by return []
  • if the input is not -1, we can first store this value somewhere, say result. After that, we should call iotxt recursively and let this inner iotxt handle the rest of the inputs. Finally, we will get return value rest, then we just simply put result and rest together.

Moreover, you can think of IO a like some program (which might have side effects like reading from input or writing to output) that returns a value of type a. According to the definition of your iotxt, which is a program that reads input until meeting a -1 and gives you the list in return, the type signature for iotxt should be IO [Float].

There's a length [l1] in your code, which constructs a list with only one element(i.e. l1) and thus length [l1] will always return 1. Instead of doing this, you can say length l1, which calculates the length of l1.

Finally, you don't need to group function arguments by parenthese, just simply say f x if you want to call f with x.

I've modified your code a little bit, hope it can give you some help.

import Data.List

-- iotxt reads from stdin, yields [Float]
-- stop reading when "-1" is read
iotxt :: IO [Float]

main :: IO ()

iotxt = do
    a <- getLine
    -- read in numbers that are not equal to -1
    let result = (read a) :: Float
    if (not (result == -1.0)) 
        then do
            -- read rest of the list from stdin
            rest <- iotxt
            -- put head & tail together
            return $ result:rest
        else do return []

main = do
    putStrLn("Enter a number [-1 to quit]")
    l1 <- iotxt
    -- size of the list 
    print $ length l1
    -- sum 
    print $ sum l1

Upvotes: 2

Related Questions