views:

480

answers:

3

Consider the following Haskell program. I am trying to program in a "stream style" where functions operate on streams (implemented here simply as lists). Things like normalStreamFunc work great with lazy lists. I can pass an infinite list to normalStreamFunc and effectively get out another infinite list, but with a function mapped onto each value. Things like effectfulStreamFunc do not work so well. The IO action means that I need to evaluate the entire list before I can pull off individual values. For example, the output of the program is this:

a
b
c
d
"[\"a\",\"b\"]"

but what I want is a way to write effectfulStreamFunc so that the program produces this:

a
b
"[\"a\",\"b\"]"

leaving the remaining actions unevaluated. I can imagine a solution using unsafePerformIO, but let's say I am taking that off the table. Here is the program:

import IO

normalStreamFunc :: [String] -> [String]
normalStreamFunc (x:xs) = reverse(x) : normalStreamFunc xs

effectfulStreamFunc :: [String] -> IO [String]
effectfulStreamFunc [] = return []
effectfulStreamFunc (x:xs) = do
    putStrLn x
    rest <- effectfulStreamFunc xs
    return (reverse(x):rest)

main :: IO ()
main = do
     let fns = ["a", "b", "c", "d"]
     es <- effectfulStreamFunc fns
     print $ show $ take 2 es

Update:

Thank you all for the helpful and thoughtful feedback. I had not seen the sequence operator before, that is helpful to know about. I had thought of a (less elegant) way to pass around IO (String) values instead of Strings, but for the style of programming that is of limited usefulness, since I want to other stream functions to act on the strings themselves, not on actions that can produce a string. But, based on thinking through the other responses, I think I see why this is unsolvable in general. In the simple case I presented, what I really wanted was the sequence operator, since I was thinking that the stream ordering implied an ordering on the actions. In fact, no such ordering is necessarily implied. This becomes clearer to me when I think about a stream function that takes two streams as input (e.g. pairwise addition two streams). If both "incoming" streams performed IO, the ordering of those IO actions is undefined (unless, of course, we define it by sequencing it ourselves in the IO monad). Problem solved, thank you all!

+2  A: 

I do not really understand your main goal but your usage of putStrLn results in the evaluation of the entire list because it will evaluate the argument when performed. Consider

import IO

normalStreamFunc :: [String] -> [String]
normalStreamFunc (x:xs) = reverse(x) : normalStreamFunc xs

effectfulStreamFunc :: [String] -> IO [String]
effectfulStreamFunc [] = return []
effectfulStreamFunc (x:xs) = do
    rest <- effectfulStreamFunc xs
    return (reverse(x):rest)

main :: IO ()
main = do
     let fns = ["a", "b", undefined,"c", "d"]
     es <- effectfulStreamFunc fns
     print $ show $ take 2 es

this results in "[\"a\",\"b\"]", while using the putStrLn version it results in an exception.

Tomh
+7  A: 

How about this code:

import IO

normalStreamFunc :: [String] -> [String]
normalStreamFunc (x:xs) = reverse(x) : normalStreamFunc xs

effectfulStreamFunc :: [String] -> [IO (String)]
effectfulStreamFunc [] = []
effectfulStreamFunc (x:xs) =
    let rest = effectfulStreamFunc xs in
        (putStrLn x >> return x) : rest

main :: IO ()
main = do
     let fns = ["a", "b", "c", "d"]
     let appliedFns = effectfulStreamFunc fns
     pieces <- sequence $ take 2 appliedFns
     print $ show $ pieces

Rather than effectfulStreamFunc actually doing any IO, this one instead creates a list of IO actions to perform. (Note the type signature change.) The main function then takes 2 of those actions, runs them and prints the results:

a
b
"[\"a\",\"b\"]"

This works because the type IO (String) is just a function/value like any other which you can put into a list, pass around, etc. Note that the do syntax doesn't occur in "effectfulStreamFunc" - it is actually a pure function, despite the "IO" in its signature. Only when we run sequence on those in main do the effects actually occur.

Jesse Rusak
If you still like the do notation, you could write the second clause of effectfulStreamFunc as:effectfulStreamFunc (x:xs) = let putAction = do putStrLn x return x in putAction : effectfulStreamFunc xsI think it reads a little better.
Nathan Sanders
Good point. I guess the do syntax is orthogonal to the issue of actually running the monad.
Jesse Rusak
+1  A: 

As mentioned by Tomh, you can't really do this "safely", because you're breaking the referential transparency in Haskell. You're trying to perform side effects lazily, but the thing about laziness is that you aren't guaranteed in what order or whether things get evaluated, so in Haskell, when you tell it to perform a side effect, it is always performed, and in the exact order specified. (i.e. in this case the side effects from the recursive call of effectfulStreamFunc before the return, because that was the order they were listed) You can't do this lazily without using unsafe.

You can try using something like unsafeInterleaveIO, which is how lazy IO (e.g. hGetContents) is implemented in Haskell, but it has its own problems; and you said that you don't want to use "unsafe" stuff.

import System.IO.Unsafe (unsafeInterleaveIO)

effectfulStreamFunc :: [String] -> IO [String]
effectfulStreamFunc [] = return []
effectfulStreamFunc (x:xs) = unsafeInterleaveIO $ do
    putStrLn x
    rest <- effectfulStreamFunc xs
    return (reverse x : rest)

main :: IO ()
main = do
     let fns = ["a", "b", "c", "d"]
     es <- effectfulStreamFunc fns
     print $ show $ take 2 es

In this case the output looks like this

"a
[\"a\"b
,\"b\"]"
newacct