

Haskell IO for Imperative Programmers - benwr
http://www.haskell.org/haskellwiki/Haskell_IO_for_Imperative_Programmers

======
yason
I don't want to come out as a language troll -- I have written enough and much
liked Haskell -- but one thing that Clojure, my favorite language, got right
is multiparadigm programming.

Because I/O is serial and imperative by nature it just so much makes sense to
handle I/O in a serial and imperative way and write the rest of the program in
functional style. The same can be said of state and assignments: you need
persistent state at _some_ point in runtime, and I think it's best to accept
the crap and be explicit about it.

I greatly appreciate the idea of monads and all the insight that went into
them, but it seems to me that they were conceived merely to solve a problem
that doesn't need to exist. Monads are a great academic hallelujah and I hope
they lead to the discovery of some new, unfamiliar programming paradigm that
benefits all programmers.

However, I think monads are currently like the various design patterns akin to
the Visitor (and its relatives) in Java. People had to invent these patterns
basically to emulate a plain old function pointer bundled with userdata
pointer. That's an old "trick" from C in the 70's and assembly before that,
but only such that is otherwise unavailable in Java.

Consequently, if you're all pure and lazy-evaluating and you decide to make
that a principal issue then yes, you do need monads to do the simplest thing,
such as I/O, simply to fit into the purely lazy environment. IIRC the I/O
monad was where it all began -- I would be surprised to note otherwise.

Then again, what I like in Clojure is that while the program control isn't
automatically lazy-evaluating I still get most of the benefits of laziness
from the fact that almost all data flow in Clojure is lazy by default. I can
still write these nice infinite Fibonacci series or infinite sequences of
random numbers and (take) as much as I need. And I still have (delay)
explicitly for any body of code that I need evaluated lazily. And I can still
enjoy writing most of my code in beautiful pure functional Lisp.

Yet I _can_ still shuffle my data procedurally. And loop around I/O like a
grumpy Pascal hacker in a hamster wheel as much as I want _when_ I need to.
And still confine that mess into a function or two with big warning labels
around, so to not contaminate the rest of my program.

I know there are people who build incredible things out of monads, or
inventing monadic metatypes I'm unable to grasp, and generally doing stuff
never done before.

But that is merely a sign of great cleverness of these people; the same can be
said about C++ templates that you can whack into such a spaghetti that they
actually run parts of your computation in compile time.

One could, I suppose, say that it's like a treadmill with wheels that are
connected to the running belt but slightly downgeared: you can use it to move
on a street by exercising walking with your legs but most people choose to
just.. you know, walk directly.

~~~
ramchip
Monads can be useful as an abstraction; it's more than just a somewhat
convoluted way to do I/O. Look at a parser written with Parsec, or the JIT
code generation that Harpy offers with monads and combinators. It's very
concise and readable. I also think a major strong point of Haskell that you
omitted is the type system. I've never had so few runtime errors. Which might
not be saying much, since I usually program in C++ ;) but after, I admit it, a
fair time spent fighting with the compiler, my experience is that when I
finally get it to compile the code is correct. The purity also allows for some
clever optimizations; with native code generation, Haskell is very fast.
Especially if you include the JVM start time, it feels much snappier than
Clojure/Java.

~~~
a-priori
Here's an example of a parser I wrote using Parsec:
[http://github.com/michaelmelanson/cminus-
compiler/blob/maste...](http://github.com/michaelmelanson/cminus-
compiler/blob/master/Compiler/Parser.hs)

------
Luyt
The author makes a nice case for lazy evaluation, but I doubt the "goal to
help experienced procedural programmers make the transition to Haskell" is
achieved because he doesn't explain how to read special Haskell constructs:

    
    
        mapM_ (interactFile (unlines . map processIt . lines)) args
        ss <- mapM readFile fileNames
        putStr $ f (concat ss)
    

A c++ programmer might ask: What do these dots means? Wat does the $ character
do? Why us mapM sometimes used with and without trailing underscore?

It seems like one should learn Haskell first, before being able to understand
the examples.

~~~
jberryman
The article is billed as a tutorial on haskell IO, so I presume a knowledge of
haskell syntax as basic as (.) and ($) is assumed. But I agree that this
probably isn't such an effective introduction to IO or haskell.

------
WilliamLP
> safeRead s = catch (readFile s) $ \\_ -> return ""

> Contrasting this with Java is left as an exercise for the reader.

String safeRead(String fName) {

    
    
        try { return readFile(fName); }
    
        catch (Exception e) { return ""; }
    

}

Just the same thing, except replace ASCII garbage with words? (It assumes I
have a readFile function, but that's not such a terrible thing to have
around...)

~~~
jrockway
Java's standard library has no way to read a file with one function.

~~~
WilliamLP
I am allowed to write my own function in Java. Having a basic file read in a
static function library is more than reasonable. Should it be in the standard
library? Yes, but it not being there isn't really a cost.

~~~
zaphar
It is a cost because you now have to carry around your static function with
you wherever you code.

Either you 1. have it in a utils library that adds to the external
dependencies of all your java projects. or 2. you copy and paste it
everywhere.

Either way it's a cost however small that java has and other languages have.

Additionally your example doesn't really demonstrate the same feature the
haskell example did, which was a stream that haskell through laziness handles
transparently for you. Your readfile static function breaks the stream because
it forces you to read the entire file into memory before returning the
contents. That was sort of the point and your counter example in java
demonstrates it beautifully.

An example that did what the haskell example did would require significantly
more code.

------
shrughes
I really despise tutorials that extol the benefits of laziness by
demonstrating interact and getContents, the most evil functions in the Haskell
library.

~~~
fp
Could you elaborate, please? Or point me to a discussion of the issues with
said functions? Thanks!

~~~
shrughes
They work by having input/output happen outside the IO monad. That breaks the
abstraction -- which is impossible. You can't implement interact or
getContents out of simpler functions, unless you use unsafeSomethingOrOtherIO.

~~~
camccann
Do you have any references for that? I looked through some Haskell
documentation and didn't see anything warning about those functions being
unsafe (although there does seem to be an unsafeHGetContents in
System.IO.Unsafe).

Is there's something I'm missing that says "hey, this function uses
unsafePerformIO behind the scenes" or whatnot? Being not very experienced with
Haskell I'm a little worried about library functions being unsafe without me
realizing it.

~~~
jfoutz
It's just getContents (and brother hGetContents).

Using getContents is fine for a quick way to slurp in a file, but it's like
doing a read without checking for -1. Since haskell is lazy, figuring out why
an underlying read failed can be fairly confusing.

If you think your program is doing IO and there's no IO type getting in the
way, unsafePerformIO is lurking in the darkness.

~~~
camccann
Well, I was confused because getContents seems to be of type "IO String":
[http://www.haskell.org/ghc/docs/latest/html/libraries/base/P...](http://www.haskell.org/ghc/docs/latest/html/libraries/base/Prelude.html#v%3AgetContents)

I can see why a lazy input stream could make things opaque even in a
completely type-safe program, though.

