[Haskell-cafe] Re: Hiding side effects in a data structure

ChrisK haskell at list.mightyreason.com
Tue Oct 30 15:28:47 EDT 2007


Ketil Malde wrote:
> I've done something similar, I think.  Often, I want to output some
> kind of progress indicator, just to show that the program is working.
> Typically, the program works by lazily evaluating a list (lines from
> an input file, say); each element of the list is wrapped with an IO
> action that outputs the status when evaluated -- which typically
> happens lazily from pure code. 
> 
>> countIO :: String -> String -> Int -> [a] -> IO [a]
>> countIO msg post step xs = sequence $ map unsafeInterleaveIO ((blank >> outmsg (0::Int) >> c):cs)
>>    where (c:cs) = ct 0 xs
>>          output   = hPutStr stderr
>>          blank    = output ('\r':take 70 (repeat ' '))
>>          outmsg x = output ('\r':msg++show x) >> hFlush stderr
>>          ct s ys = let (a,b) = splitAt (step-1) ys
>>                        next  = s+step
>>                    in case b of [b1] -> map return a ++ [outmsg (s+step) >> hPutStr stderr post >> return b1]
>>                                 []   -> map return (init a) ++ [outmsg (s+length a) >> hPutStr stderr post >> return (last a)]
>>                                 _ -> map return a ++ [outmsg s >> return (head b)] ++ ct next (tail b)
> 
> -k


Your use of unsafeInterleaveIO is just not quite correct.  A quick series of
examples:

Let me define this function:

> unsafeSequenceIO :: [ IO a ] -> IO [a]
> unsafeSequenceIO [] = return []
> unsafeSequenceIO (x:xs) = unsafeInterleaveIO $ do
>   this <- x
>   rest <- unsafeSequenceIO xs
>   return (this:rest)

And an infinite [ IO a ]

> todo :: [ IO Int ]
> todo = map return [0..]

These diverge

> *Main> liftM (take 10) (sequence todo)
> *Main> liftM (take 10) (sequence (map unsafeInterleaveIO todo))

This is finite:

> *Main> liftM (take 10) (unsafeSequenceIO todo)
> [0,1,2,3,4,5,6,7,8,9]

An alternate definition of unsafeSequenceIO, which is not quite the same but
still works, is

> unsafeSequenceIO' :: [ IO a ] -> IO [a]
> unsafeSequenceIO' [] = return []
> unsafeSequenceIO' (x:xs) = do
>   this <- x
>   rest <- unsafeInterleaveIO' (unsafeSequenceIO' xs)
>   return (this:rest)

The two definitions differ only very slightly in how lazily the very first
element is handled:

> *Main> (unsafeSequenceIO (error "boom":[]) >> print "ok")
> "ok"
> *Main> (unsafeSequenceIO' (error "boom":[]) >> print "ok")
> *** Exception: boom



More information about the Haskell-Cafe mailing list