Not the grandparent, but I realized the other day that I'm at nine years of clojure, so...
What's made clojure so great, imo, is its unicorn status as a principled-yet-practical language. That "principled" part is not worthless---it means that a lot of great minds are drawn to it. Before react took over the world, clojure folks were already taking steps in that direction.
A lot of other things. The "sequence" as a core abstraction is very powerful. Immutable data structures by default make functional programming perfomant and efficient.
Speaking of data structures, data structure literals have spoiled me for other languages. After Java, especially.
And none of this mentions clojure's lispiness. The ease of metaprogramming has allowed the community to build some of the best tooling out there, between CIDER for emacs, and Figwheel for the browser---oh, did I forget to mention clojurescript? Being able to reuse code on the front and backend is great for web applications.
Clojure isn't "everything." I think I'd still benefit from learning Haskell, APL, and Forth, and I wouldn't mind knowing Ruby and js a bit better. And I'll probably be dragged back to Python and R if I keep doing maths.
But if someone asked me which single language would probably do the most for them professionally, I'd say clojure. It's the language people start startups so they can use it.
There's a really good UI framework for Clojure (not ClojureScript) called seesaw. If I recall correctly it was swing, but it's a very nice data focused, functional library. It really works well with the Clojure philosophy and interactive development. It's also VERY complete.
There are downsides, however. The UIs that it creates definitely look like Swing apps. I.e. ugly at least on Linux. Also, the up to date documentation is difficult to find, I'll like to it here when I get home. The GitHub-linked docs work fine, but they do miss a few features.
Seesaw/swing are the old stable solution. The latest library is cljfx built on JavaFX. The developer is very responsive and open about it's development
It's all React-like. You have a state atom and a GUI map data structure (which I think is equivalent to your DOM in React..) and then you hook up events that update the state and blah blah. It's all very clean and easy to read/use. The underlying JavaFX is also great for an OO GUI library so it's not gross to dive into if you need it
For frontend Reagent (React but with just Clojure functions and data structures. No classes, no jsx, just one language and immutable data.), and re-frame which does state management and event dispatching better than the competition. Redux is a poor poor imitation.
There is a swing example on the getting started page (or there was a few years ago). It was a one liner to have a Swing pop up say "hello world" or something like that. I'm not sure how easy it is to write actual apps that way.
Dynamic and functional. Lazy evaluation. Real immutable data structures. Core.async. A modern lisp not constrained by the exclusive use of parens, ie. [] for vectors and {} for sets & maps as with JS, Python and Ruby. Real REPL-driven development. Macros. Spacemacs with CIDER. Polymorphism which beats OOP at its own game. Massive Java ecosystem at your fingertips plus 25000 pure Clojure libraries and last, but by no means least - a rock 'n roll BDFL with a mullet.
- Immutable data-structures with concise literals for lists, vectors, maps, and sets.
Having pure functions and immutable data-structures makes code easier to reason about, easier to test, and thread-safe.
(But clojure doesn't "force" you to be pure. The idea is that you write as much of your code in pure functions as you can, and push the IO and impure parts to the extremities. It's very pragmatic in this way, and you can get real work done.)
- Simple syntax:
While the syntax may be intimidating at first and arithmetic looks weird to untrained eye, once you realize that everything is a function (or a special form that also looks just like a function), the very light syntax and consistency feels amazing/refreshing.
- Macro system:
This is tied to the previous point. Since all your code is technically a "list", and you have something akin to a pre-processor where you have the full library of clojure functions to manipulate data. But in this pre-processing phase, your data is the code. (the code is a list). Now you can dynamically re-arrange or re-write code. Lookup homoiconicty and learn about the kinds of things you can do in macros that can't be done in other languages.
- Capabilitis for general programming, abstractions, code re-use etc.
- Performance is quite good. It's often nearly as fast as java, yet your codebase might be 10x smaller because the language is so expressive.
- Concise/expressiveness. Clojure comes with a nice built-in library with generic functions that you re-use again and again. Give a programmer a few dozen of these functions and it's amazing what can be composed to solve many problems succinctly and elegantly.
- There are surely other benefits, but the last one I'll leave with is hard to explain unless you have felt it before. It's REPL driven development.
Clojure comes with some seriously awesome REPLs (i.e. a shell for interactive tinkering with the language). Other languages may have some form of REPL, but no other language in my experience has come close to the feel you get in a clojure REPL. I can best explain it as a freedom of very light-weight experimentation that you use to write your code. It's a playground for writing functions with very quick feedback to see if your code will work or not. One factor that makes the clojure repl experience so nice ties back again to the succinctness and expressiveness of the language. Typing commands in the repl is painless because it's concise, not a lot to type, and then the feedback is so instant.
Somehow when I write code in python, java, javascript, or other languages, I just don't use their REPL or shell as often. It's just not quite the same as the clojure repl experience.
We have to quote the parameter (1 + 2) as clojure evaluates arguments to functions (mostly..), by quoting it we are saying don't evaluate, instead treat it as data.
So you can see
(+ 1 2)
Looks like Clojure code, even though it's a list.
We can eval it:
(eval (swap '(1 + 2)))
=> 3
It's inconvienent to have to remember to quote the params and call eval.
Up steps macros, macros are evaluated before compile time and don't evaluate their arguments. So we can rewrite swap as a macro
You couldn't write it as a function, unless you pass in the 1+2 part to an outer function (macro) as either a list of arguments (1, math.add, 2) or a string (+you have an eval fn).
At that point you're emulating lisp without the elegance, and the first approach is only possible because functions are first class objects. If you were trying to rearrange an expression that had control flow or keywords in it (eg. Modify the behaviour of an if) then you would have to reify the "if" (change the original code so it had a class to represent the if not use the keyword). So you're kind of reinventing lisp by wrapping every part of your language as an object
Clojure tried to evaluate it's argument to swap, and the argument was (1 + 2), which is a function call, where the function is 1 and the arguments are + and 2.
So we quoted it in the function call by putting ' in front of the list '(1 + 2):
(swap '(1 + 2))
=> (+ 1 2)
Here, we stll didn't get 3 as our output... We got (+ 1 2), which is a list. Because the function returned a list, it didn't return code! It might look like code, but it's not code! It's a list.
So if I was to
(+ (swap '(1 + 2)) (swap '(3 + 4)))
=> Crashes! Can't convert alist to a number.
Because what it actually runs is
(+ '(+ 1 2) '(+ 3 4))
Whereas with the macro
(+ (swap (1 + 2)) (swap (3 + 4)))
=> 10
Works because the macro gets expanded BEFORE compile time, and our swap code gets replaced out with the code the macro generates!
In clojure if the reader (compiler) ever sees a list like `(2 3)` it evaluates it as a function call with the first item as the function so you'd need `'(2 3)` or `(list 2 3)` to generate a list literal (or more often `[2 3]` as a vector type in clojure).
You could do `(defn swap [the-list] ((second the-list) (first the-list)))` (which would invoke the second item as a function on the first item). It comes down to is the list a literal list as a parameter to something or is the first item a function to be invoked.
It was an admittedly simple example that was to show how easy it is in Clojure to treat code as data and data as code.
The main point of macros is when you use a regular function in Clojure you have applicative order evaluation. Macros do not, as macros are designed to transform and generate code.
A better example would have been the (when) macro.
In Clojure you have (cond) and (if) for conditional evaluations. If has the form
(if (cond)
(when-true)
(when-false))
e.g.
(if (= 1 1)
(println "1 = 1")
(println "uh-oh the unviverse is broken!"))
=> "1 = 1"
Now what if we want to do more than 1 statement on the true path and we dont care about the false path?
COuld we implement this as a function, sure? BUt we would have to quote the function calls when we pass them so they aren't evaluated and then eval them if true. You could do it, but it would be messy.
(if (= 1 1)
(do
(println "1=1")
(println "Also hello")))
So we want to extend the language so we can write (when) and the compiler will write us an (if (do))
This is incredibly easy in Clojure. We just need to create a list where the first item in the list is `if`, the second item is the conditional we pass to the macro, the 3rd item is a sublist, of which its first item is `do` followed by the list / functions to execute when our test evaluates to true!
(defmacro when [test & body]
(list 'if test (cons 'do body)))
This is just the source code for the actual clojure core when macro. But you can see how easy this is!
If we run the macro expansion on when we can see the code that it generates:
After you have a lot of nesting this can get difficult to read, so you have a macro ->> which is the threading macro. This takes a series of functions and threads the result of each function as the input to the next.
(->> (range 1 11)
(filter even?)
(reduce +))
The source code for this is almost as simple as (when)
The ` ~ and ~@ are just doing some quoting and quote splicing to determine when we want stuff evaluated.
Basically, using macros you do things like control symbolic resolution time, extend the compiler to create a DSL spcific to your domain and reduce boiler plate code.
That's before you start getting into properly weird stuff like anaphoric macros.
Thank you! I think I've seen the "if" and "when" examples before, but it clicked better this time.
I've read a few Lisp books and dozens of internet blog posts, so I know about macros and why people use them without getting the full understanding which comes with actually writing code.
Because function arguments are evaluated before the function itself. In this case, that evaluation would fail because numbers do not implement IFn, and hence cannot be called.
Macro arguments are not evaluated, and so this works.
You could make it a function, and pass the arguments quoted, but it'd be more cumbersome.
Macros are actually my least favourite part of Clojure. Sure, its great to have them and there are some libraries that use them to excellent effect (instaparse, Hugsql, etc), but most of the time, I prefer tools that don't use macros. You can see by comparing libraries that were made in the earlier days of Clojure versus more recent ones: the earlier ones love to use macros while the newer ones prefer functions and data. The data-first libraries are, in my opinion, easier to test and easier to build complex things on top of. If I look at a readme and see that the expected way to interact with a library is a macro, I usually look for a data-first library instead and only use the macro one if I can't find an alternative.
Macros are technically cool, you can do some interesting things with them, but I find that in most cases where they're used, they are inferior to alternatives. Its technically very cool that core.async could be implemented as macros, but I feel that core.async greatly suffers from it versus being built into the runtime: you cannot use many operations in functions called by core.async/go because the macro can't see inside function calls. Also, I've had exceptions thrown by core.async where the stack trace did not mention any of my code. That was not fun to debug.
Don't get me wrong, macros do have their place: hugsql uses macros to parse the SQL file and generate functions for you, which is awesome! But for every great macro-based library, there are many more that I wish didn't use macros.
(This is user-facing macros in a libraries' API. I have no problem with using macros internally)
Absolutely, I even mentioned some cases where I felt they were used to great effect. Having said that, though, in my experience, they do cause an “just build it with macros” attitude. For example, the issues I mentioned with core.async will likely never get fixed as doing so would require compiler/runtime support which is highly unlikely to happen.
What's made clojure so great, imo, is its unicorn status as a principled-yet-practical language. That "principled" part is not worthless---it means that a lot of great minds are drawn to it. Before react took over the world, clojure folks were already taking steps in that direction.
A lot of other things. The "sequence" as a core abstraction is very powerful. Immutable data structures by default make functional programming perfomant and efficient.
Speaking of data structures, data structure literals have spoiled me for other languages. After Java, especially.
And none of this mentions clojure's lispiness. The ease of metaprogramming has allowed the community to build some of the best tooling out there, between CIDER for emacs, and Figwheel for the browser---oh, did I forget to mention clojurescript? Being able to reuse code on the front and backend is great for web applications.
Clojure isn't "everything." I think I'd still benefit from learning Haskell, APL, and Forth, and I wouldn't mind knowing Ruby and js a bit better. And I'll probably be dragged back to Python and R if I keep doing maths.
But if someone asked me which single language would probably do the most for them professionally, I'd say clojure. It's the language people start startups so they can use it.