
My Increasing Frustration with Clojure - village-idiot
http://ashtonkemerling.com/blog/2016/06/11/my-increasing-frustration-with-clojure/
======
dantiberian
The thing to realise about Clojure is that it isn't an open source language
like Python. It is a language controlled very tightly by Rich Hickey and
Cognitect. Major work is done in secret (transducers, reducers, spec), then
announced to the world as a "Here it is!" and then suggestions are taken. This
goes well mostly, though it took a lot of outside persuasion that Feature
Expressions weren't the best idea, and to come up with Reader Conditionals
instead.

The underlying problem Clojure/Core has is their communication. If they would
come out and explain their philosophy then people wouldn't get frustrated and
confused when they expect the language development to work like other open
source projects. Clojure's development is functioning exactly as planned. It's
not a mistake.

A better way to treat Clojure is closer to a project at Apple (except for
Swift). You can submit bugs, and make suggestions for future improvements, and
if you really want to provide a patch. But go into it realising that it's not
a community project, and you're much less likely to get frustrated.

With all that said, the proof of the pudding is in the eating, and for the
most part Clojure has developed pretty well from Rich's tight control. I'd
love it if there was a Snow Leopard year for Clojure where the focus was
fixing longstanding bugs and feature requests, and more focus on those in
general, but the language is developing well.

~~~
MikeOfAu
Please everyone, upvote dantiberian's answer. He nails it.

The moment you get over expecting Clojure to behave like a typical "open
source" project and understand it is Rich Hickey's personal project (+
Cognitect's), which happens to also be available to you, if you want to use
it, then the whole thing makes more sense. Some frustration disappears. You
know where you stand, and what to expect.

Hickey and Cognitect have every right to run it the way they like. IMO, the
only thing they don't do right is to communicate their intentions. There's a
pretense maintained around it being an "open source" project, when it really
isn't, not in the way that python is open source.

Use it if it works for you. Otherwise don't. Don't expect to have a voice, and
don't expect you'll be able to contribute much, and don't expect that
something will be fixed unless it suits Hickey's Cognitect's agenda.

I use ClojureScript, knowing and accepting these things (with some sadness),
because I think the language gives me a competitive edge.

~~~
lemming
I agree - this is the only right answer. Like you, I'm sad to have to accept
it, but it's the only way to a frustration-free (or lower-frustration) Clojure
experience.

------
_halgari
Something that is often very hard to understand (it took me years to do so).
Is that maintaining a language is insanely hard. Everything has a cost. Let me
give a good example: A few years back someone submitted a patch that improved
the error messages in Clojure. Worked great, just a single extra "if" and a
message. It was committed to master.

Then people's code got way slower. Why? Well this was a often used function
and that single if blew the JVM inline budget causing that function to never
be inlined. And that improvement had to he yanked out.

When we talk about patches for a language, we have to realize that if we don't
want constantly breaking APIs we have to test that a patch doesn't break
existing code, that it also doesn't slow down existing code (or at least not
enough that users would care), that in all sorts of cases the JVM won't freak
out and do something bad. Then we also have to make sure that the change
doesn't restrict the language in the future. Does making some interface public
mean it will always be public? Are we happy to keep it that way for all time?

Taking all that into consideration, I have to sit back and say , yeah, maybe
I'll just remember to only hand sets to the functions in `clojure.set`. Or
maybe I'll help out by adding clojure.spec annotations for these functions so
I can turn them on during testing.

In the end, I'd much rather have a rough-around-the-edges language that takes
a garbage-in-garbage-out approach, than one that breaks the API at every turn.
Perhaps they aren't mutually exclusive, but it certainly takes a heroic
effort, and a lot of prioritization.

~~~
lawpoop
Sounds like there might be a potential for a dev and production codebase?

~~~
msbarnett
This is in a sense what the clojure.spec work would allow -- dev time
exceptions when you violate a function's contract that have no impact on
production speed.

For some reason the blog's author dismisses spec as irrelevant greenfield
development, when it would quite neatly solve many of his complaints

------
brandonbloom
Clojure is a language for people who know what they are doing by people who
know what they are doing.

If you're giving sequences to the set functions, you either (A) don't know
what you're doing or (B) have yet to discover that clojure.set is not what you
want.

Set union can't be done efficiently on arbitrarily sized sequences. At least
one of the two arguments needs to offer fast set membership; ideally the
larger one, hence the requirement that all arguments be actual sets. If you
have sequences, you need to make an explicit decision to pay the cost of
indexing (eg. via set or into functions) or you need custom code for your use
case.

Sure, static or dynamic enforcement could help catch silly errors here, but
then the author goes and poo poos on "greenfield" development of things like
spec, despite the fact that would "fix the bug" exactly as requested by
throwing when you gave non-sets to the set functions.

I've followed a lot of the discussion around the supposedly horrible way the
Clojure community is run. So far, the absolutely only complaint that I can
sympathize with is that communication could be faster, clearer, and softer.
Alex Miller has made dramatic strides in improving this, but he's just one
man. Cut these guys some slack, they work crazy hard to build and shepherd
something awesome for free.

~~~
jgalt212
> they work crazy hard to build and shepherd something awesome for free.

I'm not so sure about that. I'd say that Clojure now exists and thrives to
help Cognitect make money. This is no way a bad thing, only a reminder that
there is no such thing as a free lunch.

In a similar vein, Go exists to help make Google money. The original purpose
of open sourcing it was different for Google (make the language more
robust/better) than it was for Hickey (grow the language/community).

~~~
brandonbloom
I anticipated this response to the word "free", but I stand by my statement.

Clojure is _free_. If you don't want Cognitect stuff, don't use it. If you
don't like how Rich/Cognitect et al are running the project, take your EPL
licensed ball and go home. Similarly, in the case of Go: Substitute Rob/Google
and BSD-ish license.

Just because these languages serve the interests of their respective
maintainers does not make them any less free to you. It may make the community
less useful to _you_, but hey, they created these things to be useful to them.
There's an implicitly mutually beneficial social contract around contributions
and community. If you don't like that contract, don't use the language.

In the very unlikely case that Cognitect or Google puts some company-specific
stuff in the language in a way that actively destroys the good will of this
social contract, then and only then do you have any right to complain.

~~~
jgalt212
> does not make them any less free to you

Of course not, but it does make them unencumbered. And I'm not complaining.
I'm just saying that's cost (i.e. not free) of doing business with a sponsored
open source language.

~~~
brandonbloom
If you take the stance of "nothing is free", then the word "free" isn't
particularly useful.

~~~
jgalt212
A+ extreme reductionism.

I never said "nothing is free", nor implied that, but merely that users need
to cognizant of the goals of "free" language maintainers (especially when
there is a corporation rather than a person as BDFL) that do not necessarily
align with the goals of the median or average user.

Now that we are on this topic, I'm starting to think maybe it's worth paying
for a programming language so that the goals of the maintainers are better
aligned with the goals of the users. That being said, there is real value to
the ability inspect the source. As such, it's very difficult to do open source
as a paid product.

~~~
calibraxis
There is a history here. Please read:

1\.
[https://web.archive.org/web/20100220100027/http://clojure.or...](https://web.archive.org/web/20100220100027/http://clojure.org/funding)

2\.
[https://web.archive.org/web/20140704050611/http://clojure.or...](https://web.archive.org/web/20140704050611/http://clojure.org/funding)

My own interpretation: programmer culture has such problems that someone
actually considered corporate funding healthier than user donations.

~~~
snaky
There are many definitions of the term 'healthier'. But the links are pretty
amusing. And now we have a first fork [https://github.com/jaunt-
lang/jaunt](https://github.com/jaunt-lang/jaunt)

------
jonnybgood
Clojure is not a community-driven language. It belongs to Rich Hickey and by
extension Cognitect. The bugs that will be fixed and the features that will be
added are the ones Hickey cares about. Not saying this is good or bad. It's
just how Clojure development is ran.

~~~
aaron-lebo
This is the most realistic answer.

Steve Yegge was making similar criticisms years back, saying Clojure was
"user-hostile". That's probably not fair and largely a matter of opinion (see
the link for a back and forth), but it is another piece of evidence that says
the Clojure community does things their own way. Not shocking, it's a lisp!

[https://www.reddit.com/r/Clojure/comments/3jjit5/what_does_s...](https://www.reddit.com/r/Clojure/comments/3jjit5/what_does_steve_yegge_mean_by_clojure_being/)

Clojure development is opinionated, for good and bad.

~~~
snaky
There's nothing wrong to be user-friendly or user-hostile. But it helps to be
clear about that up front. Good example, from not user-friendly (but
developers-friendly) Linux distro -
[http://exherbo.org/docs/expectations.html](http://exherbo.org/docs/expectations.html)

------
lgrapenthin
As a dev programming in Clojure professionally day to day this complaint (core
team not fixing/prioritizing bugs) seems absurd to me.

The only example given for a bug is not even a bug but undefined behavior
triggered by undocumented API usage.

If the author means clojure.spec by "greenfield development" (I don't see what
else he could mean) he should really reconsider his complaint since
clojure.spec is going to do what he is asking for (throwing a runtime error
during development if you use clojure.set with the wrong datatypes).

My impression over the last few years is that the core team became truly
excellent in reacting to users needs and reports often instantaneously,
especially Alex Miller and David Nolen on the CLJS side.

~~~
mcguire
" _The only example given for a bug is not even a bug but undefined behavior
triggered by undocumented API usage._ "

C gets a lot of grief about that sort of thing. It's interesting to see
current languages taking the same approach.

~~~
lgrapenthin
Could you elaborate why? (Haven't done much C) - Is the point of view that it
is the languages job to prevent the programmer from relying on uncertainties?

~~~
mcguire
The C standards take the rather unique approach of dividing behavior into 4
categories:

* _standard-defined_. You can rely on this; if the code you write is limited to this, you're good to go.

* _erroneous_. The compiler is supposed to give you an error message when you write code like this.

* _implementation-defined_. A particular compiler can do something reasonable with the code. If you write code depending on implementation-defined behavior, you're ok, but your code is not portable. A compiler that doesn't do something reasonable in an I-D situation _isn 't_ required to generate an error, IIRC. On the other hand, whatever it does is supposed to be consistent.

* _undefined_. There are explicitly no requirements on the compiler. Whatever it feels like doing is fine with the committee. There's no requirement for a compiler warning or error (and by default, there probably won't be one).

The reason for the different categories is twofold:

1\. C is a systems language. It's frequently used to do things that depend
entirely on the specific hardware the code is running on. The result isn't
portable, but in this case, it isn't supposed to be.

2\. Performance. The idea is that the compiler can take _portable_ code and
produce a program that runs very quickly on the specific hardware. As long as
the code doesn't do anything non-standard, the results are guaranteed to be
good.

An example of the latter is signed integer overflow. (I can't remember whether
it's I-D or undefined, but...) If you do MAXINT + 1, the resulting value can
differ on different hardware; it could be the most-negative integer, it could
be zero, it could cause a hardware trap. Now, the C standard could specify
what the behavior should be, say generating a signal that kills the program
with an error. But that would be very expensive on hardware that doesn't trap
signed integer overflow---it would require a check after every arithmetic
operation. So the standard doesn't say what should happen.

Another example is poking at memory that isn't allocated by your program (on
the stack, or the result of malloc, say). You need to be able to express that
for systems programming, but a general standard can't say anything reasonable
about it.

As a result, C has a lot of unsafe corners where you can unintentionally
invoke I-D or undefined behavior. In the first situation, your program will
break if you try to port it. In the second, your program might work, most of
the time. Or it might toss a runtime error. Or it could generate mangled
results.

Or it could have a really nasty and embarrassing security hole.

As a result, C is regarded as a difficult and (unnecessarily?) dangerous
language. And most languages that aren't C and aren't aimed at systems
programming try very hard to avoid undefined behavior. Even if you manage to
avoid security holes, the bugs resulting from mangled results are a royal pain
in the ass to track down.

~~~
lgrapenthin
Thanks for this long and elaborate reply.

Clojure tries to put as many user faults into "erroneous" as possible but in
the OPs complaint it doesn't have the required information at compile time
because it is typed dynamically. This is the fundamental problem with dynamic
typing that more problems keep ending up in runtime. Behavioral concerns like
security etc. are then often addressed by runtime checks.

Clojure avoids those runtime checks everywhere for performance reasons - It is
a clear design choice. So your observation seems correct that the "C approach"
is used in the dynamic parts of Clojure.

Since we don't have a standard, the official documentation is the contract we
should rely on and very soon clojure.spec, which is intended as a "standard as
data", is going to be.

~~~
lemming
_Clojure tries to put as many user faults into "erroneous" as possible_

I would absolutely disagree with this. Clojure has long had a Garbage-In-
Garbage-Out philosophy, which puts it clearly into "undefined" territory. The
examples with clojure.set are pretty clearly GIGO in my opinion, but
"erroneous" would mean a runtime check and an exception clearly stating which
invariant was violated, not returning something totally unexpected.

You could argue that it's under "implementation-defined", where the number of
implementations is one, but I don't see how you could consider the set
operations accepting and returning non-set things as consistent.

~~~
lgrapenthin
As I said, the GIGO approach is only used where costly runtime checks would be
the alternative. Macros check their arguments at compile time a lot.

~~~
lemming
I disagree on both points there, I don't think that an instanceof check is
that costly compared to many of the other performance hits we're willing to
accept with Clojure for saner behaviour. For example, persistent data
structures are slower than mutable ones, but I'd never go back to using
mutable ones in most cases. Why is one performance hit acceptable for better
behaviour and the other isn't?

I also disagree about macros, too - I spoke at the conj last year about that,
and that talk seems to have been at least part of the motivation for
developing spec. I hope that things will improve as spec becomes more widely
used, but right now the situation for macro error messages is pretty bad.

------
lkrubner
I love Clojure, and I use it in every project where I have control over the
technology, but I do agree with the points raised by Ashton Kemerling.

Clojure is beautiful, and backed by beautiful theories, but its implementation
needs to be clean or it will eventually develop enough special cases that its
beauty will be ruined. In some sense, you could say this is what happened to
Ruby, though there the allowance of "special cases" was more deliberate. I
recall, back in 2003, reading the interview between Matsumoto and Bill
Venners:
[http://www.artima.com/intv/ruby.html](http://www.artima.com/intv/ruby.html)
\-- and I thought that Ruby had a beautiful philosophy. But having worked with
Ruby for many years, I now see how much it is undermined by the many special
cases it allows, and the wild ambushes that its most imaginative features
allow (monkey patching).

I don't want to see Clojure go down that path. I want Clojure to remain
beautiful, and that means the implementations must also be beautiful. There is
a limit on how much the core team can say "That bug is only an implementation
detail". If you take away all the implementation details, then Clojure is only
a theory, not a real technology that can be used by real people to do real
things.

In a truly ideal world, Clojure could be the practical Lisp that pushes itself
to bring in as much of the experimental Lisp's beauty as is practical. I would
love to see the best ideas from Racket and what Fogus refers to as the
Fluchtpunkt Lisps:

[http://blog.fogus.me/2011/05/03/the-german-school-of-
lisp-2/](http://blog.fogus.me/2011/05/03/the-german-school-of-lisp-2/)

These are ideas that can make the world better. I have high hopes that Clojure
will be the project that makes some of these beautiful ideas practical. I will
be very sad if Clojure ends up like Ruby or Groovy or Scala, becoming a bit
too muddy to reach its full potential.

~~~
catnaroek
I don't see what Ruby's “beautiful philosophy” is. Here are some examples of
philosophies I actually consider beautiful, regardless of the result they led
to:

(0) Lisp, Scheme, Forth: “A small extensible core language beats a fixed large
language.” (While Common Lisp is actually pretty large, there's a rather small
subset of it from which the rest can be built.)

(1) ML, Haskell: “Let's see how much we can do using types as our primary
abstraction enforcement mechanism.”

(2) C++, Rust: “Abstraction isn't at odds with efficiency and fine-grained
control.” (Runtime enforcement is, though.)

What does Ruby have to offer? Matsumoto claims to prioritize “developer
happiness”, but allowing arbitrary modifications to arbitrary parts of any
program doesn't make everyone happy. And, even if it did, “developer
happiness” isn't a technical criterion anyway.

~~~
chriswarbo
I'm not a Ruby user, but its dynamic OO nature reminds me of the Smalltalk and
Self tradition, and those languages certainly do stick to a "beautiful
philosophy", something like 'it's method calls all the way down'.

Other beautiful philosophies include:

\- Logic programming. Although Prolog strayed from the path in the name of
efficiency, approaches like minikanren seem more "philosophically pure".

\- Term rewriting. I've only used it in the form of Maude, but Pure looks like
a more practical implementation.

\- Proof assistants. The (tongue in cheek) philosophy is something like "once
it compiles, we don't even need to run it". Agda is probably the most "pure"
example which is still maintained. Less elegant examples are Coq, which uses
imperative metaprogramming, and Idris which has no qualms with switching off
checks in the name of pragmatism.

~~~
catnaroek
Agreed about Prolog. I missed that one.

I know neither Maude nor Pure, so I can't comment.

Coq is ridiculously powerful, but hardly beautiful.

~~~
chriswarbo
> Coq is ridiculously powerful, but hardly beautiful.

I agree, hence my qualification that it relies on an imperative layer (Ltac)
to do anything useful, like dependent pattern-matching. I've edited my
potentially ambiguous phrasing.

------
dustingetz
I think this article is spot on. But. I think the root cause here is that many
of the tools are build by the community. Great tools cost a lot of money to
build, and you need to build the right thing at the right time, the cost needs
to be spread across the comminuty. The community is growing and tools are
improving. The first generation tools with all their rough edges are being
replaced by simpler tools that have incorporated lessons of the first gen.
Improvement is accelerating. So when the core team focuses on new features
like CLJC, Spec, Transit, Transducers, Datomic, ClojureScript, they are doing
things that only the core team can do. There are other more opinionated things
(like frameworks) that the core team isn't suited to do, everyone's app is too
different.

The thing i love most about clojure is that all the pieces seem to fit
together and be working towards a grander architectural vision much larger
than just a language or just a database. I use Clojure not because it lets me
code my java/javascript stuff a little better, but because this grander vision
makes impossible things possible. The thing I am trying to build is not
possible in any other ecosystem today.

So I am willing to eat the many little pains described in the article, pains I
feel every day, because the grand vision is so powerful, and hope that one day
the little stuff will be better.

~~~
wmccullough
I couldn't agree with you more. I've been wanting to learn Clojure for years
now, but I'm not going to fight with an IDE for hours to do so.

~~~
zdkl
1)install leiningen 2)install lighttable 3)??? 4)Clojure

Edit: You really don't need an IDE for clojure. A repl and a text editor are
your best friends IMO

------
dkersten
I'm a big fan of Clojure and have been since 2009. I'm such a big fan that I
run the local Clojure meetup.

I do think that Cognitects interaction with the community could be friendlier
in terms of bug reports and community patches. They have a bit of a history of
not communicating what they're doing (for example, the Zach Tellman thing
someone else mentioned). I don't think they need to change much to fix this,
they just need to communicate what they plan to do sooner (in the Zach case,
they could have told him sooner _" hey stop working on that, we'll think about
it but aren't sure, it might take some time"_ so that he knows not to spend a
year on it before it gets rejected/NIH-implemented). A tiny change in
behaviour would go a long way. Having said all of that, I still <3 Cognitect
and Rich and think they're doing great work.

I also think that Clojure has a bit of a stability/quality image problem that
could be easily fixed: They should put a feature freeze on the language for
ONE release and then spend 6 months or so resolving JIRA issues (bugs,
stability, quality; not feature or performance tickets). By spending one
release cycle on a "bug bash", I think would show the world that they care and
that yes, Clojure IS solid.

I've never personally been bitten by any Clojure bugs and don't think it
actually has of a quality problem at all. In my experience, Clojure is of
excellent quality. Its just a perception thing, really. Sure there of course
are bugs, but so does every other language.

After the 1.9 release is out would actually be an ideal time: spend 6 months
on quality and call it Clojure 2.0 ;-)

After that, I think people would be much happier to receive new features.

I don't think Cognitect will do that though, at least, going by how they've
worked in the past and honestly I won't lose sleep if they don't - I'll still
love Clojure and will still have buckets of respect for Rich and his team.
However, I do think it would benefit the community (and by extension the
ecosystem and language).

~~~
thaumasiotes
> I've never personally been bitten by any Clojure bugs

I actually have encountered one; the cl-format function is documented as
"implements the common lisp format function", but does not comply with that
function's spec. (Unless they've fixed it since I ran into the problem in 1.4,
which is sounding unlikely.)

------
nidu
Once I tried to make a union of a sorted set and an unsorted one. Result set
inherits behaviour of a bigger input set so when i first got my sometimes-
sorted set i thought like loosing my mind.

~~~
dilap
Avoiding madness like this is why I have come to really prefer statically
typed languages.

~~~
masklinn
The issue doesn't have much to do with dynamic typing, any subtyping support
can trigger it. Take Java, if Set had a `Set union(Collection)` method a
SortedSet could return a new SortedSet (cast as a set, but still with the
actual behaviour of a sorted set).

~~~
sanderjd
But you would not be able to pass it to a method that takes a SortedSet, which
means you can't rely on the sorted behavior. Incorrectly using behavior that
your implementation doesn't support is where the confusion comes from, it
doesn't come from your implantation having extra behavior that you can't use.

------
stuarthalloway
Alex Miller is the person primarily responsible for triaging Clojure tickets.
For a thoughtful, balanced assessment of the issues Ashton raises, read Alex's
detailed comment on the original post:
[http://ashtonkemerling.com/blog/2016/06/11/my-increasing-
fru...](http://ashtonkemerling.com/blog/2016/06/11/my-increasing-frustration-
with-clojure/#comment-2725501771)

------
catnaroek
There are basically four possible attitudes to abstraction:

(0) We don't need no stinkin' abstractions! Obviously not befitting a high-
level language like Clojure, move along.

(1) We enforce abstractions in our heads. Running into an undocumented
scenario is undefined behavior. (Yes, in the C sense.)

(2) We enforce abstractions via compile-time checks. Clojure won't have this
anytime soon, move along.

(3) We enforce abstractions via sensible coercions (whenever possible, if
desired) or runtime exceptions (anywhere else).

Like the author, I find (3) the most sensible choice, and I'm surprised that
the Clojure team would rather choose (1), which is known to be a major
productivity killer.

~~~
sethev
It seems disingenuous (in the blog post) to act as if Rich was saying that the
results of applying union to a list were correct when he was clearly saying
that the fact that it produced any value at all was an implementation detail.

~~~
catnaroek
I think you misread the author's complaint. He's complaining that the `union`
abstraction is leaky - it exposes its own ad-hoc implementation choices.

~~~
sethev
I think you may have misread it - he's pretty clear that he considers these
bugs that clojure core chooses to ignore because they either a) don't
understand them b) want to do cool new stuff

~~~
catnaroek
> he's pretty clear that he considers these bugs

 _Design_ bugs, not implementation ones. In other words, the author of the
post acknowledges that the existing implementation does what Hickey wants, but
the author would prefer that it did something else.

~~~
sethev
If that's true this blog post is just whining. He knows that Clojure core made
a decision he disagrees with and he's just really upset that after 7 years
they still haven't changed their opinion to match his.

~~~
catnaroek
Of course he's whining. And I don't blame him - I'd whine too if I had to use
programming tools that make it unnecessarily difficult to detect programming
mistakes. Conceptually, there are three very different operations:

(0) Concatenation, which acts on sequences, and is neither commutative nor
idempotent.

(1) Merging, which acts on priority queues and multisets, and is commutative
but not idempotent.

(2) Union, which acts on sets, and is both commutative and idempotent.

There are some sensible coercions:

(0) Every set can be coerced into a multiset in which every element occurs
exactly once.

(1) Every priority queue can be coerced into a sequence by lazily removing the
minimum element until the heap is empty.

(2) Every sorted multiset can be coerced into a sequence by traversing its
elements in order.

(3) Compatible coercions, like (0) and (2), may be composed.

But there's no sensible way to coerce a sequence into a set. The elements of a
sequence might not even have decidable equality testing, which the set
abstraction needs to ensure that no element is duplicated. The union of two
things that are neither sets nor coercible to sets should be treated as an
error, to be caught either at compile-time (which Clojure won't do, and that's
fine) or at runtime (which any sensible dynamic language should do).
Protecting abstractions is fundamental if you want to avoid bugs.

------
willvarfar
I'm not a clojure user but I am a fan of Rich's talks so I am kinda taken
aback by this - this is just not the kind of Clojure I was expecting from what
I know of the principles Rich preaches.

I really was expecting belt and braces defensive error checking and never ever
giving a dud result.

~~~
mcguire
It's a somewhat common problem with Lisps specifically and dynamically typed
languages in general.

Because the bottom-level operations won't let you do things that are
completely idiotic, everything is "safe", so maintaining the _sanctity_ [1] of
higher level abstractions sometimes gets lost in the shuffle.

I once wrote a blog post about related issues.[2]

[1] Ok, so there are lots of terms with just slightly the wrong meanings. I
came up with a new one. Sue me.

[2] [http://maniagnosis.crsr.net/2011/06/some-lisp-
suggestions.ht...](http://maniagnosis.crsr.net/2011/06/some-lisp-
suggestions.html?m=1)

------
daxfohl
Odd, in two years I've never been bit by a single one of these bugs. And if I
had, I could probably pretty easily shrug them off.

My frustration came from the lack of a type system, and that there's no
"myThing." (myThing-dot) to give you intellisense. Even many dynamic language
editors have some variant of this now. So coding in Clojure involves too much
looking stuff up and memorizing stuff, and is way slower than it should be in
2016.

~~~
dragandj
What editor do you use? Emacs Cider does this, and does it much better than
IDEs (IMO).

~~~
daxfohl
I use Cursive, and yes it has intellisense equivalent to Cider, but still not
good enough.

In Scala, Java, C#, F#, C++, I can do `myArray.` and it shows all methods
available to arrays. No Clojure editor has anything like this. JetBrains
editors take it a step farther and even include "code template" options that
apply. So typing "myArray." will also include options for "foreach, for, for-
reverse" templates. This, in addition to automatically importing any required
library for the method you select.

With Clojure, there's no "dot". You're stuck hunting through documentation,
trying to figure out first which namespace your function belongs to, then
requiring it, then figuring out which function you want. Cursive only helps
out with that last step, and even then, it displays _every_ function in the
namespace, when generally you only want to see the functions that apply to
your variable.

While I'm all about immutability and structured data types, I've grown
slightly away from functional-first languages in general for that reason. Even
in F#, you've got to do `myList |> List.map ... |> List.filter ...` etc,
requiring more lookup than C#'s `myList.Select(...).Where(...)`. The "dot" is
an underappreciated aspect of OOP.

------
Naomarik
Respect and treatment of people is a real concern. I made a decision not to
pick a certain library after seeing people being shut down in the issue
tracker in the most unnecessarily abrasive way possible.

Clojure does have a lot of amazing people though, and I've been very welcome
reaching out to people for help as a whole.

~~~
pdimitar
This is my exact sentiment in many areas of life, being an adult who've seen
many different ways of treatment of both myself and many others.

This exact HN discussion (and the original article) actually made me stay away
from Clojure for good. I was curious, I wanted to learn the LISP way of doing
things but I'm not gonna pretend that I am OK with people dismissing the
possible irrelevance of their language by a death of 1000 paper cuts -- of
which several were outlined the by the original article's author.

Same thing like the game Mortal Kombat X. Bought it for both myself and my
girlfriend and we love it but it turns out Warner Brothers & Nether Realm
Studios figured they'll support it only on the console almost immediately
after it was released on PC.

I have enough money to buy 5 PS4 machines if I want to, and to buy one MKX
copy for each. But I won't. I won't support people who treat their
users/customers like that.

Same goes to Clojure. Same goes for Rails in the recent months when DHH
actually has to write articles defending his strong opinions.

------
slantedview
Just saw this tweet from Rich Hickey [1]:

> If you think you know how I ought to spend my time, come over and mow my
> lawn while I expound on the problems of dev entitlement culture.

All due respect, but I don't think Rich gets it.

Other people are spending _their own_ time as well, researching, writing and
reporting issues, and in many cases, would be perfectly happy to submit fixes
for those issues (and in some cases, they have). It doesn't take any more work
for Rich or someone else to look at an issue and say, yes, this is a problem
and thank you for fixing it, then it does to say screw you, WontFix. And that
is where Clojure has a problem.

Rich's argument is really a strawman. Nobody is telling him what to fix, but
neither can anyone force him to recognize the importance of fixing long
outstanding bugs _in his own work_, even if, thanks to a great community,
fixing those bugs wouldn't require him to lift a finger.

1:
[https://twitter.com/richhickey/status/741982205392617472](https://twitter.com/richhickey/status/741982205392617472)

~~~
nikofeyn
i saw that too. it seems to be a rather childish response. i have recently
become very excited about clojure as someone new to it since there are some
greatn resources for the language in the form of books (even one on frp), but
this ordeal and the clojure people's response has turned me off of it.

it seems all peopme are really crying out for is clear and accurate
communication. i mentioned this in another comment that this attitude is a
stark contrast to the f# and racket communities.

------
Bahamut
I'm not a Clojure user, but this article reads to me like complaints about the
language's faults, and frustration it isn't solved a particular way. He may be
right in some or all of these cases, but some of the phrasing used does not
encourage anyone to look at it further. Attitudes like this make me want to
quit open source when I encounter it in projects I maintain and/or contribute
to.

------
hellofunk
I'm sad to see someone so clearly disappointed by this interesting and
innovative product. But considering his frustration, I think it's fair he get
a refund. But that's not possible, because it is free. In his own words, he
writes Clojure all day and loves his job, all because of this interesting and
free product called Clojure.

Clojure is far from a perfect language, but what language is? I also love C++
and you'd be hard-pressed to find anyone who thinks it is perfect.

There are flaws and unique quirks with any programming language. A good
language is like a person: it has a personality with all the good and bad that
come with its unique bundle of traits.

~~~
Chyzwar
Just because something is free do not make it holy and free from critique.

I left Clojure because its inherit both Java and Lisp flaws: memory hungry,
difficult setup, enterprise frameworks and hard to read, impure and slow.

I believe that there still chance for Lisp in Web Assembly.

------
undershirt
> To say that I was treated pretty shabbily by David Nolen on this issue
> almost goes without saying

He maintains projects by trying to triage issues in an efficient and
emotionless manner. I wouldn't take it as rude though.

------
ddellacosta
I'm having trouble finding much value in this post. It seems to be a
combination of the author picking apart very specific aspects of Clojure
(clojure.set? clojure.test fixtures?) in a highly opinionated fashion to
represent issues with overall project focus, combined with not acknowledging
the project's history (maybe that can explain the inconsistency with Protocol
usage--as far as I know Protocols only got added in 1.2), combined with not
acknowledging the size of the team and what they have to support, combined
with not acknowledging how much time is spent on dealing with real, important
bugs and performance issues (please see release 1.8's changelog, for example:
[https://github.com/clojure/clojure/blob/master/changes.md](https://github.com/clojure/clojure/blob/master/changes.md)),
combined with not acknowledging how much new development is actually about
features that users really want (see clojure.spec or reader conditionals in
1.7, both of which address long-standing issues previously solved by third-
party libraries, as just two things that immediately come to mind).

As someone who has been using it professionally for more than three years,
there is a ton I have to gripe about in Clojure. Personally I find the hodge-
podge type system and inconsistency with how certain core functions handle
associative data structures infuriating, and schema/clojure.spec on some level
I see as a hacky half-measure, and yes parts of clojure.test are semantically
inconsistent and awkward to use in ways that bug me. But: as a language
allowing me to get things done in a professional context, as a community of
developers with actual adult leadership who are generally welcoming and share
a practical, thoughtful philosophy, I think it holds up just fine against
other language ecosystems.

It would have been quite reasonable to write a blog post about the
deficiencies in clojure.set and clojure.test. Write a post about how Protocols
could be better used throughout the Clojure codebase itself, and maybe even
push some patches up via Jira and see what responses you get. And sure, I've
had some prickly interactions with David Nolen myself; he can be that way--he
also addresses bugs quickly, is constantly providing answers to questions on
IRC and Slack, and is pushing ClojureScript in new and interesting directions
while acting as the primary maintainer of the CLJS codebase (I believe?).

Point being, some of the issues raised in the piece may be real issues. But
they do not represent some kind of overarching deficiency in the team as the
author seems to suggest, nor are they necessarily relevant to the majority of
developers using Clojure professionally.

~~~
village-idiot
Let me get this straight, you agree with every single gripe I have, have some
more of your own, agree that writing a post on this would be reasonable, but
I'm wrong?

------
mbfg
Hickey's latest tweet:

If you think you know how I ought to spend my time, come over and mow my lawn
while I expound on the problems of dev entitlement culture.

[https://twitter.com/richhickey/status/741982205392617472](https://twitter.com/richhickey/status/741982205392617472)

------
daniel-levin
Do you have any experience with attempting to fix these bugs and then
submitting patches / pull-requests? Yes, the core team should just fix these
bugs. But if they're receptive to upstream changes you want made, then that
makes this much less of an issue, in my mind. An open source project's
openness depends critically on the core team's acceptance of user patches.

------
olivergeorge
Interesting bit to me is the communication problems, not so much the technical
ones, we live with tradeoffs.

The thing is communicating takes time and busy people are entitled to
prioritise "doing" over "talking".

As a community, Clojure is defined by the promises made and the processes
established. I think "Brand Clojure" is defined by some truly wonderful
attributes but it could be improved.

Perhaps this could this be fixed with process? For example

* Document architectural decisions [1] [2]

* Close tickets WONTFIX with a link to the relevant architectural decisions

Make that a process/promise so that it becomes a consistently applied approach
and it would give the community confidence they are being treated well, heard
and respected. "Brand Clojure" becomes more lovable.

But these processes also take time and we don't want to burden Clojure core
devs. (More realistically, I doubt they'd allow us to impose on their precious
time.)

So, can this happen without adding overhead for the core developers? I don't
know. It's an interesting question though. Can we learn from others online
communities on this topic? Are those who do this well based on dev leads who
are naturally inclined to communicate? Are any using community funded
positions to deliver on communication" outcomes?

Sounds like a community advocate/support role:

* Triaging tickets ensures inbound communication is effective and helpful. That helps by saving core dev effort. That helps avoid confusion on both sides.

* Generate architectural decision docs which can be linked from tickets

* Ensure WONTFIX doesn't feel like a dead end. Link to ADs, blog topical workarounds.

* Keep architectural decision docs fresh as consequences become more apparent over time.

Few ideas in there. Just a thought exercise.

\--

[1] See Documenting Architecture Decisions
[http://thinkrelevance.com/blog/2011/11/15/documenting-
archit...](http://thinkrelevance.com/blog/2011/11/15/documenting-architecture-
decisions)

[2] Is the 'garbage in/garbage out' architectural decision documented?

~~~
puredanger
I actually would love to have ADRs for Clojure - that's an exceptional idea.
The design wiki pages often record much of the decision record, but it's quite
common for them to be out of date by the time something is done. Will consider
more.

------
trmsw
The OP creates tickets with titles like "Clojure.Set Does the Wrong Thing" and
then calls people out for poor communication?

------
based2
[https://www.reddit.com/r/programming/comments/4nlhih/my_incr...](https://www.reddit.com/r/programming/comments/4nlhih/my_increasing_frustration_with_clojure/)

------
tel
This particular complaint feels like a typing issue. If functions can accept
anything and use lots of partial duck typing then they might partially accept
anything and not quite work.

There's just an unstated precondition: the arguments are sets.

------
OlegYch_real
this is the experience you'll have with any unityped language, because they
explicitly move responsibility from library designers to end users

and these undefined-behavior bugs are hard to fix because someone somewhere
already depends on that on undefined behavior, and it's just a snowball which
understandably leaves library designers unresponsive

------
blain_the_train
> And the namespace is completely riddled with bugs. union returns duplicates
> if some of the inputs are lists instead of sets depending on their length.

to be clear I assume he is taking about this:

``` try-spec.core=> (clojure.set/union [1,2] [1,2]) [1 2 1 2] ```

Though im not sure what "depending on their length means".

> for the above bugs there are two possible fixes: raise an
> IllegalArgumentException if anything other than sets are provided

Isn't this an argument for types? Wont this type of problem be prolific in any
dynamic language? What is the union of anything besides sets?

> coerce lists and vectors to sets before continuing

This only works for very similar data. How do you coerce a map into a set? Is
it based on keys or values? What about a string? is it based on the whole
string or the characters. I loaded up python3 to see how it was handled...

``` In [7]: set(["hello"]).union(["hello"]) Out[7]: {'hello'}```

is that what you would expect? Maybe. But honestly it should have thrown an
error imo. Python is just skirting the issue. A set needs a hashable type. A
list isn't one because its mutable. So python knew you "meant" a set of the
items in the list... It throws up its hands if you force the issue further:

``` In [8]: x = [1,2,3]

In [9]: set(x) Out[9]: {1, 2, 3}

In [10]: set([x])
\---------------------------------------------------------------------------
TypeError Traceback (most recent call last) <ipython-input-10-0b079714f02c> in
<module>() \----> 1 set([x])

TypeError: unhashable type: 'list'```

The problem is that we have handed the union function the wrong type of
arguments. That it fails in further down stream is the trade off we make with
dynamic languages. The community seems to admit the trade off and alleviate it
with things like clojure.spec. Im not mental equipped to argue type theory
here. But i suppose i feel this is an _ancient_ argument and saying its a
"bug" seems simplistic.

> How you define getting the wrong type with nonsense values counts as
> “working” is beyond me. Is it just because it doesn’t throw an Exception?
> Anyone here prefer bad data instead of exceptions when dealing with
> functions like this? I doubt it.

This is maybe the deeper problem. You would expect union to fail given the
wrong types. Sure the failure wont be a type error but it will be contained
inside the function. Leaking out the wrong data means its harder to debug. I'm
generally curious how and why set would work on lists at all.

As to the rest of the arguments made here. I don't know enough to hazard a
guess. But i would appreciate any feedback on my interoperation of the above
situation.

~~~
todd8
I'm a bit confused by your example. Python has different design goals than
Clojure. Clojure competes with rather high performance languages, Java and
Scala, on the JVM and it appears from the OP that its implementors have
decided that it can't afford to spend too much time at run-time checking.

Python on the other hand at least attempts to adhere to a principle of least
surprise and enforces strong type requirements at run-time. It does this
because consistency is generally more important than performance
considerations in Python, one of the reasons that Python is _much_ slower than
Clojure.

The API for the set constructor in Python accepts any iterable as an argument.
Furthermore the elements of a set must be objects that are hashable.

Consequentially, Python sets rule out mutable members, such as Lists, Maps,
and other Sets. Sets of Set Objects aren't allowed since Set Objects can be
modified. Strings are immutable so Sets of strings are allowed and even
frozensets (an immutable kind of set object) and tuples (which happen to be
immutable in Python) can be members of sets in Python. I believe that this is
all spelled out in the documentation pretty clearly, and the language's built
ins and standard library enforce this simple model at the cost of run-time
type checking.

Thus,

    
    
        set([1,2,3,2]) == {1,2,3}
    
        # the tuple (10,20) is immutable and hashable
        set([1,2,"abc",(10,20)]) == {1,2,"abc",(10,20)}
    
        # strings are iterables
        set("abc") == {"a", "b", "c"}
    
        # lists are iterable and strings immutable and hashable
        set(["abc"]) == {"abc"}
    
        # comprehensions and generators are iterable
        set(i*2 for i in range(5)) == {0,2,4,6,8} 
    
        # sets are iterable
        set({2,4,6}) == {2,4,6}
    
        # int 14 is not an iterable
        set(14) <--- error
    
        # and [2,3] is a list and so mutable and not hashable
        set([1,[2,3]]) <--- error
    

The interface to the union method for sets also takes an interable so,

    
    
        set([1,2,3]).union(["a", "bc"]) == {1,2,3,"bc","a"}
        set({1,2,3}).union({3,4,5}) == {1,2,3,4,5}

~~~
blain_the_train
I'm not questioning that python api isn't clear. Neither is clojure one. Both
require the user to read them in order to properly apply them.

I think pythons resolutes only seem less surprising because someone would be
used to them? Maybe that coercion is easier to reason about. Im honestly not
sure. Like this example:

    
    
       In [19]: s = set([1, 2, 3])
    
       In [20]: s.union({"x", "y"})
       Out[20]: {1, 2, 3, 'y', 'x'}
    

Is that more clear then explicitly requiring only sets be unioned?

Why the key and the value? The meaning of the key and value have been
drastically change now. Maybe this is what all languages do?

anyway, I think i gained a lot thinking this through. Thanks everyone :)

*One error Is I mistakenly said that strings weren't hashable.

~~~
todd8
Oh, I think I understand your question. This is a set literal, not a map
literal:

    
    
        {"a", "b", "c"}
    

This is a map:

    
    
        {"name": "Fido", "age": 8}
    

Note the colons.

The point I was making is that Python doesn't make performance trade offs with
these set APIs. Set union is in no way surprising. It either gives the correct
intuitive result or raises an exception because of incorrect argument types.

~~~
blain_the_train
I'm having quite the day. I think switching between clojure and python is
frying my circuit. I know the difference between a map syntax and set and
somehow managed to use set in place of map. I think this happened in part
because my point is that any output of union(set, dict) is up to the language
designer and has no basis in set theory (though i would be interested to here
other wise)

    
    
        >>> s = set([1, 2])
        >>> d = dict(x="hi")
        >>> s.union(d)
        set([1, 2, 'x'])
    

apologies for the confusion... I need to proof read my comments more
throughly.

------
calibraxis
I did not see the link to the intersection issue. Is it this?
([http://dev.clojure.org/jira/browse/CLJ-1682?page=com.atlassi...](http://dev.clojure.org/jira/browse/CLJ-1682?page=com.atlassian.jira.plugin.system.issuetabpanels:all-
tabpanel#issue-tabs))

So "nonsense values" returned by `intersection` means it returns a seq when
you pass in a seq? So `(:key_1)` rather than `#{:key_1}`?

------
eric_h
> The thing I am trying to build is not possible in any other ecosystem today

Umm... what? I'm afraid I don't understand how the "grand vision" behind
Clojure adds pixie dust that makes it capable of something another Turing
complete language isn't. Care to elaborate?

~~~
dang
This is the sort of generic programming language flamewar comment we can do
without. If you want to discuss questions like this on HN, please pose them
without snark.

We detached this subthread from
[https://news.ycombinator.com/item?id=11883875](https://news.ycombinator.com/item?id=11883875)
and marked it off-topic.

~~~
eric_h
As an aside, I don't think this thread turned into a flamewar, there's some
good discussion.

Also, s/he started it ;)

~~~
dang
I take both points but the combination of snark and genericness is still an
explosives-handling violation.

Thanks for the friendly responses.

~~~
eric_h
> still an explosives-handling violation.

Haha, what a fantastic way to word it. Duly noted.

------
MustardTiger
And that he is a well known troll.

~~~
dang
Not so well known that this doesn't cross into personal attack. Please don't
like this here.

We detached this comment from
[https://news.ycombinator.com/item?id=11884356](https://news.ycombinator.com/item?id=11884356)
and marked it off-topic.

~~~
MustardTiger
It is in his post history here, I didn't know "look at the other things this
person says" was considered a personal attack.

------
meeper16
Time to go back to Tcl.

------
gabrielc
I think elixir is a better choice.

~~~
hugdru
I was thinking of learning a modern language for the web back end. After
analysing elixir, go and clojure I decided to go for clojure. Uhmmm, maybe go?
I don't know :X. Stick with php? Meehhh

~~~
reitanqild
You could do worse than learning modern php. Esp. if you have some experience
with it and as long as you are ready to leave bad habits behind. (Going for a
full stack framework might help here by more or less forcing you to do things
the right way.)

~~~
cutler
As far as jobs are concerned you can't go wrong with PHP. Clojure jobs are
virtually non-existent outside London and even in London Clojure they are
scarce. As far as Indeed.co.uk goes even Rails is pretty thin on the ground
outside the capital. PHP, JS and Java, on the other hand, are a bag of tricks
you can take anywhere and always find work. Keep Clojure for your own
projects.

------
bootload
_" Basically I want Clojure to be a simple to use language backed by a
friendly and active community. What I see now is drifting in the wrong
direction, and I’d like to see that corrected."_

Opportunity here to create a company, fix the bugs, improve the broken bits,
build an editor and release a better clojure and _charge_ accordingly.

------
rurban
The underlying issue to me seems to be that Clojure is still not developed by
a team, but by a single developer, who cannot think out of the box.

union and intersect not doing the same thing as in common lisp (accepting
lists and vectors), and returning buggy values instead is just a bug, even if
the developer didn't have that in mind originally, and doesn't like to support
that.

~~~
zacharypinter
Of all the people in the world I can think of with the inability to think
outside the box, Rich Hickey has to be at the bottom of the list :)

~~~
mcguire
Different people have different boxes.

------
bachback
what an annoying post. no real content, but an author desperately trying to
prove he's smart.

"To say that I was treated pretty shabbily by David Nolen on this issue almost
goes without saying."

~~~
vimota
I would say he backs up his claims quite well, unlike your comment. He gave
ample evidence at his frustrations with the progress of the language - what
did you find annoying about it?

~~~
awwaiid
I see a lot of issues but not a lot of PRs. As a maintainer there is a lot of
prioritization going on, and it seems that the things that bother the blog
author are not things that bother the maintainers. I also agree with some of
the replies on the issues - garbage in garbage out for a function is ok
behavior.

One of the cool things about clojure and other languages like this is that
these things can often be fixed at runtime - so even if your fix doesn't get
accepted you can have a "my-better-fixes" lib you pull in for yourself. A
"consistent-set-opts" lib you add to your project.cls for example.

~~~
bpicolo
In what way is "garbage in garbage out" better than consistent handling and
explicit failure, especially for something so well-defined mathematically?
That's sort of a lazy argument, imo.

I shouldn't need to pull in more libs for baseline language expectations,
either. Core libraries are what languages really are, much more important than
just syntax.

~~~
dragandj
Because checks to avoid some kinds of garbage can incure significant
performance penalty. There is always a tradeoff in that sense, and I think
Clojure is pretty well balanced in that way. So, read the docs, try the
functions out to see how they work, write tests, and sanitize the data at the
appropriate place.

~~~
bpicolo
Matters so rarely that it's far better to rewrite in those cases when
necessary.

~~~
dragandj
In core libraries? If low-level functions checked the inputs for each odd
case, they'd (often unnecessarily) spend 90% of time doing those checks.

