I've done commercial Haskell work and it was fantastic. Almost all of the things you mention are problems with technical leadership and not Haskell itself. These problems are all very, very easy to deal with.
1) Many of us have also seen the C++ library or application that busts out the template metaprograms at every opportunity. Haskell is the same. Use restraint when bringing in new libraries and programming techniques. (as an aside, I have yet to encounter a single problem that I thought merited the use of arrows)
2) You also see this in other languages, though to a lesser degree. The same techniques we use with other languages work just as well in Haskell: As leadership, form a strong opinion on the best way to write control flow and ensure that everything uses a consistent style.
3) Are your engineers running around implementing random research papers? This has nothing to do with Haskell.
4) In practice, this is almost never a big problem. Refactoring a pure function to an effectful function is easy and safe. In a production setting, almost all of your functions run in some monad already, so it's pretty rare to run into a situation where you suddenly need to percolate effects through many functions.
I've never been lucky enough to get a full-time Haskell job (nor did I try), so my experience is based on single-man development & prototyping using the existing stuff found on Hackage. I presume that it may be different for a huge organization that can afford to build the entire ecosystem from the ground up while enforcing design rules (and these rules must be created by an exceptionally brilliant architect!). But in other languages you don't have this situation.
> Many of us have also seen the C++ library or application that busts out the template metaprograms at every opportunity.
C++ and boost set standards a bit low, to be honest.
> Are your engineers running around implementing random research papers? This has nothing to do with Haskell.
Not research papers, just the usual business logic. The problem is that research papers set the code style. Also they are not "my engineers".
> In practice, this is almost never a big problem.
It typically is, because changing internal implementation is reflected in the interface, and for each invocation of the function change has to propagate through _the entire_ call stack to some place running in IO monad or whatever.
> almost all of your functions run in some monad already
You defy your own argument. If that's how you design application, what's the point of bothering with side effect isolation then?
The bulk of real Haskell programs do not choose such granular restriction of side-effects that it's unmaintainable. As always, there are tradeoffs. In most applications, the code at application boundaries live in some Monad that are based on IO.
For Example, Yesod gives you the Handler monad, which is based in IO, and really just exists to provide access to runtime/request information, so at the API handler level you can do anything you need to. But what's nice is that not everything has to live in IO, and so in places where it makes sense, such as a parser, we can say that parser doesn't need IO, because that wouldn't make sense.
And my point here is that just having the ability to separate between IO and non-IO is very useful; we also do not have to split every single effect down into it's own special, separate type; in many cases that would just be overkill.
> You defy your own argument. If that's how you design application, what's the point of bothering with side effect isolation then?
I don't understand this.
If you have a monad like
do
...
_ <- doSomething arg1
let result :: Result = pureFun arg2 arg3
...
How is that not simpler and better than having that pureFun invoke a bunch of side effects and perhaps even mutate something that you have to be aware of? How does this take away the benefits of side effect isolation?
Because as code evolves you may find you need some logging in pureFun and now you have to go and sprinkle some IO wherever pureFun is used, which may propagate to who knows how many other places.
Of course, I suppose there is always unsafePerformIO, at least for those late-night debug sessions.
> Of course, I suppose there is always unsafePerformIO, at least for those late-night debug sessions.
For temporary debug output you should probably consider the Debug.Trace module before unsafePerformIO. It has a simpler interface and doesn't introduce the possibility of arbitrary I/O, just text output (and event logging if that's enabled in the RTS).
If you're able to run the code locally, there is a function called Debug.Trace.trace which writes messages to stdout without requiring IO.
If it's a production issue, all you need to do is figure out what is being passed to your function. It's a pure function, so you can always debug it locally once you've got that.
Haskell doesn't work that way at all. If you have variables inside a function, due to laziness their values may or may not ever be computed. Moreover, Haskell compiler sort of reduces the equations so the functions are not imperative and the computations would not happen as you expect them to. Logging would not get you anywhere.
However, you can just log the output if you like without putting IO into the function. I see zero reasons why anybody would push such logging into production. It behaves exactly the same way on your own computer as it would in production - it's just a pure function.
I know that Haskell's lazyness adds an extra layer of complexity to this problem.
However, disregarding the implementation details, here is the abstract problem: I have a program which is reading some complex data structure from IO, applying a complex, pure pipeline to it, and sending the reply back through IO.
Now, say the output is not correct in some way. Your only chance of debugging the issue is to somehow visualize some of the intermediate values, right?
This being a pure pipeline, you don't need to have logging in production. You could just log the input value, and when a bug occurs, run the same input value from the logs through a modified version of the original pipeline with some logging sprinkled throughout - you're right on this side.
Now, I expect that there are many situations where adding the logging in production is still preferable - maybe the pure pipeline is in development and some kind sof issues are frequently occurring, maybe the pipeline takes too long to re-run, so it's just easier to have the logs from the first run etc.
What I'd do is simply break down the larger pure function into smaller pure functions and sequencing them in the monad.
This way you would still retain purity with your functions but you could log every intermediate step by simply logging their output (and its corresponding input). Would probably make the code more maintainable as well.
From what I understand Haskell has great tooling for function profiling. Unfortunately, I got no experience with these yet so I can't say if they'd help you solve such problems.
Edit: Btw sounds like property based testing might work well as a solution to this kind of problem. It works really well with pure functions.
That's right. Java is advertised as a "workman-like language" (today we should say worker-like) because it doesn't require a guru leader to get a working project built.
At my job, software is only have the engineering, we need to save half our brains for product/business issues.
Java is also advertised as "enterprise" language and it can't even encode in its type system that a piece of data can be missing. Or maybe it can, with Optional<T>, thanks Haskell.
I can't tell I had less problems understanding magic container IoC stuff than most Haskell concepts.
To be pedantic, Java perfectly encodes the information that a piece of data can be missing - any type supports null as a valid value. What it can't do is encode the information that a piece of data CAN'T be missing.
Not even that. Java's optionals are almost useless, needlessly verbose, you still don't have pattern matching, and there's also still null to contend with.
1) Many of us have also seen the C++ library or application that busts out the template metaprograms at every opportunity. Haskell is the same. Use restraint when bringing in new libraries and programming techniques. (as an aside, I have yet to encounter a single problem that I thought merited the use of arrows)
2) You also see this in other languages, though to a lesser degree. The same techniques we use with other languages work just as well in Haskell: As leadership, form a strong opinion on the best way to write control flow and ensure that everything uses a consistent style.
3) Are your engineers running around implementing random research papers? This has nothing to do with Haskell.
4) In practice, this is almost never a big problem. Refactoring a pure function to an effectful function is easy and safe. In a production setting, almost all of your functions run in some monad already, so it's pretty rare to run into a situation where you suddenly need to percolate effects through many functions.