For those of you in the industry, why would I want to use Clojure? What would be a good project to try it on? A Web app? Why is it worth learning it?
For those of you in the industry, why would I want to use Clojure? What would be a good project to try it on? A Web app? Why is it worth learning it?
Over the past couple of years, I've learned Common Lisp by working through Conrad Barski's Land of Lisp[0] and dabbled in Haskell via Learn You A Haskell[1]. I had a great time learning both, but I found that Common Lisp had a decent number of warts (e.g. four different kinds of equality) that seemed to be primarily due to backwards-compatability concerns, and I had a really hard time getting up and running when trying to write a simple webapp in Haskell. In addition, I've gotten spoiled by how batteries-included Python (which I use at my day job) is; the library scene for both of those languages seemed lacking in comparison.
What I like about Clojure is that it gives me fast immutable data structures, has a really strong emphasis on functional programming, and has access to a really huge amount of useful libraries.
So - I've mainly been learning Clojure as a nice treat for myself; the amount of jobs that exist that employ programmers to code specifically in Clojure didn't even really occur to me as something worth thinking about. It's just really a lot of fun to program in. Also, with any luck, I'll be able to transfer any lessons I learn from Clojure to my daily work in Python.
(Haskell still has a very special place in my heart [I do frequently find myself wishing I had types], I'm not trying to dis Common Lisp nor Haskell, I'm just trying to honestly answer the question of why I thought it was worth learning Clojure).
When you say "has a huge amount of useful libraries" but then say that Python is "batteries included", which libraries in Clojure are particularly useful? Which ones did you wish it had?
2) I used those two phrases interchangeably - I consider both Clojure and Python as "batteries-included" languages. Case in point - before I was aware that Clojure Toolbox existed, I was able to google "clojure http request" and find several libraries that let me perform HTTP requests programmatically that I could include in my project.clj and start using right away. My memory's a little fuzzy, so apologies if this is completely wrong, but I don't recall having as easy a time doing that in e.g. Common Lisp or Haskell.
As far as which libraries in Clojure are particularly useful - Ring and Compojure are fantastic, and core.logic[0] is extremely interesting; I'll write a blog post showcasing core.logic (and using it for my first time ever) at some point. Midje is great for writing unit tests. I've only been using Clojure for a few months, so I can't point to too many others that have really been extremely useful for me, but I definitely recommend checking out the Clojure Toolbox[1] for a more thorough list of the main contenders.
[0] https://github.com/clojure/core.logic/wiki/A-Core.logic-Prim... [1] http://www.clojure-toolbox.com
Basically it makes it easy to convert projects without tearing down your infrastructure, ie, easy to convince your boss.
If you really think that "four different kinds of equality" in Common Lisp is a wart, then I recommend that you read "The Best of Intentions" (http://www.nhplace.com/kent/PS/EQUAL.html) by Kent Pitman. The article explains why that situation is quite intentional, and why it is very rational, and why it is in fact ill-advised to expect a single meaning for "equal" ... or, in fact, other "obvious" concepts like "copy" ... to be sufficient.
That article was one of the many over the years that helped me refine the way I think about programming and programming languages.
If you want to start your own venture, using a powerful language like Clojure might give you an edge.
http://www.paulgraham.com/avg.html
Learning a powerful, less main stream language like Clojure can also signal to smart, innovative software companies you might be the kind of person they should hire.
From my _very_ short experience (I've started coding a simple game), here are my two cents:
- If your application is heavily dependent on state, which changes continuously and must be maintained during most of the execution time - like a game - it may not be a good fit. You'll waste a lot of working around the fact that each modification results on new objects. - If your application comprises mainly of short requests that may create some state that will most certainly be discarded shortly after - like a web application - then I think it's a good fit and you'll probably enjoy it better.
Once again, I've just got started with it, so take this with a grain of salt :)
I may try the blog/webapp idea with Clojure.
I find the majority of my time in writing stateful programs goes towards reasoning about safety, and having access to STM lets me write correct code with less thinking. Then I focus on optimizing the parts where performance is critical.
I have found that this rarely enters discussions regarding Clojure, although the STM is frequently lauded. GHC's STM also does not work with the IOMonad, which is clear because of the types, and is articulated in books such as such as Real World Haskell.
http://book.realworldhaskell.org/read/software-transactional...
Are people really getting benefits from transactions which do not involve I/O?
Agents are integrated with the STM - any dispatches made in a transaction
are held until it commits, and are discarded if it is retried or aborted. Unlike refs and atoms, it is perfectly safe to use agents to coordinate
I/O and perform other blocking operations. This makes them a vital
piece of any complete application that use refs and Clojure’s STM
to maintain program state over time. Further, thanks to their semantics,
agents are often an ideal construct for simplifying asynchronous
processing involving I/O even if refs are not involved at all.
Further: Agents are integrated into Clojure’s STM implementation such that
actions dispatched using send and send-off from within the scope
of a transaction will be held in reserve until that transaction
is successfully committed. This means that, even if a transaction
retries 100 times, a sent action is only dispatched once, and that
all of the actions sent during the course of a transaction’s
runtime will be queued at once after the transaction commits.The critical thing, as you observed, is to separate IO from retryable STM transactions with a barrier; e.g. by receiving data, doing an idempotent computation inside dosync or swap!, and then emitting results. Haskell has an advantage in that this separation is provable by the type system, whereas in Clojure you need to remember.
A contrived example:
(let [pizza (get-from-fridge)
meal (dosync
(alter eaten-foods conj pizza)
(deref eaten-foods))]
(tell-friend "So far I ate" meal))
where get-from-fridge and tell-friend are IO operations, and our list of eaten foods is mutated by pure functions.In practice I don't find this particularly limiting: dosync and swap! are almost always short operations for performance reasons anyway. Then you return a consistent snapshot of the updated state from dosync--where multiple pieces of state are involved, you can use vectors or maps along with destructuring bind. Sometimes it's a tad unwieldy, but typically much shorter than the equivalent mutex dance.
There are other aspects of Clojure's concurrency libraries, like agents, futures, and promises, which are useful in IO. Specifically, agents give you asynchronous serializability, futures give you asynchronous concurrency, and promises allow for synchronous handoff of delayed values. Those are quite useful when working with IO, though for heavy lifting you may be better off using explicit queues and worker pools from java.util.concurrent.
Sometimes I think of Clojure (as an imperative language) as the dual of Haskell's lazy evaluation model. Clojure uses futures, promises, and lazy sequences to provide explicit laziness, where Haskell uses the IO monad to provide explicit ordering. Both have an STM for serializable, atomic mutability between threads.
Of course, everyone there is doing FP in C++.