Cabal, which unsurprisingly is not mentioned. Dependency hell makes it stupidly difficult to rely on other people's code on a large scale. I'd rather write uglier code in JavaScript if it means having access to other people's packages immediately and without complications.
If you're stuck, asking questions on the Haskell irc channel on freenode is a great way to get feedback. Learning new things is a social process, not something to be done in a padded room. :-)
The problem is you only waited a week. Haskell is so different from the most popular languages you should approach it like you're learning programming for the first time.
Rome wasn't built in a day! Learning a new language in a week is not an easy thing to do. The reason other languages seem a lot easier to learn (Python, Ruby, etc.) is that those languages are all fundamentally similar to one another; Haskell is not.
Haskell is really not much more strongly typed than Java, or even C++ (although it's much less liberal with letting you cast than either of those languages). Just like those languages, if a function has been declared to use types a, b and c, then you'd better supply types a, b, and c as arguments. This is the same. The only real extension that I can see is the ability to create functions which accept or return arbitrary types, like `foo :: a -> [a]`, which isn't an option in Java or C++ without some hackery. Of course there are type classes and algebraic data types and other things as well, but I don't think they really make the language "more strongly typed," just more expressive in their types.
What makes Haskell a little challenging in the beginning is its currying, which means you don't need to call a function with all of its stated arguments, and the fact that function application associates to the left; i.e. that `f x y` is the same as `(f x) y`, NOT `f (x y)`, which is closer to how it would be in, say, Ruby or CoffeeScript. There are very good reasons for this, but it does take some getting used to. Currying and associativity rules are responsible for a lot of seemingly incomprehensible type errors. You get better at avoiding them as time goes on. I feel that the weirdness of reading Haskell DSLs, like Parsec, which often make heavy use of customized operators, or do-notation, is an extension of these issues, since most of the difficulty comes from trying to figure out how the types propagate through what amounts to be long, complicated chains of function applications.
The worst thing to do when learning Haskell is to think that you will be comfortable in just a week. Learning Haskell for most is like relearning to program. It takes time to build up intuition and understanding of the language. I know that it took me multiple tries at Haskell over a few months before it finally "clicked" for me and became comfortable to work in.
The latest version of Cabal (1.18) includes built-in sandboxing support, which alleviates this problem to a significant extent. Prior to that, there were/are tools [^1][^2] for doing the same thing external to Cabal itself. Dependency hell has without a doubt been problematic with Cabal, but it is fast becoming a solved issue.
I did use cabal-dev, and it only moved the problem from being a global problem to a local one. Is the problem that you cannot have several versions of the same package solved? Because this really needs to work, otherwise you effectively have a limit on how many modules you can use sensibly without expecting things to break.
You can have several versions of the same package. You can't have one package built multiple times to depend on different versions of the same package. This is the main cause of dependency hell.
Thanks for pointing that out. The author didn't emphasize that point, and it makes sense now.
Is it me, or was it a bad example for where Haskell "really shines"? I don't like the "use Haskell cause it has these really cool functions" approach. Feels so superficial.
It wasn't so much a bad example so much as it was a trivial one. Perhaps a more powerful one (that displays the advantages of lazy evaluation and higher-order functions) would be the function to generate an infinite list of Fibonacci numbers.
Haskell is worth the effort, but I don't think the article's author did a great job of articulating why.
Some things I've noticed:
Haskell does not automatically admit an extra "null" value to every type, so you can ordinarily ignore NullPointerExceptions and the like. When you do need a nullable bit of data, you have to do some extra work: You must rename the type from "Foo" to "Maybe Foo" and change all the callsites. These callsites must test for null and specify what happens. (there exists a non-terminating explode() function if you really want it) The build will fail until every last case is covered. This is great for reliability.
Most data in Haskell is immutable. This encourages good programming practice, sure, but it really pays off when you start writing parallel code. Parallel Haskell is by far the easiest way I know to write fast concurrent stuff. On that note, Haskell's concurrency primitives are very well-designed.
Haskell's solution to callback hell is more general and powerful than anything I know. The "do" block is really just super elegant syntax sugar for a CPS chain.
Our unit test suite does not intermittently fail. We restrict side effects such that our build will fail if a test tries to do non-faked I/O. It took a bit of effort to build, but normal use is effortless.
Yeah, I don't touch Java very often (and my Java is more sledgehammer than scalpel at the best of times), but off the top of my head I'd reduce the example to:
int[] arr = new int[50];
for(int x =2; x <= 100; x = x+2){arr[(x/2)-1] = x;}
int[] arr2 = Arrays.copyOfRange(arr,5,arr.length);
There's probably a more fluent method to achieve the same result (in Scala, F# and C#, certainly there are), but I'd like to see a considerably less-contrived example from a more-disinterested party.
This is yet another shallow puff piece on Haskell. Reading it, you would think that the author has only ever used Ruby and Haskell, as he gives the impression Haskell is the only language that supports functional/declarative style programming.
In actuality the author is trying to sell Haskell because he sells Haskell IDEs.
I'm not knocking Haskell, but let's not pretend that this is a useful piece of journalism.
As someone who has been curious about Haskell, I'm happy to hear someone else say this. The way this article was written feels exactly like the "ruby fever" of 5 years ago.
Or all the other fevers we've suffered through. Haskell seems like an attractive language but I wish boosters wouldn't advocate their languages as the fix to the problems of large-scale software engineering issues when there's little or no evidence for their claims.
Well, the problem with this is that the evidence is locked up in company trade secrets. A lot of the big commercial users of Haskell are in finance and all their code is under lock and key.
There was a cool talk on "The Haskell Chat" episode two [1] that goes over how haskell is used in a large team, some members with expert haskell knowledge, some as just technical end-users of the libraries.
I found it really interesting to see how a large team can deal with a real haskell project.
The difference is that Haskell is a very principled language. All of the claims and excitement are actually backed up by mathematical proofs. Pure functions really do make your code easier to maintain.
And no, it's not the case that Haskell is the only language which allows you to write pure functions. People who try to claim that are being silly and dishonest. Haskell's advantage is that its type system allows you to know at a glance whether or not a function is pure.
While I fully agree that pure code is far easier to maintain than impure code, and the advantages of Haskell are numerous, I think it is a bit strong to state that such claims are backed up by mathematical proofs. Ease of maintenance is a fundamentally subjective claim that cannot be proven, but must be borne out by individual experience (as it has been for you, me, and many others).
Ease of maintenance is a fundamentally subjective claim that cannot be proven, but must be borne out by individual experience (as it has been for you, me, and many others).
I disagree. You can make objective measurements of maintainability. You can look at cyclomatic complexity and reusability (both wins for pure functions). You can test how often changes lead to bugs and how often the errors are caught. There are many instances of Haskell code where there is only one sensible implementation of a function, given its type. Hell, some Haskell libraries have even been formally verified though admittedly that is done with external packages.
I will agree on one thing, though: you do run into trouble when you throw around the word all (or indeed any absolute) without actually meaning it.
> You can look at cyclomatic complexity and reusability (both wins for pure functions).
Yes, those are both wins for pure functions. But even there you need an asterisk, because side effects do have their place - the fact of the matter is, the primary task of most real-world software can be summed up as "manipulating mutable state". In light of that I don't think you can necessarily jump from the observation that there are many situations where pure functions are easier to work with than impure ones to the conclusion that pure functions are inherently more maintainable.
But even if that were granted for the sake of argument, it's not much of a win for Haskell. The ability to create pure functions is a feature of every language that has been invented during the lifetime of probably every person reading this post, and therefore not really a differentiating feature of Haskell.
Nor is lack of ability to create impure functions a feature that can be claimed of Haskell, because that simply isn't true. Haskell's real differentiator in that department is just that you've got to jump through hoops to do it. And if your business rules are inherently impure, then it's not clear to me how a language that tries to ghettoize impurity makes them easier to implement.
The win for Haskell is that it wears its statefulness on its sleeve. Of course you can, with sufficient discipline, write pure code in any language, though it can be frustratingly difficult in languages where libraries make idiomatic use of mutation. And yes, you can also write impure code in Haskell, at least for some definitions of "impure".
What you can't do in Haskell is write impure code that claims to be pure (up to the customarily and idiomatically avoided `unsafePerformIO`), or write code whose degree of statefulness is ambiguous. Reliable, explicit, and statically enforced purity holds more than just theoretical benefits. I don't have to read through library code to see if it is thread-safe. I don't have to worry about whether passing a data structure to a library function will result in that structure being mutated behind my back. I don't have to trust code comments that may not be in sync with the current state of the code. Simply by virtue of the fact that a function does not mention `IO` in its type, I can have total confidence that it won't violate my assumptions about its behavior. And I don't even have to take it on faith that the author wrote the correct type for his code; if he hadn't, the compiler would have rejected it. This is the difference between "pure by convention" and "provably pure".
Every language must necessarily have an "IO monad" and interact with the inherently stateful world, or else it is useless. Haskell is different not because you can choose to avoid I/O, but because when you do so, the type system will back you up with perfect accuracy.
But even if that were granted for the sake of argument, it's not much of a win for Haskell. The ability to create pure functions is a feature of every language that has been invented during the lifetime of probably every person reading this post, and therefore not really a differentiating feature of Haskell.
I already dealt with this three posts above:
And no, it's not the case that Haskell is the only language which allows you to write pure functions. People who try to claim that are being silly and dishonest. Haskell's advantage is that its type system allows you to know at a glance whether or not a function is pure.
Nor is lack of ability to create impure functions a feature that can be claimed of Haskell, because that simply isn't true. Haskell's real differentiator in that department is just that you've got to jump through hoops to do it. And if your business rules are inherently impure, then it's not clear to me how a language that tries to ghettoize impurity makes them easier to implement.
That's only the case for beginners who don't understand monads and their associated libraries. For an experienced programmer, Haskell offers much more powerful and expressive (not to mention safe) means of combining effectful computations than typical imperative languages.
It is not a "strong" statement to say that some Haskell code is backed by proofs. There is Wadler's Theorems for free![1] which allows one to reason about pure function's behavior purely from type signatures. As well GHC supports various features that enables one to encode some dependent typing[2] constructs.
Allowing the type of value to to depend on the value itself. A common example being a list that can expresses whether it is empty or not in its type. These types allow one to write functions with signatures like: `safeHead :: SafeList a NonEmpty -> a`. Uses of this function will fail to compile if I can not prove that my list will always be non-empty.
Combine this with the well known Curry-Howard Correspondence [3] which shows that types have a direct correspondence to proofs. The logical extension of this is computer theorem proving languages like Coq, Agda, Epigram, Idris.
For example Coq[4] uses a dependently typed lambda calculus as its proof term language, and sees wide spread use in the formal verification of software. It is not a stretch to say that less powerful types systems like Haskell's allow us to encode some forms of proof. If you view the type checker as an proof checker, one provides propositions to it (in the form of types) and then supplies proof (in the form of terms). These can be trivial propositions like `a :: Int`, which in Haskell there are 2^31 correct answers.
I'm aware of dependent types and, more generally, the strong undercurrent of emphasis on provable correctness in the Haskell community at large. I don't disagree at all with what you wrote, but it's not what I was referring to with my comment.
My disagreement was specifically with the implied claim that it can be mathematically proven that Haskell code is easier to maintain than code written in other languages. That has indeed been my experience with Haskell, but I do not think it is directly provable. What seems easy for me may be stupefyingly difficult or confusing for someone else.
"How easy something is" is not really a quantifiable measurement; it is a subjective experience. As chongli points out, however, there are related measurements that can be taken, such as cyclomatic complexity, and those at least seem to be correlated with ease of maintenance. So perhaps I'm just being overly pedantic!
I misread your comment, usually people challenge that point. I totally agree that it is difficult to talk about maintainability in an objective fashion, but there is something to be said about having as many of your assumptions in your codebase vs. your test suite. My experiences working in a Ruby code base is that many assumptions have been encoded in the tests, and not notated in the main code base. When those assumptions change there is a large burden on the programmer of having to unify ad-hoc assumptions from both the test cases and main code base. Validating whether those assumptions still hold, or if it is a test from 3 years ago, and has only been vacuously passing.
I found working in a big Ruby code base that this required spending a non trivial amount of time unifying the two. In that regard by minimizing the amount of test code one has to write and maintain I would argue that maintainability goes up. I've also seen a lot of FP practitioners boasting about relatively low test to code ratios, while in the Ruby world often times you have at least 2x test code, since often times you even have to validate assumptions like `object.responds_to? :foo`.
If by mathematical proofs you mean a dense, tangled run-time written in C that is statically linked into every executable then I think I understand where you're coming from.
The author is the CEO of a company that sells the first (and only?) commercial Haskell IDE. I point this out because the author (from his background) clearly knows about the subject matter, while reading the content of the article would lead you to believe the author was a novice to software development and computer science.
The shallowness of the article combined with the background of the author can be reconciled if you understand the article as an advertisement.
I have and will continue to maintain that it's Haskell's operational challenges that are holding it back from wide adoption, at least on the web. In a lazy language, your cost model is all out of whack. When your program consumes resources, you can't unambiguously identify the line of code that caused your program to consume those resources. That's because a function application itself does nothing. Needing the result of the application is what causes work to be done. And that function application could be floating through your program as a thunk for who knows how long until its result (probably a partial result at that) is forced. So who's to blame for the work? The function application or the code that forced its result? Who knows.
The typical solution to this problem in production Haskell systems, by the way, is to run the application with profiling turned on.
There Is some preliminary work to add a "Strict language" mode that can be selectively enabled per module. Johan Tibbel has some experimental support worked out, and hopefully it can be polished enough to make it Ito ghc 7.10 in the next year.
One important thing in the article that was a bit obfuscated: modern OOP languages introduce a staggering amount of dependency and complexity. Things like TDD are then layered on top to try to ensure solid code is being built.
In a pure functional environment, you don't have those dependency issues. Your problems boil down to types -- do you have the correct fields and definitions in your inputs? -- and transforms -- are your symbolic transformations what is desired?
In practice, pure FP is the closest I've seen to stuff that "just works". While FP can be a huge pain in the ass mentally, it beats the heck out of the spaghetti dependency issues in OOP. [insert long discussion here about whether pure FP can actually scale in a large organization]
I think for a lot of folks, the differences between languages and the surrounding toolset is very diminished. That is, there seems little gain in making this distinction.
No because that dependency hell is up front (Haskell ecosystem likes being explicit). Whereas in dynamic languages you just install it all and hope it doesn't crash (and your unit test coverage is sufficient).
Firstly, I think this is somewhat unfair to dynamic solutions. I could just as easily reword it as "you try and pull in a library and hope it doesn't break your compilation." Simply put, it could be the up front hell that probably puts a lot of people off of a solution.
Not to mention adding a new piece to a highly type based solution could involve modifying a lot of the existing types to make room for the modification. Whereas with many dynamic solutions, this is not the case.
It is more of a contract. The library author is stating this package was developed against these dependencies and versions. It is known to work well in these cases.
This does not mean it will not compile. In fact often the fix is simply for the author or developer to relax the version bounds and everything will be fine.
Lastly, many of these issues arise not because of a direct dependency but an indirect one. The problem is that GHC will not allow you to depend on two packages which themselves depend on different versions of the same package. Once this is fixed, along with sandboxing, will eliminate the vast majority of the cabal hell problem.
I still feel you are being a touch unfair to the dynamic world. Many times, bringing in a dependency just works.
The analogy I have in my mind is a digital computer for a bike. The general "contract" is simply that you will somehow get a pulse to the computer for each rotation of a tire with a known size. The details of how the bike is constructed are ultimately irrelevant.
Further, because this is such a general contract, it doesn't actually matter if you hook it to the wheel or not. You can, in fact, use such a computer in a non bike device with success.
There's work to provide this "module by contract" notion you're talking about exactly (http://www.mpi-sws.org/~skilpat/backpack/) but it's very new and far from implementation. OCaml does a good job of this and is usually the poster child for parametric modules done right.
That said, dynamic languages allow this kind of contractual module imputation by the virtue of allowing absolutely anything to be considered a module and hashed out at runtime. At the least this means carrying around a lot of tagging/runtime typing information so you can call up runtime errors. At the most, it means that your whole system is held together by an increasingly wide set of unchecked and uncheckable assumptions.
Cabal hell is annoying but at least it's checking.
But wait, don't we need to compare like with like here? Haskell is a break from imperative programming, not OOP. Could it be that functional programming just hasnt had enough industry time to develop an overhead concept that could be compared to what OOP did to imperative languages?
But is there also some selection bias at play here? Typically you don't see a lot of GUI programs or games written in pure FP; so far, I haven't been reading much that exols the virtues of FP in those domains.
http://prog21.dadgum.com/138.html
"If you want to be on the cutting edge of functional programming research, it's easy. Pick something that looks like a poor match for state-free code, like a video game, and start working on it."
Will I have to write a FP "white paper" to make a game in FP?
The problem I have with this is Carmack is in the top 0.01% of developers so of course writing a game in Haskell is going to be within his grasp. For the rest of us it's a huge exercise in mental gymnastics. I've written a few visualizations for data using Gloss (http://gloss.ouroborus.net/) with Haskell and although it does "just work" when it does compile understanding how to manage state in a pure environment is an uphill learning experience to say the least.
Many medical techniques were probably pioneered by specialists and later adopted by general practitioners.
The perceived difficulty of mastering pure functional programming for practical work might be a result of bias in the typical CS education experiences. That might shift as the value of pure functional programming gets more universally recognized.
For the rest of us it's a huge exercise in mental gymnastics.
I've thought about this a lot and honestly, I think this is a good thing. Languages which are much less strict about types and mutation require huge mental gymnastics too, they just don't tell you about it. They let you happily continue on your merry way until some time at a later date you realize your program is a giant ball of mud and you have no idea how to change things without breaking it. Haskell forces you to deal with the hard problem of state up front in exchange for having a clear, maintainable program down the road.
Unless someone had literally never seen anything on Haskell before, this kind of article adds absolutely nothing new to the discussion. It's all been said before, and more often than not it's been said by FP Complete. It reads like an infomercial, and it's frankly embarrassing reading it.
63 comments
[ 3.2 ms ] story [ 58.9 ms ] threadIt may be that I am simply too dumb, or that I didn't find the right resources, but 'easy to learn' this language is not.
What makes Haskell a little challenging in the beginning is its currying, which means you don't need to call a function with all of its stated arguments, and the fact that function application associates to the left; i.e. that `f x y` is the same as `(f x) y`, NOT `f (x y)`, which is closer to how it would be in, say, Ruby or CoffeeScript. There are very good reasons for this, but it does take some getting used to. Currying and associativity rules are responsible for a lot of seemingly incomprehensible type errors. You get better at avoiding them as time goes on. I feel that the weirdness of reading Haskell DSLs, like Parsec, which often make heavy use of customized operators, or do-notation, is an extension of these issues, since most of the difficulty comes from trying to figure out how the types propagate through what amounts to be long, complicated chains of function applications.
[^1]: http://hackage.haskell.org/package/cabal-dev [^2]: http://hackage.haskell.org/package/hsenv (Disclosure: I am the current maintainer of this project, though I am happy to see it becoming obsolete)
$a = range(2,100,2); $b = array_slice($a, 5);
Is it me, or was it a bad example for where Haskell "really shines"? I don't like the "use Haskell cause it has these really cool functions" approach. Feels so superficial.
Having strong and concise type guarantees is great. Haskell's a great example of that. So is Ocaml.
Haskell is worth the effort, but I don't think the article's author did a great job of articulating why.
Some things I've noticed:
Haskell does not automatically admit an extra "null" value to every type, so you can ordinarily ignore NullPointerExceptions and the like. When you do need a nullable bit of data, you have to do some extra work: You must rename the type from "Foo" to "Maybe Foo" and change all the callsites. These callsites must test for null and specify what happens. (there exists a non-terminating explode() function if you really want it) The build will fail until every last case is covered. This is great for reliability.
Most data in Haskell is immutable. This encourages good programming practice, sure, but it really pays off when you start writing parallel code. Parallel Haskell is by far the easiest way I know to write fast concurrent stuff. On that note, Haskell's concurrency primitives are very well-designed.
Haskell's solution to callback hell is more general and powerful than anything I know. The "do" block is really just super elegant syntax sugar for a CPS chain.
Our unit test suite does not intermittently fail. We restrict side effects such that our build will fail if a test tries to do non-faked I/O. It took a bit of effort to build, but normal use is effortless.
int[] arr = new int[50]; for(int x =2; x <= 100; x = x+2){arr[(x/2)-1] = x;} int[] arr2 = Arrays.copyOfRange(arr,5,arr.length);
There's probably a more fluent method to achieve the same result (in Scala, F# and C#, certainly there are), but I'd like to see a considerably less-contrived example from a more-disinterested party.
In actuality the author is trying to sell Haskell because he sells Haskell IDEs.
I'm not knocking Haskell, but let's not pretend that this is a useful piece of journalism.
I found it really interesting to see how a large team can deal with a real haskell project.
[1]: http://www.haskellcast.com/episode/002-don-stewart-on-real-w...
The difference is that Haskell is a very principled language. All of the claims and excitement are actually backed up by mathematical proofs. Pure functions really do make your code easier to maintain.
And no, it's not the case that Haskell is the only language which allows you to write pure functions. People who try to claim that are being silly and dishonest. Haskell's advantage is that its type system allows you to know at a glance whether or not a function is pure.
I disagree. You can make objective measurements of maintainability. You can look at cyclomatic complexity and reusability (both wins for pure functions). You can test how often changes lead to bugs and how often the errors are caught. There are many instances of Haskell code where there is only one sensible implementation of a function, given its type. Hell, some Haskell libraries have even been formally verified though admittedly that is done with external packages.
I will agree on one thing, though: you do run into trouble when you throw around the word all (or indeed any absolute) without actually meaning it.
Yes, those are both wins for pure functions. But even there you need an asterisk, because side effects do have their place - the fact of the matter is, the primary task of most real-world software can be summed up as "manipulating mutable state". In light of that I don't think you can necessarily jump from the observation that there are many situations where pure functions are easier to work with than impure ones to the conclusion that pure functions are inherently more maintainable.
But even if that were granted for the sake of argument, it's not much of a win for Haskell. The ability to create pure functions is a feature of every language that has been invented during the lifetime of probably every person reading this post, and therefore not really a differentiating feature of Haskell.
Nor is lack of ability to create impure functions a feature that can be claimed of Haskell, because that simply isn't true. Haskell's real differentiator in that department is just that you've got to jump through hoops to do it. And if your business rules are inherently impure, then it's not clear to me how a language that tries to ghettoize impurity makes them easier to implement.
What you can't do in Haskell is write impure code that claims to be pure (up to the customarily and idiomatically avoided `unsafePerformIO`), or write code whose degree of statefulness is ambiguous. Reliable, explicit, and statically enforced purity holds more than just theoretical benefits. I don't have to read through library code to see if it is thread-safe. I don't have to worry about whether passing a data structure to a library function will result in that structure being mutated behind my back. I don't have to trust code comments that may not be in sync with the current state of the code. Simply by virtue of the fact that a function does not mention `IO` in its type, I can have total confidence that it won't violate my assumptions about its behavior. And I don't even have to take it on faith that the author wrote the correct type for his code; if he hadn't, the compiler would have rejected it. This is the difference between "pure by convention" and "provably pure".
Every language must necessarily have an "IO monad" and interact with the inherently stateful world, or else it is useless. Haskell is different not because you can choose to avoid I/O, but because when you do so, the type system will back you up with perfect accuracy.
I already dealt with this three posts above:
And no, it's not the case that Haskell is the only language which allows you to write pure functions. People who try to claim that are being silly and dishonest. Haskell's advantage is that its type system allows you to know at a glance whether or not a function is pure.
Nor is lack of ability to create impure functions a feature that can be claimed of Haskell, because that simply isn't true. Haskell's real differentiator in that department is just that you've got to jump through hoops to do it. And if your business rules are inherently impure, then it's not clear to me how a language that tries to ghettoize impurity makes them easier to implement.
That's only the case for beginners who don't understand monads and their associated libraries. For an experienced programmer, Haskell offers much more powerful and expressive (not to mention safe) means of combining effectful computations than typical imperative languages.
Allowing the type of value to to depend on the value itself. A common example being a list that can expresses whether it is empty or not in its type. These types allow one to write functions with signatures like: `safeHead :: SafeList a NonEmpty -> a`. Uses of this function will fail to compile if I can not prove that my list will always be non-empty.
Combine this with the well known Curry-Howard Correspondence [3] which shows that types have a direct correspondence to proofs. The logical extension of this is computer theorem proving languages like Coq, Agda, Epigram, Idris.
For example Coq[4] uses a dependently typed lambda calculus as its proof term language, and sees wide spread use in the formal verification of software. It is not a stretch to say that less powerful types systems like Haskell's allow us to encode some forms of proof. If you view the type checker as an proof checker, one provides propositions to it (in the form of types) and then supplies proof (in the form of terms). These can be trivial propositions like `a :: Int`, which in Haskell there are 2^31 correct answers.
[1] (http://ttic.uchicago.edu/~dreyer/course/papers/wadler.pdf)
[2] (http://en.wikipedia.org/wiki/Dependent_type)
[3] (http://en.wikipedia.org/wiki/Curry%E2%80%93Howard_correspond...)
[4] (http://en.wikipedia.org/wiki/Curry%E2%80%93Howard_correspond...)
My disagreement was specifically with the implied claim that it can be mathematically proven that Haskell code is easier to maintain than code written in other languages. That has indeed been my experience with Haskell, but I do not think it is directly provable. What seems easy for me may be stupefyingly difficult or confusing for someone else.
"How easy something is" is not really a quantifiable measurement; it is a subjective experience. As chongli points out, however, there are related measurements that can be taken, such as cyclomatic complexity, and those at least seem to be correlated with ease of maintenance. So perhaps I'm just being overly pedantic!
I found working in a big Ruby code base that this required spending a non trivial amount of time unifying the two. In that regard by minimizing the amount of test code one has to write and maintain I would argue that maintainability goes up. I've also seen a lot of FP practitioners boasting about relatively low test to code ratios, while in the Ruby world often times you have at least 2x test code, since often times you even have to validate assumptions like `object.responds_to? :foo`.
The shallowness of the article combined with the background of the author can be reconciled if you understand the article as an advertisement.
The typical solution to this problem in production Haskell systems, by the way, is to run the application with profiling turned on.
In a pure functional environment, you don't have those dependency issues. Your problems boil down to types -- do you have the correct fields and definitions in your inputs? -- and transforms -- are your symbolic transformations what is desired?
In practice, pure FP is the closest I've seen to stuff that "just works". While FP can be a huge pain in the ass mentally, it beats the heck out of the spaghetti dependency issues in OOP. [insert long discussion here about whether pure FP can actually scale in a large organization]
Although I've read some blogs that complained that ML gets this right, and Haskell makes it harder with it's typeclasses
Not to mention adding a new piece to a highly type based solution could involve modifying a lot of the existing types to make room for the modification. Whereas with many dynamic solutions, this is not the case.
This does not mean it will not compile. In fact often the fix is simply for the author or developer to relax the version bounds and everything will be fine.
Lastly, many of these issues arise not because of a direct dependency but an indirect one. The problem is that GHC will not allow you to depend on two packages which themselves depend on different versions of the same package. Once this is fixed, along with sandboxing, will eliminate the vast majority of the cabal hell problem.
The analogy I have in my mind is a digital computer for a bike. The general "contract" is simply that you will somehow get a pulse to the computer for each rotation of a tire with a known size. The details of how the bike is constructed are ultimately irrelevant.
Further, because this is such a general contract, it doesn't actually matter if you hook it to the wheel or not. You can, in fact, use such a computer in a non bike device with success.
That said, dynamic languages allow this kind of contractual module imputation by the virtue of allowing absolutely anything to be considered a module and hashed out at runtime. At the least this means carrying around a lot of tagging/runtime typing information so you can call up runtime errors. At the most, it means that your whole system is held together by an increasingly wide set of unchecked and uncheckable assumptions.
Cabal hell is annoying but at least it's checking.
It scales for us at Standard Chartered.
http://prog21.dadgum.com/138.html "If you want to be on the cutting edge of functional programming research, it's easy. Pick something that looks like a poor match for state-free code, like a video game, and start working on it."
Will I have to write a FP "white paper" to make a game in FP?
http://functionaltalks.org/2013/08/26/john-carmack-thoughts-...
The perceived difficulty of mastering pure functional programming for practical work might be a result of bias in the typical CS education experiences. That might shift as the value of pure functional programming gets more universally recognized.
I've thought about this a lot and honestly, I think this is a good thing. Languages which are much less strict about types and mutation require huge mental gymnastics too, they just don't tell you about it. They let you happily continue on your merry way until some time at a later date you realize your program is a giant ball of mud and you have no idea how to change things without breaking it. Haskell forces you to deal with the hard problem of state up front in exchange for having a clear, maintainable program down the road.