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.
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.
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...)
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.
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.
I really despise tutorials that extol the benefits of laziness by demonstrating interact and getContents, the most evil functions in the Haskell library.
If I recall correctly, `getContent` (and therefore `interact`) uses `unsafeInterleaveIO`, the most unsafe function of the whole Haskell library, while not satisfying all the condition it should satisfy.
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.
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.
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.
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 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.
Monads are a great academic hallelujah and I hope they lead to the discovery of some new, unfamiliar programming paradigm that benefits all programmers.
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.)
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.
"
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;
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 docome across as a borderline fanboi/troll. Not sayng you are one, but your argement comes across that way. Subtle condescension is still condescension.
It seems like one should learn Haskell first, before being able to understand the examples.