Haskell has an equivalent of with
for files, it's called withFile
. This:
with open("file1", "w") as f:
with open("file2", "r") as g:
k = g.readline()
f.write(k)
is equivalent to:
withFile "file1" WriteMode $ \f ->
withFile "file2" ReadMode $ \g ->
do k <- hGetLine g
hPutStr f k
Now, withFile
might look like something monadic. Its type is:
withFile :: FilePath -> IOMode -> (Handle -> IO r) -> IO r
right side looks like (a -> m b) -> m b
.
Another similarity: In Python you can skip as
, and in Haskell you can use >>
instead of >>=
(or, a do
block without <-
arrow).
So I'll answer this question: is withFile
monadic?
You could think that it can be written like this:
do f <- withFile "file1" WriteMode
g <- withFile "file2" ReadMode
k <- hGetLine g
hPutStr f k
But this doesn't type check. And it cannot.
It's because in Haskell the IO monad is sequential: if you write
do x <- a
y <- b
c
after a
is executed, b
is executed and then c
. There is no "backtrack"
to clean a
at the end or something like that. withFile
, on the other hand,
has to close the handle after the block is executed.
There is another monad, called continuation monad, which allows to do such
things. However, you have now two monads, IO and continuations, and using effects of two monads at once requires using monad transformers.
import System.IO
import Control.Monad.Cont
k :: ContT r IO ()
k = do f <- ContT $ withFile "file1" WriteMode
g <- ContT $ withFile "file2" ReadMode
lift $ hGetLine g >>= hPutStr f
main = runContT k return
That's ugly. So the answer is: somewhat, but that requires dealing with a lot of subtleties that make the issue rather opaque.
Python's with
can simulate only a limited bit of what monads can do - add entering and finalization code. I don't think you can simulate e.g.
do x <- [2,3,4]
y <- [0,1]
return (x+y)
using with
(it might be possible with some dirty hacks). Instead, use for:
for x in [2,3,4]:
for y in [0,1]:
print x+y
And there's a Haskell function for this - forM
:
forM [2,3,4] $ \x ->
forM [0,1] $ \y ->
print (x+y)
I recommed reading about yield
which bears more resemblance to monads than with
:
http://www.valuedlessons.com/2008/01/monads-in-python-with-nice-syntax.html
A related question: if we have monads, do we need exceptions?
Basically no, instead of a function that throws A or returns B you can make a function that returns Either A B
. The monad for Either A
will then behave just like exceptions - if one line of code will return an error, the whole block will.
However, that would mean that division would have type Integer -> Integer -> Either Error Integer
and so on, to catch division by zero. You would have to detect errors (explicitly pattern match or use bind) in any code that uses division or has even slightest possibility of going wrong. Haskell uses exceptions to avoid doing this.