Why do you prefer Clojure over Haskell?


#41

Some background: I worked professionally in Haskell for over two years from 2010-2013. I have been programming in Clojure since 2008.

I’ve thought long and hard about why I prefer Clojure to Haskell, even though I think Haskell is a very fine, well-designed language. I’ve tried to figure out the precise points, which have evolved over the years. My Haskell experience is only a little in a college class and at one company, so I’ve tried to separate the company’s practices from the language (which was hard!).

Every language has faults, and I’m not just going to pile onto the problems with Haskell. I want to focus on the few things that matter in my choice.

Okay, so here are the reasons why I prefer Clojure:

  1. Fast feedback is the number one thing I look for in a language.

    I found in Haskell that it might take 2 hours to get code to compile. People would say that “once you get it to compile, it just works”. But I found that I often couldn’t even remember what I was trying to do. Or I would run it and realize it wasn’t doing what I wanted and it had to be redone. 2 hours just to find that out!

    Though very often the compiler errors were useful and helped guide me, I was totally divorced from the actual problem I was solving for that long period of time. That never happens in Clojure. There are long debugging sessions in Clojure, but I’m in there with the code and the problem.

    Even without that problem, Clojure just wins here. It is much easier to write code at the REPL and eventually get it into production code.

  2. Library ecosystem

    Back in 2012, we did lack some libraries. We were shelling out to curl for some things because the libCurl wrappers didn’t have the feature set we needed. It worked, but it was less than ideal. There was nothing we needed to do that we couldn’t work around somehow. This has likely changed in the years since I’ve worked in Haskell.

    Clojure gets to use the industry tested Java libraries. Clojure wins here. I’m also familiar with the JVM, so I have a personal advantage there.

  3. Haskeller in Clojure land

    I’m not an expert Haskeller by any means, but I did learn a lot about its powerful type system. I eventually learned to write code that would compile the first time–which meant having a copy of the type checker running in my head. Now, when I’m in Clojure, I can still use that type checker. I can take it with me wherever I go. It’s not quite the same as having it enforced automatically, but it’s there.

    Now, what that means is I can use the good parts of Clojure and the killer advantage Haskell has. If the type checker or the strict lack of side effects is getting in the way, I can still use the flexible Clojure language. But if I want the strict type discipline, I’ve got it.

    You can’t get that the other way–a Clojurist moving to Haskell. This is of course based on personal experience, so I can’t expect others to have this advantage. And this doesn’t work on a team, either, where Haskell would enforce the types regardless of good or bad practices.

All that said, I often miss the type system. And I want to check out Haskell again soon!


#42

I think function composition and currying, when used extensively, require static typing. They don’t provide much visual semantic information to indicate errors. This is something Gilad Bracha has commented on. Point-free style is so error-prone because of it that you need someone to check your back. And it seems like you’re expressing that same phenomenon in a different way.

That said, I rarely use function composition and currying in Clojure. If you want to do a lot of composition and currying, my suggestion is to use Haskell.


#43

I realized this morning that no one seems to have responded to this question! Yes, we tried core.typed at work. I can’t remember whether we tried Schema first, but we went back and forth several times between “plain Clojure”, Schema, and core.typed. Then we went back to plain Clojure for a while and now we’re using Spec.

On the plus side, core.typed let us “type check” our code separately from runtime. On the negative side, idiomatic Clojure code often contains constructs that are extremely hard to describe in core.typed and we found ourselves either telling core.typed to ignore certain functions or rewriting the code to less idiomatic constructs purely to satisfy core.typed. Also on the negative side, once you started annotating code with core.typed, it required whole namespace annotation (even if it was just to tell core.typed “Don’t check this function yet!”). It also tended to bleed into other namespaces (because core.typed needed to know about the functions you call), and that often bled into the third party libraries you used (we started writing annotations for clojure.java.jdbc, for example, because we used it so heavily).

So we added core.typed annotations and then removed them (several times) and we added Schema annotations and also removed those (several times). Schema provides rich runtime checking but it has most of the syntactic “noise” of core.typed and your type checks are only as good as your unit tests – beyond that, you’re just doing runtime checking with more detail.

Why are we using Spec then? It feels like less noise than Schema, it has Clojure/core support (which also means it pulls in fewer dependencies), and the integration with test.check and the whole data generator side of things has been very valuable for testing code.

I think core.typed is great if you buy into it wholesale from the first line of code in your project (and you’re not relying on too many third-party libraries). It would be much more useful if it had better type inference and also treated unannotated functions as unchecked (so you don’t have to annotate as much code) – in other words, it needs better support for the boundary between checked and unchecked code, which Ambrose has talked about, as have others in this space (look at Typed Racket and recent work there for example).


#44

My PurefunctionPipeline&Dataflow testing and debugging is also this way, just using ; instead of (comment ) , because editor single-line comment are more convenient, regular expression lookup (defn test-fnxx []) or ; (defn test-fnxx []) is very easy.

functions and its test (debug) functions put together for easy debugging, reading and synchronizing updates.

and an ->> thread macro observation function: #(do (prn %) %)


#45

The slack group just called Functional Programming would be a good start, they have a very active Haskell channel with a several prominent community members (thinking Edward Kmett and Chris Allen).


#46

I found this to be one of the benefits of programming in Miranda at school (plus Haskell and OCaml in industry). Ultimately, I prefer to work in scheme-like languages, but adding another set of cognitive tools often provides useful carryover. Similar benefits in other directions can be derived from asm, APL, FORTH and Smalltalk.


#47

For beginners, there’s the #haskell-beginners channel over there.


#48

Is it that an interactive style of development was not a common practice? A few months ago I was writing a small Haskell project with Stack. I used Emacs, Intero and a GHCI frame and it seemed to map well to the workflow I use with Clojure and CIDER.

Have you seen Eta?


#49

Well, things have probably progressed quite a lot since I worked full time in it. For instance, we didn’t use Stack. I’m not sure if it existed.

I think the interactive programming is hindered by the type system, though there are probably workarounds. For example, let’s say you have a type called A and a function called f, that takes an A as an argument. They’re used quite a lot throughout the program. How do you interactively modify A and f to implement some new functionality? Once you change either, you start getting errors. This may be different, now, but at the time, I faced this constantly.

The best advice I’ve gotten is to make a new type A2 and a new function f2, which start off as copies, and so everything still compiles. You can then modify them, since they’re not interwoven throughout the code. Once you have them modified the way you want, you can use the same technique to make a g2 and h2 (modified copies of g and h, which call f). You wind up with about twice as much of everything, but you always have working software. Then you can go back and delete all of original functions. Then you can rename all the functions to remove the 2. It’s quite a lot of work.

And the biggest problem is you have to do that before you even know if it’s going to work the way you want it to (as in, run the software and test it).

Clojure avoids this scenario because functions only tend to depend on very small parts of an entity map. For instance, they may only care about one key. Any changes to the entity that don’t remove that key are still going to work. You don’t have the cascade of changes. That’s why we tend to say that types are coupling.

I can imagine things are much better now. There was something new in ghc, where you could defer type errors to runtime, which could make things easier to compile and run small sections of your code.

Eric