Reputation: 55
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
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 IORef
s, 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
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:
-1
, then we can just pass an empty list []
back, wrap it inside the IO
by return []
-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