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!
-
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.
-
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 runsequence
on those in main do the effects actually occur.Nathan Sanders : 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 xs I think it reads a little better.Jesse Rusak : Good point. I guess the do syntax is orthogonal to the issue of actually running the monad. -
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 thereturn
, 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\"]"
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.