# Populating a list with user input

I am trying to learn me some Haskell because I found it to be an interesting language. Right now, I am trying to implement a function in Haskell to get acquainted with the language. The function just gets user input and adds the input numbers to a list of floats.

get_budget_values _values _index _length = do
tmp_value <- getLine

let new_value = read tmp_value :: Float
let new_values = new_value : _values
let new_index = _index + 1

if new_index < _length
then get_budget_values new_values new_index _length
else return new_values


So, is this a proper way to implement a function like that in Haskell? What beginner mistakes am I making? Are there any standard ways of doing something like this?

# Add type signatures to top-level functions

You should always add a type signature to top-level functions:

get_budget_values :: [Float] -> Int -> Int -> IO [Float]
get_budget_values _values _index _length = do
...


# Make functions easy to use and hard to misuse

However, this function's type is weird. We want to get _length values, so why do we have to supply them first? That's error prone. Our funtion should have the type Int -> IO [Float] instead:

get_budget_values :: Int -> IO [Float]
get_budget_values n = go [] 0 n
where
go values index len = do
tmp_value <- getLine

let new_values = new_value : values
let new_index = index + 1

if new_index < len
then go new_values new_index len
else return new_values


Note that due to get_budget_values' type signature we don't have to use :: Float at read temp_value anymore.

# Follow naming conventions and use existing library functions

Still, our function is sub-par, since we don't follow the common naming convention (camelCase) on the one hand don't use library functions for our advantage.

So let's say we have doNTimes :: Int -> IO a -> IO [a] so that doNTimes n action repeats the action n times and collects the results in a list. Then we can write

getBudgetValues :: Int -> IO [Float]
getBudgetValues n = doNTimes n getSingleFloat
where
getSingleFloat = do
tmpValue <- getLine


The getLine followed by read approach is so common that there is a function for that, namely readLn:

    getSingleFloat = readLn


We end up with

getBudgetValues :: Int -> IO [Float]
getBudgetValues n = doNTimes n readLn


What's missing is doNTimes. Now, we know that there is replicate :: Int -> a -> [a], which is almost correct, but we would end up with [IO Float] instead of IO [Float]. But there's also sequence :: [IO a] -> IO [a].*

Therefore, we can write

doNTimes :: Int -> IO a -> IO [a]
doNTimes n action = sequence (replicate n action)


# The final result

This function is also often used and available as replicateM in Control.Monad. We end up with

import Control.Monad (replicateM)

getBudgetValues :: Int -> IO [Float]
getBudgetValues n = replicateM n readLn


# Exercises

Write replicate and sequence yourself without looking at their respective source code. replicate :: Int -> a -> [a] should fulfil the following properties for positive n:

let result = replicate n x
in length result == n && all (== x) result


For any non-positive n, the list should be empty.

sequence :: [IO a] -> IO [a] should execute the actions and collect their results, e.g.

sequence [putStrLn "Hello", putStrLn "World"]


should print Hello and World on two lines and return [(),()].

* the actual types are more general

You just want call getLine function length_ times? It is as simple as

values <- (map read) <$> replicateM length_ getLine :: IO [Float]  Here replicateM c a performs a action c times. The replicateM length_ getLine expression returns an action that produces a list of strings, IO [String]. Using <$> operator (aka fmap) you can apply a function to [String] part of that IO [String] thing. Finally, the function we apply is map read converts a String into a Float, because we explicitly wrote the type of the whole expression.

It might be useful to you to see how replicateM itself is implemented:

replicateM cnt f =
loop cnt
where
loop cnt = if cnt <= 0 then return [] else do
first <- f
leftover <- loop (cnt - 1)
return (f:leftover)

• I don't know what I am doing wrong, but the implementation you provided crashes after I enter the number 2 – Joan Vene Mar 9 '18 at 22:38
• Crashes? Any error messages? – arrowd Mar 10 '18 at 7:52
• I had imported the wrong module – Joan Vene Mar 14 '18 at 0:07