Here is my list of bad language features:
1. It doesn't support dot notation for records.
2. Record names can't be overloaded.
3. Monads might lead to messy code due to verbose syntax. For example, instead of writing c <- openDbConnection (readConfigData), I have to write ugly code
conf <- readConfigData
c <- openDbConnection
5. Many extensions, essential for productive development are out of the language standard. For example, multi param type classes, existential types. Haskell' standard is in progress for a very long time, and still isn't finished.
> Monads might lead to messy code due to verbose syntax.
Your example could be written `do c <- openDbConnection =<< readConfigData` (assuming there should be a `conf` in the third line). If you need multiple parameters passed like this you're right, you probably need to execute them separately and give their results names. That however can also lead to more readable code if getting the parameters isn't just a single statement.
> Many extensions, essential for productive development are out of the language standard.
There is always this discussion what "valid Haskell" is, on the one side the "Haskell Report" guys, on the other the "whatever GHC accepts for the next 10 years" section. A few remarks:
- GHC is a giant project that uses neither multi-parameter typeclasses nor existentials.
- I don't think language extensions are ever removed from GHC, just pronounced deprecated. (I don't know about such a case at least, correct me if I'm wrong)
- Heavy use of language extensions does mean GHC lock-in. It's free software, but I can still see how that could be a concern.
2. Definitely agreed.
3. "readConfigData >>= openDBConnection" can be used in a simple case like your example. Cases with more arguments can use "ap" or "<*>", which are perhaps a bit ugly, but most of the time you don't need them.
5. The fact that Haskell is a living language that is improving all the time is a point in its favor, I think.
3. Don't modify parameters. It makes code easier to test in any language.
5. GHC may as well be the standard. Are you really going to use a different compiler.
It's not about modifying parameters. It's about verbosity of the code. The type of the parameter is IO String.
>5. GHC may as well be the standard. Are you really going to use a different compiler.
It's a real issue. Different language extensions might modify how type inference works. If you add one more extension, you program might suddenly stop to compile. We need a fixed set of language extensions which are always available.
Do you have some real examples of this happening with GHC extensions? I haven't run across one in the field.
> We need a fixed set of language extensions which are always available.
This sounds very much like a language standard. Haskell isn't much different than other languages here, though the language committee is slow to ratify generally accepted extensions into the standard. GHC at least makes them available to people who want to kick the tires. Then read about closures in Java. Look at C++11 revisions and notice that sometimes meant cutting features, even if they were previously implemented by some compilers (template exports) or prototyped (concepts).
I am not that advanced in how GHC types work. However, I understand, that in any language extensions there are tradeoffs between how expressive a language is and how much type inference we have.
5. What on earth are you talking about? Conjecturing about type inference is not useful. While I may prefer some extensions to be on be default, in hundreds of thousands of lines of haskell code I am yet to see adding an extension break existing code. The worst I have seen is adding an extension slow down compilation.
In haskell you have to forget most of what you have learned in order to be successful. Otherwise you will fight the language.
It's the same kind of terseness that languages like Perl have. It's write/only code.
No, the type of the parameter is String. IO String is the type of the expression that gets that parameter when executed. There's a popular quote by shachaf, "getLine :: IO String contains a String in the same way that /bin/ls contains a list of files" that illustrates this nicely.
#5 is a fair to a point. I don't find MPTC or existential types to be useful in almost any case: I actively try to avoid both. ScopedTypeVariables, FlexibleContexts, FlexibleInstances and potentially RankNTypes and the poorly named UndecidableInstances should be standard though (imo). Some people also find OverlappingInstances and the like to be generally useful but you probably don't want to hear what I'd say about the subject.
There are plenty of other (also subjectively ugly) ways to write #3. SHE (a Haskell preprocessor) deals with it in one way, see the pigworker's idiom brackets: https://personal.cis.strath.ac.uk/conor.mcbride/pub/she/idio.... Without a preprocessor you can certainly use applicatives or some regular monad combinators to git'r'done but a little sugar could go a long ways. I'm not attached to any of these.
Lucky for us there are many languages to choose from, personal taste has proven a fickle mistress.
readConfigData :: IO ConfigData
readConfigData = undefined
openDbConnection :: ConfigData -> IO DbConnection
openDbConnection = undefined
openDbConnection2 :: ConfigData -> ConfigData -> IO DbConnection
openDbConnection2 = undefined
openDbConnection3 :: ConfigData -> ConfigData -> ConfigData -> IO DbConnection
openDbConnection3 = undefined
main :: IO ()
main = do
-- it does work...
conf <- readConfigData
_c0 <- openDbConnection conf
-- how about a flipped bind?
_c1 <- openDbConnection =<< readConfigData
-- or join/fmap...
_c2 <- join $ openDbConnection <$> readConfigData
-- need two parameters? liftA2/liftM2 has been around for a while
_c3 <- join $ liftA2 openDbConnection2 readConfigData readConfigData
-- or use Functor/Applicative to deal with arbitrary numbers of side effecting parameters, longhand idiom brackets...
_c4 <- join $ openDbConnection3 <$> readConfigData <*> readConfigData <*> readConfigData
conf <- readConfigData
c <- openDbConnection
c <- openDbConnection =<< readConfigData
-- do something with c
As for 5., GHC is de-facto standard.
If you're trying to sell me on Haskell, right there you just made me think "uh, ok, guys, but my code does not live in an ivory tower, and very much needs to deal with the real world, messy data, users, and so on".
I realize that Haskell can handle that, too, but in terms of copy writing, it leaves something to be desired.
They subsequently go on to write much more interesting reasons why you might consider Haskell, including examples of companies using it in the real world, but they should not lead with talk of 'purity'.
Haskell advocacy is high in the running for the least effective in the business. Boosters can't seem to help but pitch the things they care about rather than the things the listener does. Maybe it feels satisfying, but it's no way to attract converts.
A more effective approach would be to choose common tasks people perform in other languages and demonstrate how they can be less (error-prone, time-consuming, verbose) in Haskell.
For instance, I recall reading about a Haskell Web framework where the compiler guarantees user-generated content never makes it into any served HTML unsanitized. What a boon for security! However, this is not shouted from the rooftops; instead, it's taking me so long to track down I've given up in the hopes that someone will post it here instead.
I've used Yesod in actual real-world projects and it was always a pleasure, albeit sometimes slightly complicated - the new version (which I haven't used yet) promises to simplify this a lot though.
It's not really that Haskell removes impurity, more that it adds purity. The "main" function of any Haskell application is an IO action, but you can define pure functions which relinquish IO and global state. And the best practices include doing that as much as possible, which is a terrific way of making your program more comprehensible and correct.
Speculative guesses as to what might stand in the way:
1. The lack of a purity-tracking mechanism like Haskell's may make programming in this style in C more complex and error-prone.
2. The fact that C compilers don't expect you to use this programming style may mean they don't optimize common operations in the way Haskell does; for example, if you do manipulations on large arrays in a functional rather than imperative style, Haskell will optimize away a lot of the temporary arrays, but C compilers may not.
It's not only that purity is safe and beautiful. It's also in many cases much easier to understand, because it's about writing functions that express transformations. These transformations should make sense in a conceptual way.
When you distill your domain concepts (see "Domain-Driven Design") and write pure functions on domain data, the result is remarkably clear, testable, and debuggable, no matter which language.
Previously I would have been (prematurely) concerned with the efficiency of in-place updates, but since I'm not writing high-performance CPU-bound software, I don't care about that anymore until the profiler tells me to.
The best code in the system, IMO, consists of small stateless singletons with clear dependencies and non-modifying operations. This code often comes out of test-driven development and has tests that read like specifications.
The way I've done it so far is seems to be working, and I've implemented standard types (their introduction and elimination rules) and few functions on them. The two main problems I've run into is memory leaking and portability.
Since the constructors of an algebraic data type should return const data, it's a bit difficult to free them without violating the type system. I haven't decided yet if I'll end up violating the type system or just drop that const. I think the latter makes more sense, since we're trusting C as little as possible in the first palce. Everything actually might as well be done in void pointers but I think there's some value in using types where possible for documentation's sake.
About portability. The main thing here that I ran into was that there's no real standard and portable way of doing closures or anonymous functions in C yet. Right now, what I'm doing is using nested functions, which works in GCC and clang, but I have no idea if they'd work in MSVC or any of the other compilers out there like pcc and lcc. OS X has blocks, but I figure if they're using gcc too, why not stick with nested functions. As far as I can tell, I won't have the problem of having nested functions returning before their parent does because of the eliminator model.
The libCello (http://libcello.org/) author has been able to successfully model typeclasses in C, so I will be picking that up as soon as I get around to it.
I pushed some of the code to GitHub just now for this post:
I'm glad to hear other people had the same idea. If anyone has any suggestions, please let me know, as I wouldn't mind some collaboration on this.
The nice thing about Haskell is that it conceptually mallocs a fresh array to return, but then optimizes away the intermediate array in a lot of cases.
You could turn it around and say that non-haskell programs live in an ivory tower and ignore the messy world below :)
So someone has a tough problem dealing with the messy real world, and then you tell him to use Monads. Now he has two problems :-)
Ok, I'm kidding, but mbrock's answer is a lot better in terms of selling the language in that it mentions something that anyone can figure out is a good idea, without getting into the details of it.
> encourage a clean "separation of concerns" between logic and interaction.
The problem with Haskell and clarity are, in my experience, twofold:
- large amount of high-level concepts to absorb (though not everything is necessary to start producing code)
- the power of the language is its own worst enemy at times - it is possible to write a pipeline of complex computation with very little work, which leads to less code but less readability (on the other hand, Haskell functions are typically short, which helps a lot with maintaining up-to-date comments)
On the other hand, the separation between pure and impure code is not a complex notion per se, and reasoning about pure code is made much easier.
One of my primary complaints about Haskell, however, is that Haskell code is impossible to debug. You can reason about it, the type checkers can check deep properties about it, but it is difficult to actually observe the computer executing it! To me, this is a deal breaker.
I don't think it's quite as easy, but I'll concede the point so I can make the more important point: it is much easier to write clear Haskell code than clear Java code (at least, "clear" as I understand clarity, YMMV).
> You can reason about it, the type checkers can check deep properties about it, but it is difficult to actually observe the computer executing it!
Certainly purity makes it hard to observe, and laziness makes it hard to understand how the program executes. However purity also means that static debugging (e.g. QuickCheck) is far more powerful than in other languages so dynamic debugging (actually watching your running program) is less needed. In fact I do far less debugging in Haskell than in Python, say. I first fight the compiler. When I win, the end result is correct more often in Haskell than in any other language I've ever used.
> To me, this is a deal breaker.
That's fair enough. Everyone has their own preferences. Personally I've never noticed it be a problem.
Disclaimer: I don't write performance sensitive code. I expect performance tuning in Haskell is harder than what I've described above.
And now we get to a few of questions related to bias:
* Given that debugging is difficult in Haskell, do Haskell programmers to get in the habit of statically debugging their code? Is that really more "better" than dynamic debugging? Some type error messages in languages like Scala and Haskell can make static debugging quite painful.
* Does Haskell's powerful type system bias it to well understood problems with meaningful types? I attended a WGFP meeting once, and it was amazing to hear people like SPJ talk about how they found elegant solutions to icky programming problems, but I thought that, given a dirtier language, you could just write a dirty solution and be done with it.
* Would Python's dynamic type system make it more suited to problems with dirtier less clear types when compared to Haskell? If you know both Haskell and Python, do you divide your potential programs into "Python programs" and "Haskell programs"?
* When one writes Haskell code, do they really not have to debug and test their code?
I'm not sure what you mean by "static debugging". You're going to get type error messages while you're trying to compile, but certainly not at runtime. GHC really does its best to make them as explicit as possible and often suggests the correct fix.
> * Does Haskell's powerful type system bias it to well understood problems with meaningful types? I attended a WGFP meeting once, and it was amazing to hear people like SPJ talk about how they found elegant solutions to icky programming problems, but I thought that, given a dirtier language, you could just write a dirty solution and be done with it.
Not really. It is really a general purpose language, and you find very good libraries to interact with less strongly typed systems (eg, JSON).
> * Would Python's dynamic type system make it more suited to problems with dirtier less clear types when compared to Haskell? If you know both Haskell and Python, do you divide your potential programs into "Python programs" and "Haskell programs"?
If anything it would be more a question of libraries. One thing you won't be able to get in Haskell though is something like an ORM, with lazy-loaded collections and stuff like that. I find myself missing SQLAlchemy. Persistent does not have the same flexibility, despite Esqueleto which looks nice.
> * When one writes Haskell code, do they really not have to debug and test their code?
No. The best type system does not help when you write the wrong values with the right type in your program, or get the logic of your program wrong. But the errors tend to be more interesting. Also, you have a very powerful system to test the pure parts of your code (QuickCheck).
No, I classify my potential programs into "Haskell programs". I haven't missed Python once.
Haskell programmers still have to debug and test, but I find it somewhat easier than I've found it in other languages.
But it's far from impossible (I use Debug.Trace successfully), and you do it far less often.
So debugging takes 5 times more effort but needs to be done 15 times less than in other languages.
At least that's the deal I'm getting and I don't usually find myself debugging much so it's not a grave concern.
I'd say lack of non nullability or pattern matching are worse show stoppers.
For example, if you have a function: add x y = x+y, and you wanted to see all of the inputs add gets, you could do: add x y = trace (show (x,y)) $ x+y.
For anyone confused about how such a function is possible in Haskell, not that it is in Debug, and the documentation clearly states that it should only be used for debugging.
EDIT: You could also do traceShow (x,y) $ x+y.
I've studied a lot of mechanisms for debugging Haskell code, and some of the debugging mechanisms taken for granted in other languages can be clawed back. But at the end of the day, it is a struggle: the purity (or more accurately, laziness) that is supposed to help so much ends up hurting in at least one aspect.
Perhaps why Haskell programs are so easy to reason about statically is because they are so hard to reason about dynamically :)
Haskell has excellent static reasoning/debugging facilities therefore there is little pressure to make good debugging tools.
I believe purity should make debugging easier, not harder. Laziness should only make performance debugging harder, not correctness debugging.
There is much room for improvement in this space.
It is better to lose those not interested in purity early rather than have those uninterested folks become disappointed later.
I'd recommend taking a look at the Real World OCaml book: https://realworldocaml.org
1. Tail call elimination
2. A full-featured module system
3. Functors (functions at the module level)
4. First-class modules
6. Polymorphic variants
7. A decidable type system
8. ... more?
Things OCaml lacks which Scala has:
3. Trivial Java FFI
4. Seamless syntactic macros (c.f. camlp4)
5. ... more?
It lacks 7, and about 2/3, which are basically the same, where scala at least supports mixin constructions (which you can also get from class-parameter ised functors, though maybe not in Ocamel).
I see now that Scala can encode GADTs in case classes. What mechanism is available to encode polymorphic variants? Particularly, I am interested in writing matches over unions of polymorphic variant types.
I disagree that 2 and 3 are the same (structural module subtyping, nesting, inclusion differs from applicative functors). I believe you can achieve mixins with OCaml through the object system but it's not clear to me that this addresses functor signature checking.
Another Scala bonus:
5. Objects can implicitly be null.
I was confused about polymorphic variants. Scala doesn't seem to have that.
I did a lot of work with units (dr. scheme-style functors) before, and when I moved to scala for my postdoc, I found all my patterns expressible using mixins and nominal types...I didn't miss the modules (1st class or otherwise).
Scala has a non-null-ability option, but most programmers I think would find null damn convenient.
It's the most beautiful invention of computer science, Programming in prolog, especially, is the closest a programmer can come to achieving a state of nirvana. A place where the mind is at one with the problem at hand.
Having programmed more than I care to remember (since we're establishing credibility by tossing unverifiable facts in there) I think it takes the right tool for the right job. Sometimes that's prolog, sometimes that's assembly and sometimes it's something else entirely.
Every language that is in use today has a niche, no single language manages to span more than a few such niches and the larger the impedance mismatch between the problem and the language the more work you have to do.
Do views support the notion of primary keys and foreign keys?
I wouldn't be able to program in SQL without the former, and the latter would also be useful.
I don't want to sound insulting, but 10 KLOC is not nearly enough to get a reasonable understanding of even very concise languages. 10 KLOCs of APL, maybe, but certainly not with C, Java (certainly not with Java) or even Ruby or Python.
Beware of Maslow's hammer.
Prolog should be more popular, it should be at least embedded in every program, even in the browser.
If there are more than a few for loops or if else statements than the majority of the program should be encoded in sql/prolog.
It's getting more popular steadily. Map/reduce is being replaced/complemented with distributed sql, and the frontend will also come around as people starting embedding prolog into their guis (LAMP stack in the browser).
Everything else is either interesting or not. I think Haskell is interesting from an academic research perspective, but I'm not interested in using it to write programs.