0

I am new to Haskell and really having trouble with the whole IO thing.

I am trying to find out how long it takes to traverse a list in haskell. I wanted to generate a list of random numbers and pass it as a parameter to a function so that I can print each element of the list. I am using CRITERION package for the benchmark. Here is the code:

{-# LANGUAGE OverloadedStrings #-}
import System.Random
import Control.Exception
import Criterion.Main

printElements [] = return ()
printElements (x:xs) = do print(x)
                          printElements xs

randomList 0 = return []
randomList n = do
  x  <- randomRIO (1,100)
  xs <- randomList (n-1)
  return (x:xs)


main = defaultMain [
  bgroup "printElements" [ bench "[1,2,3]"  $ whnf printElements (randomList 10)
               , bench "[4,5,6]"  $ whnf printElements [4,5,6,4,2,5]
               , bench "[7,8,9]"  $ whnf printElements [7,8,9,2,3,4]
               , bench "[10,11,12]" $ whnf printElements [10,11, 12,4,5]
               ]
  ]

Error when I run the code:

listtraversal.hs:18:67:
    Couldn't match expected type ‘[a0]’ with actual type ‘IO [t0]’
    In the second argument of ‘whnf’, namely ‘(randomList 10)’
    In the second argument of ‘($)’, namely
      ‘whnf printElements (randomList 10)’
AR17
  • 39
  • 4
  • 3
    Try giving each function an explicit type signature. Its a great exercise that is both informative at this stage of learning and helps localize compilation messages throughout all stages of your Haskell learning/use. – Thomas M. DuBuisson May 22 '18 at 16:41
  • 1
    During the benchmark, you will be repeatedly executing `printElements foo` for some `foo`. Do you want `foo` to be different in each execution? – Daniel Wagner May 22 '18 at 16:50
  • BTW with this benchmark you will largely be measuring how long it takes to print things, since generating random numbers and traversing a list take a negligible amount of time in comparison. – luqui May 22 '18 at 19:03

1 Answers1

5

Put briefly, you need to bind your function to the IO value, instead of trying to apply it to the value wrapped inside the IO value.

-- instead of whnf printElements (randomList 10)
randomList 10 >>= whnf printElements

randomList does not return a list of values; it returns an IO action that, when executed, can produce a list of values. Ignoring the various constraints induced by the implementation, the type is

randomList :: (...) => t1 -> IO [t]  -- not t1 -> [t]

As such, you can't directly work with the list of values that the IO action can produce; you need to use the monad instance to bind the value to an appropriate function. whnf printElements is one such function; it takes a list and returns an IO action.

whnf printElements :: Show a => [a] -> IO ()

Instead of pulling the list out and passing it to whnf printElements, we "push" the function into an IO value using >>=. That operator's type, specialized to the IO monad, is

(>>=) :: IO a -> (a -> IO b) -> IO b

In this case, the first IO a value is the IO [t] value returned by randomList. whnf printElements is the a -> IO b function we bind to. The result is a new IO value that take the first IO value, pulls out the wrapped value, applies the given function, and returns the result.

In other words, the IO monad itself takes care of pulling apart the result from randomList and applying your function to it, rather than you doing it explicitly.


(You might have noticed that I've said that >>= binds a value to a function and vice versa. It is perhaps more accurate to say that >>= binds them together into a single IO action.)

chepner
  • 497,756
  • 71
  • 530
  • 681
  • And of course `randomList 10 >>= whnf printElements` can also be written `do { list <- randomList 10; whnf printElements list }`, or `whnf printElements =<< randomList 10`, which makes it a bit clearer that bind is an *application* operator (apply impure function to impure argument) like `$` (pure function to pure argument) and `<$>` (pure function to impure argument). – Jon Purdy May 22 '18 at 23:01