
Fourteen Months with Clojure - lackbeard
https://blog.skyliner.io/fourteen-months-with-clojure-beb8b3e4bf00
======
klibertp
I only worked with Clojure professionally for a couple of months, but the
experience was less than stellar. Most of it probably had to do with the
codebase being written by people without any experience, but a couple of
problems could be traced back to the language itself.

I'm preparing to write a lengthy post comparing Clojure and Racket, the other
Lisp I have been using for personal projects for a couple of years now. My
opinion is that Racket is a better language overall, although it may be less
practical in terms of writing production code and it certainly lacks some of
the nice features Clojure brings. On the other hand, many of these nice
features are available as libraries (not only in Racket - in most Lisps,
including Elisp and Common Lisp).

For the last two years, I used StumpWM as my window manager and so I learned
some Common Lisp. Again, as a language, I think Common Lisp is still better
than Clojure, although it's definitely stranger.

I use Emacs as my default "computing environment", so I naturally learned
Elisp, too. This is the only Lisp I used which I'd consider inferior to
Clojure, but even then it works quite well for its use-case.

All in all, from the perspective of Lisp family of languages, Clojure doesn't
seem to be exceptional in any aspect. It does have nice features, and I bet it
feels much better compared to Java, but it also has some downsides to it which
are irritating when coming from Racket or Common Lisp.

~~~
venantius
This experience is extremely common. I have lost track of the number of people
who have formed a negative opinion of Clojure because they were forced to pick
up the pieces of a half-baked project written by someone who wasn't familiar
with Clojure or its idioms.

It should go without saying that this should not actually reflect poorly on
Clojure as a language.

~~~
shadowmint
The problem is that problem clojure code is the gift that keeps on giving, and
it boils down to the problem I have with clojure in general:

Bad clojure code is unmaintainable spaghetti code; which gets worse over time,
as people attempt to 'patch on' fixes without doing the heavy lifting of
trying to figure out:

\- What was the original author actually trying to do?

\- Why the heck did they do it like this?

\- How do we create the same functionality and prove it works with these
rubbish tests that only test the individual units of work, not the application
function?

\- Why is it all in one giant file?

I've never seen code bases descend into chaos as fast as our clojure ones
have.

Nice, elegant clojure is a pleasure to work with for personal projects, but
I'm never using it professionally again.

You might argue it doesn't reflect on the language, but I think it does. Given
what I've seen, I'd argue that clojure has an _inherent complexity_ that
results in poor code quality outcomes during the software maintenance cycle.

...specifically, sections of bad code have a disproportionately negative
effect (compared to other languages) on the surrounding code and negatively
impact the entire project's code quality.

You hack something out for a deadline? You better go back and clean it up,
because if you don't that codebase is screwed. _shrug_ That's just been my
experience over the last year on three different code bases.

~~~
jraines
No language I've used is exempt from the possibility of people writing bad
code evoking those questions.

In fact I've had these reactions at various times to code in every language
I've worked in (including Clojure, to be sure), but I don't agree with your
argument about Clojure having greater than average inherent complexity.

Once you get comfortable with parens, the core seq functions, and even a basic
understanding of laziness & immutability, so much of the inherent complexity
you find in many programs just goes away. Well -- most of the time. Again,
Clojure is not going to stop you from negating these niceties with bad
decisions.

The one thing I'd maybe admit to it being a little above average on is the
temptation to over-engineer due to novelty or feeling clever. Let's rub some
core.async on it! Parallelize all the things! It's actually easier to do this
because of the _lack_ of inherent complexity in Clojure -- as the author
mentions, everything is oriented around simple maps & lists, so juggling them
and squeezing them through Rube Goldberg machines of transformations and
mystery macros is definitely a thing you can do.

But, ideally, you just learn to . . . not do that. Like abusing Ruby
metaprogramming.

~~~
shadowmint
Only in good clojure code.

Tell me you've never picked up a piece of clojure code and gone, WTF does this
do? What are these global channels for? Why is the whole application
constantly updating a top level full-application-state atom? Why are we
blocking indefinitely with some magical invisible state hidden in a closure as
we iterate over a collection of objects to process?

If you have a beautiful clojure code base, it's fantastic...

    
    
        But, ideally, you just learn to . . . not do that
    

How do you fix a bad code base when the functions _aren 't pure_ (global
injections, global channels), or are 'pure' only in the sense the input is the
_entire application state_ , including non-pure objects like a database
handle? Or your functions are non-deterministic due to some kind
producer/consumer race condition?

I get it; don't do that.

...but what _do you do_ when it's too late, and someone has already made those
bad decisions?

Where are the debugging tools to help you figure out what's going on, and the
refactoring tools to help isolate code units and replace them?

I mean, sure, you could argue that's an issue in any language, but all I can
say is that I've used a lot of other languages, and the only other similar
experience I've had was working with perl.

~~~
Zak
> What are these global channels for? Why is the whole application constantly
> updating a top level full-application-state atom? Why are we blocking
> indefinitely with some magical invisible state hidden in a closure as we
> iterate over a collection of objects to process?

I think something that would help the Clojure community improve, especially
with regard to introducing Clojure to a team is to ask why this kind of thing
doesn't happen in Python.

I know Python doesn't come with atoms, but you could certainly stick the whole
application state in a global variable. You _wouldn 't_ though if you're a
working programmer with a modicum of experience on your first Python project.
Do people just see some new idioms and forget everything they already knew
about programming?

Python does have closures, and you certainly could write one that blocks due
to some hidden state or does something stupid involving multithreading. This
isn't seen as a problem with the language though; it's seen as a sign that the
person who did it might need to work more closely with someone more
experienced or that the team needs better code reviews.

~~~
blain_the_train
> You wouldn't... stick the whole app state in a global variable.

Huh. I thought this is what Om Next did to great success.

* [https://medium.com/adstage-engineering/realtime-apps-with-om...](https://medium.com/adstage-engineering/realtime-apps-with-om-next-and-datomic-470be2c8204b)

[edit] i misunderstood your argument. Your not implying a single state is bad,
your saying their is nothing in python to deal with it.

~~~
shadowmint
I suppose. When you update and depend on a global atom your functions aren't
pure, at all, in any sense.

Why are you even using FP at that point?

Global application state should be represented in a single root storage; the
application data store (ie. database), and the interactions with it should be
controlled and sanitised.

If you have a thousand little places across your code base updating and
reading from the database, that's horrible code too, in java or in clojure...

Modern UI frameworks are carefully controlled access and update to the display
state for a UI; they happen in a controlled and orderly manner _specifically_
to prevent the chaos you get otherwise; if not, you're doing it wrong.

This section in the om docs covers their solution, which is quite elegant:
[https://github.com/omcljs/om/wiki/Quick-
Start-(om.next)#glob...](https://github.com/omcljs/om/wiki/Quick-
Start-\(om.next\)#global-state-coupling)

(notice, you don't just reach out and update atoms directly by hand; there's
nothing wrong with having a global application state; that's a good thing, but
_directly_ interacting with it is not)

------
mrbrowning
I really enjoy Clojure. Persistent data structures, core.async, and
transducers have all been really pleasant abstractions to work with. I've been
writing in it for fun and utility for maybe five years now, and have used it
for one mid-scale contracting project, so I hope that that's sufficient
background to prevent dismissal of the following as the gripes of a beginner,
but: it's so strange to me, for all of the appeals that Clojure makes to an
interactive development process involving the REPL and immediate feedback,
that a first-class debugging experience on the level of Common Lisp's doesn't
seem to be on the roadmap at all. The type of explorative developing you do in
that environment, with the ability to drop into the debugger on error, alter
the function body and restart the frame with your new code, seems about as far
beyond the standard Clojure workflow as REPL-based development is beyond the
save-compile-fix cycle of less interactive language ecosystems.

I can deal with the Java stacktraces and the occasional mystery of which
concrete class is actually backing IPersistentMap after however many
invocations of assoc, but if the experience of writing code is going to be a
pillar of the language's sales pitch, why isn't this sort of next-level
debugging experience on anyone's radar? The CIDER debugger is nice, but it's
not CL nice.

~~~
hcarvalhoalves
There's a tracing debugger for CIDER
([http://bpiel.github.io/sayid/](http://bpiel.github.io/sayid/)) and Cursive
plugin for InteliJ integrates a step debugger
([https://www.youtube.com/watch?v=ql77RwhcCK0](https://www.youtube.com/watch?v=ql77RwhcCK0)),
but let's agree matching CL's debugger is a tall-order - it's extremely nice
and only deals w/ CL, while Clojure, being a hosted language, cannot hide the
underlying implementation (interop calls are so common and a big selling point
of Clojure in the first place).

~~~
mrbrowning
You're right, and I ought to have mentioned the difficulty of implementing
something like that for Clojure. So maybe the issue is more holistic, which is
that it's hard to tell how it's positioned: it's not quite the sort of
language that drills as deep as possible into the affordances of being a Lisp,
but it's also not quite the sort of language that does everything just well
enough, given its investment in, for example, immutable data structures. That
doesn't stop me from using it, or presumably anyone else who gets value out of
using it, but I sort of idly wonder what the cases are that it's uniquely
suited to.

It might not matter: maybe being the obvious right choice for some use case in
the abstract isn't that important, and contextual factors re who's doing the
work and how they think are just as relevant to choice of language. Still, it
seems like we tend to talk about languages now in terms of their most
indispensable applications, for better or for worse.

------
jfaucett
Clojure is interesting in a lot of ways. I've toyed with it a lot, but what
actually keeps me from using it much is how tightly coupled it is with the
JVM. I know this is its big selling point for a lot of people but for me
personally I don't enjoy having to know Java's APIs and ecosystems to get
things done.

Is anyone else out there like me who wishes there was a standalone clojure
implementation that wasn't a hosted language? Add a modern package manager on
top of that like cargo or mix and I would love to write new projects in it,
because clojure itself is a very pleasant experience.

~~~
aduffy
Why not just use one of the Common Lisp flavors then? Racket has been rising
in popularity last few years: [https://racket-lang.org](https://racket-
lang.org)

~~~
jfaucett
mainly because the CLs aren't clojure i.e. AFAIK don't use persistent data
structures, and don't focus on robust concurrency primitives, don't have STM,
etc...

But if there is a CL out there that does all that I'd give it a try tonight :)

~~~
Jach
Lisp (what is a Common Lisp "flavor"? Racket is Scheme) has answers to all...
Of course the benefit of Clojure baking those things in is you can rely on
them in other people's programs, but still idiomatic Lisp seems to do an ok
job managing state, especially compared to other languages. Plus its other
features that Clojure lacks might make the tradeoff worth it.

Parallel programming:
[https://lparallel.org/overview/](https://lparallel.org/overview/) Functional
collections:
[https://github.com/slburson/fset](https://github.com/slburson/fset) STM:
[https://common-lisp.net/project/cl-stm/doc/index.html](https://common-
lisp.net/project/cl-stm/doc/index.html)

~~~
klibertp
Racket is not Scheme (anymore). Racket is a Lisp-1 with lots of different
features and "batteries included" stdlib. It's a "descendant of Scheme".

Worth taking a look into definitely, but it doesn't have STM and "persistent
data structures" which jfaucett seems to want. It does have solid concurrency
and parallelism primitives, though.

------
rcarmo
I sorely miss writing Clojure - it's the one thing I had to give up on my
current role (largely due to lack of time, but also because I mostly write
stuff in Python as a sort of lingua franca), and I find most of the idioms
slipping away though disuse.

This was an amazing read in the sense that a) it brought back all the reasons
why I began using it in the first place.

b) it had some great lines - both the "when the going gets tough the tough use
maps" title and this lovely tidbit:

"...maybe Scala has answers to all of these problems now, as I haven’t had the
pleasure of using it in several versions. Do not @ me to talk about this."

------
jwr
I write lots of Clojure and ClojureScript everyday, and the article really
resonated with me. Especially the parts about not using anything too complex
or fancy, and sticking to simple tools. My code also has very few macros, and
relies on lots of (complex) maps, using clojure.spec to keep them in check.

I think you get even more benefits from Clojure if you write apps that run
both server-side and client-side (in the browser, using ClojureScript).

Much thanks to the author for nicely showing a good use case for the Either
monad. In general, I think there are lots of great concepts in category
theory, but well hidden behind horrendous naming (bind/return, anyone?) and
lack of good practical examples (well let's fmap inc over a vector here).

And since it seems everybody pitches in something about some language being
inferior or superior to Clojure, I'll contribute something, too: I would not
have been able to write PartsBox.io
([https://partsbox.io/](https://partsbox.io/)) without Clojure and especially
ClojureScript. It boils down to practical reasons: code size, abstractions
that I can build on, avoiding accidental complexity.

I don't like participating in discussions that compare programming languages
these days. I feel these are often very shallow. As an example, I used to
program a lot in Common Lisp, and I think it can't meaningfully be compared to
Clojure/ClojureScript. You don't see that until you've written several large
multithreaded applications with zero deadlocks (thanks to STM), after you've
used core.async to simplify complex and bug-prone code, after you've used
transducer pipelines to parallelize a large stream computation completely
avoiding the horrors of Hadoop, or after you've sent your data structures over
a websocket to code that is actually the same code that runs on the server
(cljc, files that compile to both Clojure and ClojureScript). Seriously — how
can I discuss the finer points of syntax (oh, the parentheses!) when I am able
to ship React-based apps that do isomorphic rendering (the server pre-renders
a page, and JavaScript plugs into it later), all in Clojure+ClojureScript,
without even touching node.js? You start to appreciate those things only when
you write large applications and your time is limited. Going back to my Common
Lisp background, I also wrote web apps in CL, and there is no way I would even
think about going back.

There are things I do not like about Clojure, but so far I haven't found
anything even remotely comparable in terms of real-world productivity. So I'm
sticking with what works really well, at least until something better comes
along.

~~~
erichmond
Can't upvote this enough.

> There are things I do not like about Clojure, but so far I > haven't found
> anything even remotely comparable in terms > of real-world productivity. So
> I'm sticking with what > works really well, at least until something better
> comes > along.

This is exactly my take on it. I find myself thinking about Clojure adoption
more and more these days, and where is the disconnect in all this.

I wonder if the idea of clojure both scares away "normal" programmers who are
really just focused on using languages in their professional lives and want a
simple, focused productive experience, and instead bring in more
academic/experimental/recreational programmers who then actually dislike the
fact it's made choices to promote use in the real-world.

Super fascinating stuff!

------
whalesalad
I want to echo comments here by sharing that my professional experience with
Clojure has been dismal. It's definitely unfortunate because Clojure itself is
a wonderful language.

To give you a better idea: no use of component or similar, no ability to run
the app 100% in a repl. A junior engineer prematurely split a monolith out
into microservices that weren't truly isolated in their function (they shared
the same underlying db and a handful of "libraries" that were not libraries)
-- a true fucking productivity nightmare. Shipping a small fix would
oftentimes involve version bumping and deploying 3 separate jar's before you
could even QA it. You NEED to use your repl and reloading tools when doing
Clojure. You can't rely on your test suite, jenkins, or suffer through JVM
restarts every few minutes. When people do not follow these concepts Clojure
becomes an absolute fucking nightmare to work with.

There is a stark difference between programming in a functional/immutable
style and using a functional language. You can write imperative and procedural
style programs in Clojure just as easily as you can write functional programs
in Python.

How can we fix the Clojure ecosystem? It will be hard. Lisp is oftentimes an
esoteric environment. You don't see a lot of Lisp/Clojure (correct me if I am
wrong) in environments where you need to be pragmatic and move quickly. There
also isn't a "rails" for Clojure. With the exception of Lein (which is a truly
remarkable contribution to the open source community) -- there isn't a whole
lot to use as a metal model or better a popular style of building and
organizing your apps. It's all the rope to hang yourself with -- and if you
aren't an experienced hacker you're gonna hang yourself.

Finally: I find it really tough to find solid use cases for Clojure as opposed
to something else. The real things it has going for it are java interop and
easy concurrency. I usually have a very hard time choosing Clojure over Python
because of how quickly I (personally) can use Python to build extremely
readable, modular, single-responsibility, functional, etc... code.

~~~
johnbellone
My team is responsible for building small services on top of existing
databases (or other API) and we found real success with Clojure. It took a
little while to learn, but after you understood the basics it was damn simple
to dig into a deeply nested JSON response to get at only the data you needed.

We have since moved away from it because of the inertia required to get past
the curly fingernail stigma. But even with Go/Python we haven't been able to
get the same velocity compared to Clojure services.

~~~
charlieflowers
"curly fingernail stigma"

huh?

~~~
mhluongo
Bad translation of parentheses, perhaps?

~~~
charlieflowers
That's a pretty good guess. Makes sense.

------
jtmarmon
I loved the either-monad solution. The problem is something I've experienced
many times in my clojure adventures, usually resulting in either dirtying the
function in question with its own, case specific logging or creating the if-
let monstrosity you demonstrated.

As a side note: there's something very comforting when you immediately
empathize so strongly with a programming problem someone is writing about

~~~
sdegutis
For the nested if-let mess, I'd probably do something like this:

    
    
        (let-every [x (foo)     err "foo failed"
                    y (bar x)   err (format "bar %s failed" x)
                    z (goo x y) err (format "goo %s %s failed" x y)]
          (qux x y z)
          (handle-error err))
    

Where `let-every` is a macro that works like let, but stops short on the first
nil/false variable, runs only the next symbol binding expression, and then
runs the else-clause.

There'd be nothing special about the "err" symbol on each line. It's just the
next symbol binding, but on the same line as a convenience, and this means it
can reference any previously valid symbol bindings.

Here's a quick & dirty implementation of that macro. I don't have a Clojure
interpreter installed, so I don't know if it works.

    
    
        (defmacro let-every [bindings if-body else-body]
          (let [pairs      (partition 2 bindings)
                quad-pairs (partition 2 pairs)]
            (loop [quad-pairs quad-pairs]
              (if quad-pairs
                (let [[quad-pair]         quad-pairs
                      [try-pair err-pair] quad-pair
                      [try-sym try-expr]  try-pair
                      [err-sym err-expr]  err-pair]
                  `(if-let [~try-sym ~try-expr]
                     (recur (next quad-pairs))
                     `(let [~err-sym ~err-expr]
                        ~else-body)))
                if-body))))
    

Given the above example, it should expand to this:

    
    
        (if-let [x (foo)]
          (if-let [y (bar x)]
            (if-let [z (goo x y)]
              (qux x y z)
              (let [err (format "goo %s %s failed" x y)]
                (handle-error err)))
            (let [err (format "bar %s failed" x)]
              (handle-error err)))
          (let [err "foo failed"]
            (handle-error err)))

~~~
egamble
I wrote something like that several years ago:
[https://github.com/egamble/let-else](https://github.com/egamble/let-else)

~~~
SomeHacker44
I really like `:delay`. It will allow efficiencies in my already Haskell-esque
"define all possible values used in a single big let" style I program in. My
code all handles nil safely and returns nil when appropriate but it would be
even better to avoid evaluating things unless used in the body of the else (or
a later binding).

------
ryuuseijin
There is a neat macro I've been using to solve problems like the nested if-
lets in the post:

[https://github.com/Engelberg/better-
cond](https://github.com/Engelberg/better-cond)

    
    
      (b/cond
       :let [x (foo)]
       (not x) (do (log "foo failed") false)
       :let [y (bar x)]
       (not y) (do (log "bar failed") false)
       :let [z (baz x y)]
       (not z) (do (log "baz failed") false)
       :else (do (qux x y z) (log "it worked") true))

~~~
whalesalad
Have you looked into the `some->` threading macro?
[https://clojure.github.io/clojure/clojure.core-
api.html#cloj...](https://clojure.github.io/clojure/clojure.core-
api.html#clojure.core/some->)

~~~
ryuuseijin
Yes, it gives you short circuiting on nil values. The post mentions `some->`
and laments its inability to handle varying function signatures.

You can combine `some->` with other threading macros to make it achieve the
desired effect, and you can also achieve the desired effect with `if-let` as
the post demonstrates, but I believe the b/cond approach to be more readable
than both.

------
_vya7
After spending 50 months with Clojure, I can safely say it's my favorite
server-side programming language (with Datomic as my favorite database), and
the tooling (CIDER + Paredit + Emacs) is really downright amazing in terms of
productivity.

~~~
jtmarmon
Although the tooling is great, I found it super frustrating that you basically
have to use the entire suite of prescribed tools to feel productive. Basically
everything except emacs sucks with clojure, especially if you're a vim user. I
found that pretty frustrating. Vim fireplace sucks. Then if you want to use a
gui editor with a vim plugin you have to abandon the vim world and use editor-
esque paredit plugins (since no one is developing vim-style paredit for vim-
editor-plugins) _gasps for air_.

Then finally there's emacs which I really don't want to get started with.

~~~
hacker_9
I switched to IntelliJ + Cursive, and haven't looked back since.

~~~
SomeHacker44
Yes! 1000 times, yes!

------
bsaul
From someone that has never touched clojure ( or any lisp) i would say that
the python equivalent using either/right libraries made me feel like running
away as far as i could.

Could anyone provide me with an example of a code that would actually read
nicer in clojure than in python ? Make it as arbitrary as you'd like, i'm
honestly trying to understand . i've read enough about lisp bluring the line
between data and code, so i'm starting to get an intuition of its benefit. I
was just hoping to finaly see a real world example, and i'm a bit
disappointed.

~~~
unit91
Nicer to read is pretty subjective, but I'll tell you why I've switched 2
projects from Python and Ruby: speed. Our systems just had too much to crunch
through, and the global interpreter lock pretty much killed us. We switched
over to Clojure in 2009 and never looked back. In the early days, we had to
wrap a lot of Java, but it's been several years since that was necessary.
Currently, Clojure + Kafka + Apache Storm is the scaleability trifecta.

One final comment, which is often tragically overlooked, in my opinion: ease
of deployment. Package management and deployment often sucks in other
languages (especially in Ruby). By contrast, Leiningen is a SWEET dependency
management tool, and the built-on-Java approach leaves you with a jar that you
scp and you're done.

~~~
phren0logy
I do most of my work in python, and don't plan on switching any time soon, but
after playing with Clojure I sure do miss leiningen over the mess that is pip
/ virtualenv / conda / zomgihatepackagementinpython.

------
StreamBright
Clojure is the default go to language for me over the last 5 years. It is not
even funny how more productive Clojure is compare to Java if we are talking
about JVM hosted languages. There are obviously shortcomings of Clojure,
missing Either and so on, but you can always implement these simply, worst
case with a macro. It is good to see libraries like cats though.

------
baumandm
This was a great read. My favorite part was the section about rarely used
language features, as I have had a similar experience. It makes you wonder if
you're inexperienced or doing something wrong, when in fact those features may
just not be needed most of the time.

~~~
mpweiher
Having talked to a bunch of very experienced LISPers recently (including
Richard Gabriel) about this very topic, it appears to be the case that most
people go wild with macros for a little while, but this passes and then you
write fairly straightforward code.

~~~
cronjobber
I've written CL macros. I've noticed that for me, the probability of long-term
use seems proportional to the size of a macro's implementation. The more work
a macro does, the less likely I'm to revert to "straightforward code".

------
jonaf
The author briefly touches on schemas. Anyone have experience or
recommendations working with clojure.spec? If I were using Clojure with a
schema less database, would I stand to gain anything significant?

~~~
jcadam
I've been using plumatic schema:
[https://github.com/plumatic/schema](https://github.com/plumatic/schema) in
conjunction with compojure-api. It provides a nice way to validate data coming
into my REST endpoints. I'm currently using Postgres, but I'd probably still
make use of schema if I were going NoSQL.

------
stewbrew
What's the state of static type checking in closure? Is it accepted among the
community? My uninformed impression is that runtime checking via specs won?

------
dghf
> Nevertheless I definitely emitted some crappy code in my first few months.
> Stuff like:
    
    
        (every? #(= % “success”) (map :status (:state task)))
    

> Which I’d write like this today:
    
    
        (->> task
             :state
             (map :status)
             (every? #(= % “success”)))
    

While I love threading macros, I'd argue in this case readability and
comprehensibility between the two examples is more or less a wash.

Personally, I find the former slightly easier to grasp at first sight. The
second could be improved (again, in my personal opinion) by combining the
first two lines: it seems unnecessarily granular to say, essentially, "we take
a map called task, and then we get the value of its :state attribute", rather
than just "we get the value of the :state attribute of the map called task".

Both could be made terser via the set-as-predicate idiom:

    
    
        (->> (:state task)
             (map :status)
             (every? #{“success”}))

------
thom
Clojure's been my go-to language since around 2009, and I use it full-time
today. A few points from the article:

The horrible if-let code the author shows is exactly the kind of thing people
use macros for. I'm surprised nothing exists in clojure.core for this, but
every project I've ever worked on has something like if-lets, which short-
circuits on the first falsy assignment. Everyone thinks they don't need macros
and then finds stuff they hate in the language which is easily fixed by
macros. This is probably true of the non-mainstream paradigms in all
languages. It takes some willpower and humility to learn all Haskell's lens
arrow operators instead of just laughing at them.

clojure.spec is nice, and I use it in my current project, but it has
performance issues (it is after all basically alpha code) and I'll be honest
that I don't _really_ understand what the workflow is supposed to be. If you
deal with a lot of side-effectful code you might be using conforms and asserts
to keep things sane, but I'm not really benefiting from the generative testing
bits. Thinking in terms of generators for code that _is_ pure just feels like
another job that I've for some reason forced on myself. Anyway, at the very
least, it does make the world of procedural code operating on maps a little
safer and saner and you should probably try it.

My complaints about Clojure are nothing to do with the language, which I think
is absolutely fine. My main complaint is that Clojure people are smart, and
fundamentally believe in small libraries over frameworks, and this means that
the Clojure landscape is scattered with abandoned overly-specific libraries,
and abandoned and unloved big frameworks. If I could, I would pay someone full
time to maintain Incanter, but it's now basically dead, and that means Clojure
has no useful numerical computing environment. I am lucky that I can get by
with what already exists in Incanter and what's in clj-ml for my analytics
work, but nobody is going to quit Python or R for Clojure at this point unless
they're smart enough and willing to write all this for themselves.

I happen to do some web stuff and I think ClojureScript is about as good a
language as you're ever going to get on the browser. But the other consequence
of a community where most people are smarter than you is that the best front-
end frameworks are utterly inscrutable. Om has wonderful functionality that I
think I would like in my app, but I am too dumb to understand all the moving
parts. Reagent is very elegant but is tougher to scale to complex web apps.

I still believe that Clojure is a beautiful and productive language. Year
after year I see other languages pile on new syntax while Clojure just remains
brackets and functions. For the parts of my code that are pure and functional
it's wonderful, for the parts that are ugly and pragmatic I have the full
range of the JVM landscape available to me. I get to stay in Emacs, and Cider
me custom elisp does everything I need for an IDE. But if anyone asked me to
recommend a language to spend their lives in I would probably just say Python
and if you do web stuff JavaScript. Bigger community, better maintained open
source projects, and just a clearer roadmap to mastery.

~~~
dmichulke
Re incanter I fully agree - it also works for all my use cases (well, if
something is missing and you know what you want, you can usually write it on
your own using stuff incanter does have, did this eg for Logistic Regression)
but I think it has so much potential.

For some time I expected a Java lib to appear but whatever came was always
forcibly distributed (Spark, Mahout) and/or you had to first instantiate an
AnnotatedDistributionParameterFactory which instantly makes me want to puke.
So there we go, incanter is not maintained but probably the best you can get
on the JVM :-/

~~~
thom
I do recommend clj-ml if all you need is a set of sensible ML algorithms (in
this case based on Weka):

[https://github.com/joshuaeckroth/clj-
ml/](https://github.com/joshuaeckroth/clj-ml/)

The only reason to tolerate a language as terrible as R is that it has such
astonishingly broad library support - there is basically no algorithm you can
find on Wikipedia that doesn't have some sort of R implementation. Incanter
will never have this, and tbh it's very difficult to create outputs as pretty
as themed ggplots. For very simple statistical analysis Incanter's okay (and
I've seen smart people build lots more on top of it), but even really basic
model-fitting stuff isn't built in, and if you're not smart or just don't have
the time, it'd be mad to commit to that environment.

It's a shame because structurally it's quite nice, and I did buy into the idea
that Lisps were well suited to this sort of work, especially being very REPL
focused.

~~~
dmichulke
I never really took a look at Weka since I am using the results commercially
(or rather my clients do) and I think I "distribute" that software.

Now maybe I could make the case that I am not distributing the software but I
write it in their name and on their bill. In other words, I can use it as long
as my job is to write software (for them) instead of creating a product that I
will sell.

Still, I prefer to stay away from it as long as possible but I agree it will
be hard for some tasks such as decision trees which are unfortunately a very
nice fire and forget algorithm.

There are people claiming that you can do most of classification and
regression using logistic and linear regression, so I try to stick to that as
long as possible and incanter allows for that.

------
mark_l_watson
I have over one year of clojure experience on customer projects, spread out
over the last six years. I like the language a lot, but except for my cooking
website (cookingspace.com) and the server side code for an Evernote clone
prototype, I don't use Clojure on my own projects where I tend to use Haskell
for the fun of using Haskell and then Ruby and Java are my go-to languages to
get stuff done quickly.

I agree with most of the author's opinions about the use of Clojure except
that personally I dislike using multimethods.

------
dfan
Seasoned Clojurians: is

    
    
      (every? #(= % “success”) (map :status (:state task)))
    

actually considered crappy code? It looks perfectly natural to me, but my Lisp
background is more generic.

~~~
weavejester
No, not at all.

The threading macros are more useful when you have more nested code. For
example:

    
    
        (->> coll
             (remove nil?)
             (filter :available?)
             (map :stock)
             (reduce +))
    

Is perhaps a little easier to read than:

    
    
        (reduce +
                (map :stock
                     (filter :available?
                             (remove nil? coll))))

------
SCdF
I only ever monkeyed around with Clojure, but on their first example: is it
weird I'm OK with both?

The only thing that surprises me about both of those examples is that :state
is a collection of things not just one thing, but I imagine if you're more
familiar with their structures that would be less of an issue.

~~~
mcfunley
(author)

I just grabbed an example at random there. It's not really an excessive
example of nesting, but, at the time I didn't grok threading.

I think the data being manipulated is a cloudformation response or something,
so, the structure isn't something I'm in control of.

I actually changed it from `(:status (:status ...` in the original, which is
even dumber naming, so that it wouldn't look like a typo. Real life
programming is thoroughly unglamorous I guess.

------
agumonkey
ah nesting, threading and monads.

------
aisofteng
I think that there is something more fundamental underneath this experience
report that is worth remarking upon but that isn't mentioned in the article.

>If I were going to give you a quick summary of what our codebase is like, I’d
say it’s procedural code that manipulates maps.

>One thing we do use more extensively are multimethods. We use this to
dispatch asynchronous jobs in our workers, to fan out to different handlers
from webhook endpoints, and to make transitions in our deploy finite state
machine.

>... sometimes you find yourself boxed into writing non-idiomatic Clojure. A
good example of such a situation is dealing with a morass of heterogenous
functions that can return error codes.

Languages based on different paradigms excel in different situations. Lisp-
like languages such as Clojure (and, more generally, functional languages),
have abstractions perhaps most well suited to "pipeline" situations - take an
input, apply transforms A, B, C, ... in sequence until the output is returns,
bailing out if there is an error in some stage. Multimethods, which the post
mentions, are a good example of this: given input X, send it off to Y based on
some conditions. In software applications which have to do this sort of work,
it's a good fit.

If there is complex state to be managed under many different conditions,
however, these paradigms can be cumbersome to deal with.

>It is unclear to me if the category theory would still be a win on a less
experienced team. I have a long history of being skeptical of things like
this, but it has improved our lives recently.

The author mentions realizing that the Either monad well embodied the nature
of the algorithms being implemented. Speaking from my own experience, I would
suggest that teaching a team implementing the sort of software it seems the
author's team was implementing would be a worthwhile investment - but only if
it was reasonably clear beforehand that those abstractions would be well
suited to the problem at hand.

Perhaps the most important skill to train a team up on, in my opinion, is the
ability to recognize the type of problem at hand well enough to see what
abstractions would be well suited to it and thus what language to use. In
other words: it is very important to choose your tools well.

If I could offer some advice to the author (and others in similar positions)
regarding the question of investing time in training a team in any set of
abstractions, be it concepts in category theory for the sake of Clojure or
something else, it would be this: analyze the nature of the problem at hand,
make a decision on what tool (language, framework, whatever) you feel would be
best suited, familiarize yourself with its world if you haven't already, and
then have a meeting with your team in which you concisely explain that
analysis and the reason behind your decision. Mention why other choices would
not be as well suited, but, CRUCIALLY, also give examples of situations in
which the other tools you considered would have been the best solution. If you
have and reservations about your choice, explicitly state them. (In fact, you
may be surprised by someone on the team having had experience in the domain at
hand but not having mentioned it because it hadn't become relevant yet!)

This will not only teach the reasoning process but also give the team some
information about what alternate approaches to consider when faced with a
problem unlike the ones they have seen before, especially if the team is
junior.

Furthermore, sometimes one discovers that the nature of the problem being
worked on is, in fact, different from what it was originally understood as. It
is a lot to place on one lead to always be vigilant about that, and self-doubt
can creep in. If you transparently expose your thought processes to your team,
you may find yourself surprised by eventually having a team member saying to
you, "don't you think that this other tool/approach you had considered may
actually be better suited to <situation> now"?

It is a lead's job to lead, but it is also to share as much of their
knowledge, experience, and reasoning process as possible with the team.
Sometimes we go down the wrong route at first, but everyone on a team should
understand the why and what of every point along the way. As a lead, I make it
my goal to train everyone under me well enough to replace me and to feel
comfortable enough to voice any commentary on the decisions I have made.

I was going to go on, but I think I went off on a bit of a tangent. I'll leave
off here.

~~~
dcrystal
> ...functional languages), have abstractions perhaps most well suited to
> "pipeline" situations - take an input, apply transforms A, B, C, ... in
> sequence until the output is returns

That's very true. Glad to find out I'm not the only one who perceive it this
way.

This is the reason I believe multi-paradigm languages are more practical(or
better suited to solve real-world problems) - as they allow you to utilize
different approaches for different problems. At the same time, being too
wide/generic seems to lead to lacking of some expressiveness single-paradigm
languages could provide (like lack of native pointfree style).

------
bsder
> Why not? If a language's goal is to be practical (as is Clojure's), then it
> should take practical concerns into consideration. This is something that
> the Java community definitely gets right.

AHAHAHAHAHAHAHAHAHAHAHAHAHA!

Java getting practical considerations right. Oh, I so needed a good laugh.

Let's start from the basic:

No unsigned type. Bit/byte manipulation code written in Java is garbage when
written by experienced programmers. When written by inexperienced programmers
it's a nightmare.

Ever looked at the "class pyramid" monstrosities in Java? That includes the
two primary GUI systems.

Ever debugged all the stuff coming from the type erasure that you are stuck
with doing in Java?

And, let's not even gets started on the people who think they can write
concurrent code (Hint: if your Java concurrency solution doesn't involve
java.util.concurrent-you're doing it wrong.)

While Java gets many things right, it's ability to prevent people from falling
into pitfall after pitfall is _NOT_ one of them.

I would argue Clojure does a much better job at this simply by the fact that
immutability is much more of a default choice.

~~~
sqeaky
Why do people here try so hard to be condescending sometimes? Laughing and
mocking does not help when prior to it others tried to provide earnest
replies, based on experience and presumably reality. Please save the vitriol
for arguing with those reason does not work on.

Your points do have merits in showing Java is imperfect. I would even say that
it is deeply flawed. I would not agree it is impractical.

The simple fact about the practicality of Java can be shown in the millions
and millions of lines of code powering much of the boring business
infrastructure in the United States. If Java disappeared overnight Banks,
Insurance companies and much of the rest of the financial sector wouldn't open
the following morning (And then we look at our phones).

~~~
Jach
The same would happen for COBOL. That alone doesn't make it practical or show
its practicality, it only shows that it has actually been used in critical
systems, apparently with some success. Many programming languages can claim
that.

Honestly had the same reaction as above. It's a hilarious laughable claim that
the Java community gets this nebulous "practical concerns" thing right. Agree
it's not a helpful response here but I don't blame them for falling to it.
It's not exactly "forced". Probably better for @shit_hn_says though. Edit: the
much less nebulous claim by 'tlarkworthy is much better.

~~~
sqeaky
Sticking to the technical discussion. I agree that some businesses would have
to close for Cobol. That number goes down each year, with Java it is still
going up. I just the picked the financial sector because we all know about
banks and most of us have insurance, so its visible. I also worked a contract
at Nationwide (who could do limited business without Cobol, but not without
Java) and got to see both in operation. There are many less visible things
that would fail but could have more impact.

Without Java the US military is in trouble, a majority of cell phones stop
working, the printer in the office I am now fails, Netflix has some level of
problems and more and more... It is debatable that Java is the single most
widely deployed programming languages. I say debatable, because many of those
JVMs were written in C and many of the kernels (when they need a kernel) JVMS
run on are written in C, where do we draw that line.

If Java were not practical this would not have been possible. I suspect it
would have looked more like Cobol, with shops of experts being able to run and
maintain it, but failing and migrating away as time went on.

~~~
Jach
There's not much technical discussion to be had when our views of practicality
are so different, and practicality itself so subjective and context-dependent.
If Java starts to smell like COBOL across the wider industry (not just HN
types) will it no longer be practical, because it no longer satisfies the
narrow criteria "has been deployed in critical applications _and_ is not being
migrated away from"[0]? What about the great tooling/perf/backwards
compatibility/ecosystem mentioned, do those not matter at all for practicality
concerns? I'm not sure. Maybe we'll just agree that practicality is a low bar
to satisfy and shouldn't be a high concern for much? Reminds me of the
discussions around WorseIsBetter, and a quote from Wayne Mack[1].

[0] I've heard from COBOL forums that the amount of new COBOL written each day
and existing in the world keeps increasing; they've even got Object-Oriented
COBOL now. I don't think it's unreasonable to assume that if COBOL had the
same market pressures Java does (plus university pressure) and if the world
was basically stuck with it in the same way we've had Java this century that
we wouldn't be able to push it to service our needs.

[1]
[http://wiki.c2.com/?TheSourceCodeIsTheDesign](http://wiki.c2.com/?TheSourceCodeIsTheDesign)
"I believe it is time to explicitly state the long held secret of software, we
do not need to do design; design is ineffective and costly."

