考虑以下Haskell程序。我试图以“流式风格”编程,其中函数在流(此处简单地实现为列表)上操作。像normalStreamFunc这样的函数在惰性列表上运作得很好。我可以将无限列表传递给normalStreamFunc,并有效地获得另一个无限列表,但每个值都映射到一个函数上。像effectfulStreamFunc这样的函数效果不太好。IO操作意味着我需要在提取单个值之前对整个列表进行评估。例如,程序的输出是这样的:
a
b
c
d
"[\"a\",\"b\"]"
但我想要的是一种编写 effectfulStreamFunc 的方式,使程序产生以下结果:
a
b
"[\"a\",\"b\"]"
剩余操作未被评估。我可以想象使用unsafePerformIO来解决问题,但我们先不考虑它。以下是程序:
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
更新:
感谢大家提供的有用和周到的反馈。我以前没有见过sequence
运算符,这对我很有帮助。我曾经考虑过一种(不太优雅)的方法来传递IO(字符串)值而不是字符串,但对于我想要对字符串本身进行操作的编程风格来说,其使用价值有限,因为我希望其他流函数作用于字符串本身,而不是能生成字符串的操作。但是,通过思考其他回复,我认为我明白了为什么这在一般情况下是无法解决的。在我提出的简单情况中,我真正想要的是sequence
运算符,因为我认为流的顺序意味着动作的顺序。实际上,并不一定暗示这样的顺序。当我思考一个将两个流作为输入的流函数时(例如,对两个流进行成对相加),如果两个“输入”流都执行了IO,则这些IO操作的顺序是未定义的(除非我们在IO单子中自行定义它)。问题得到解决,感谢大家!