3 ms·
In Haskell it is still of course pure functional programming to have a lazy stream of IO actions. The types make it clear when and where the IO is happening, wh
by willtim 9y ago
In Haskell it is still of course pure functional programming to have a lazy stream of IO actions. The types make it clear when and where the IO is happening, which makes reasoning about effectful streaming much easier.
- kerkeslager 9y agoI don't think it really makes sense to call the way side effects happen in Haskell "lazy". The way I think about it is that it's not so much that your program executes a lazy stream of side effects, it's that your program doesn't execute side effects at all. Instead, your program returns a pure structure which describes how side effects should be executed, and the runtime executes a program based on this description. By the time the runtime is executing that program, the pure structure which describes how the side effects should be executed has already been forced, so the side effects happen eagerly within that context. That said, I'm not much of a Haskell-er, so I'd be interested to hear from more experienced Haskell programmers on whether there's a better way to think about this.
- willtim 9y ago> I don't think it really makes sense to call the way side effects happen in Haskell "lazy". I didn't actually say this. I was suggesting a lazy chain of IO actions for such problems. For example, ListT a IO, instead of a regular lazy list with hidden side-effects. The Haskell types help make the distinction clear. > Instead, your program returns a pure structure which describes how side effects should be executed Yes exactly, the pure structure can still be computed lazily on demand, providing IO actions. My favourite Hackage streaming library for Haskell is "streaming" and it can work exactly this way.
- Tarean 9y agoWorth noting that ListTs bind isn't associative for most base monads including IO and it is deprecated for a reason. Also, by it's nature ListT can't stream IO unless you use lazy IO aka lazyRead aka unsafeInterleaveIO which deserves its name.
- willtim 9y agoI am assuming "ListT done right" not the deprecated one (which is not even a chain of actions). Here is a definition, to be completely clear: newtype ListT m a = ListT (m (Maybe (a, ListT m a))) It is a nice simple example of an effectful streaming type. > Also, by it's nature ListT can't stream IO This is only true of the deprecated one, which is also misnamed. The community is rightly re-using the name for the behaviour shown above. See list-t on hackage, or the implementation that comes with Pipes.
- wz1000 9y agoHaskell does have lazy side effects(unsafeInterleaveIO and its derivatives), which is a major source of complaints about the haskell standard library(lazy IO). There is a huge library ecosystem(conduit, pipes, streaming, machines, what have you) to solve this problem. For example, this code will fail because the file handle is closed prematurely by withFile as hGetContents returns immediately without actually reading the file, deferring that part until the data is forced. main = do contents <- withFile "foo.txt" ReadMode hGetContents putStrLn $ length contents
- taeric 9y agoI'm assuming there is a more idiomatic (nay, correct?) way of doing this in Haskell?
- danidiaz 9y agoOne possible solution is to take a "beautiful fold" http://hackage.haskell.org/package/foldl-1.3.7/docs/Control-Foldl-ByteString.html#v:length http://hackage.haskell.org/package/foldl-1.3.7/docs/Control-... and connect it to an effectful stream of bytes http://hackage.haskell.org/package/pipes-bytestring-2.1.6/docs/Pipes-ByteString.html#v:fromHandle http://hackage.haskell.org/package/pipes-bytestring-2.1.6/do... inside the callback of "withFile". Not unlike the Streams/Collectors framework in Java.
- taeric 9y agoBut... does that work? Doesn't that leave you open to having something evaluate the end of the "withFile" before you evaluated something in the callback? That is, the only reason what you said is accurate, is because you said "effectful stream of bytes" meaning "eagerly effectful." Right? If you do all of that lazily, you can just as easily shoot yourself in the foot here. (Please nobody take this as a damning criticism of Haskell. I'm not intending it that way.)
- danidiaz 9y agoThe streams/sinks work in the IO monad and that induces an ordering of the effects, yes. Within the callback, you can still do things like filtering or taking only a part of the stream. Returning the stream itself from the "withFile" and trying to run it later still is still a bug. I like this approach better that conventional Haskell lazy I/O, which I personally find quite confusing.