Haskell IO for Imperative Programmers
haskell.org
haskell.org
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.
But that is merely a sign of great cleverness of these people;
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."
This is the flakiest analogy (not to say a condescending one) I've seen here in a while.
The exact same argument could be made for writing a macro in Clojure vs just writing out the code in java.
"yes you could write a clever macro and there are clever people doing it but other people just, you know, walk"
Some programmers have trouble grasping monads. Others have trouble grasping macros. Others (believe it or not,) recursion (Ron Jeffries, an agile "guru" said "Recursive methods tend to be confusing, in my opinion." http://xprogramming.com/xpmag/dbchaskellbowling/), Others TCO (see Tim Bray's recent rant for e.g).
When some programmers look at powerful languages they see,
"merely weird languages. He probably considers them about equivalent in power to Blub, but with all this other hairy stuff thrown in as well.". - PG on the blub paradox.
Insert clojure and haskell appropriately and this sums up your argument.
People who don't "get" monads and/or or how powerful type systems work think languages with those features are "weird" or "too clever" and languages without them are "better". So what's new?
So yes, in spite of your " I don't want to come across as a language troll" disclaimer, you do come across as a borderline fanboi/troll. Not sayng you are one, but your argement comes across that way. Subtle condescension is still condescension.
fwiw.
No they're not. They are a very simple abstraction; instead of sequencing computations implicitly with syntax like ";", you sequence them by calling a function. The result of the previous line of code becomes the input to the next line of code.
All programs logically flow this way, and "monads" merely let you take advantage of this property in arbitrary code.
Finally, because I want to be able to write tests for my programs, my procedural IO code looks very similar to the Haskell version. Arbitrary IO right inside your application logic is going to create an untestable nightmare. ("Oh, I know! Mock STDIN and STDOUT!" No thanks.)
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.
> 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...)
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.
More practically, `getContent` is evil because it locks the file it is reading for an unbounded amount of time: as long as it's content (the giant string representing the file), isn't either out of scope (and eventually garbage collected) or fully evaluated.
That can be a problem if you intent to open loads of files (you could hit the OS limit) or if you want to modify the file after reading part of it.
Lazy IO is therefore quite embarrassing. Several people are working at hopefully safer alternatives right now.
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.
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.
I can see why a lazy input stream could make things opaque even in a completely type-safe program, though.
If you write
do h <- openFile "foo.txt" ReadMode
x <- hGetContents h
putStr x
you'll get different behavior than if you write do h <- openFile "foo.txt" ReadMode
x <- hGetContents h
hClose h
putStr x
In the first example, you'll print out the contents of the file. In the latter, you'll print out nothing.