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.
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.
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.
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.
Critiques from Clojure users should really try tradeoff analysis, as Hickey advocates. Which means I should analyze the costs of what I advocate, not just the benefits. For clojure.set/intersection (CLJ-1682?), Alex Miller clearly mentioned the speed consideration — one all users would have to bear. Not to mention spending time on this versus other things.
Also, it was ironic to read elsewhere in the article, "Even more obnoxious, it was closed as wontfix. Apparently a single sentence in the docs is good enough for the Clojure team..." Then in the next paragraph: "I was treated pretty shabbily by..."
(One can go on. Take the headline, "Ignorance or Apathy of Underlying Principles")
Calling hardworking people (who do free work for one's own company) "obnoxious" and ignorant/apathetic seems shabby too. While demanding "fixes" from them, and putting down their "big highlights from the past year or so" (Transit, Transducers, Spec) with a backhanded "These are okay, I guess." I'd never tolerate a client/manager with that attitude about my hard work, and that's someone paying me. No wonder free software maintainers burn out.
You're right and make a very good point. But that doesn't change the fact that there are bugs from 2009 which barely have comments or even a reasonable explanation.
Me being a pretty average typer (~95 WPM) I could type 2-3 paragraphs detailing why I am against fixing anything, in no more than 6-8 minutes.
So for 7 years such bugs to never even get a proper explanation is showing the maintainers as douchebags, not victims.
(Since people sometimes misunderstand this, I'll clarify that it isn't foul language in the sense of profanity that's the issue here. It's treating each other with respect.)
Hi, I am a Clojure committer and I just closed http://dev.clojure.org/jira/browse/CLJ-1013. Even though this issue is not a bug, explaining why is not entirely trivial. I have already spent 15 minutes on it, and have not yet found the best place to update the docs so that developers wanting to consume Clojure from Java find the information they need.
If you have a chance, could you please take care of updating the docs? Thanks in advance! Let's make Clojure great together.
Hey, I don't actually know Clojure beyond basics and several intros showing the overall framework of the language, its paradigms and generic philosophy (I don't count that as knowing a language).
I'd love to help but as it is, the original article convinced me that I shouldn't. As a commercial project maintainer myself I am all too well familiar with how many things one can miss if he doesn't write everything down and constantly manage information -- that's why I have a huge private Trello board.
I am not gonna scorn fellow extremely busy devs. But I will point out that as language maintainers your responsibility is bigger than mine. You should do your best.
If I ever decide to become an active Clojure user I definitely won't be only criticizing. I'll do my best to help!
It's my opinion that I don't have to be a Clojure expert with 5 years of experience in order to be able to recognize patterns in the language implementation and the way the ecosystem is governed, and eventually deduct I wouldn't want to associate with them for the time being.
I heard on the Cognicast about them working on clojure.spec over the weekend. Elsewhere, Alex Miller offhandedly mentioned his sleep deprivation: answering questions and writing accessible documentation.
In any prolific, underfunded work, there's always things to pick at.
Please edit out the "douchebags" comment. If not for ethical reasons, then for the fact that some of them post on HN, read such words, and probably be momentarily demotivated/distracted. Not to mention HN's guidelines on civility: https://news.ycombinator.com/newsguidelines.html
It's sadly too late now. I admit of being perplexed and somewhat angry after reading the original article and then making my own quick research on it. And I was temporarily a part of the problem I'd call "others use worse language so why should I care". That was obviously a mistake.
My apologies. It definitely won't ever happen again.
You'd be maybe be right, if that was the only thing that the maintainers were doing. Speaking as a maintainer of several projects myself, it's all too easy for some issues to slip through the cracks.
I agree. I am a maintainer myself of a closed-source & commercial project. It's indeed extremely easy to miss out on a crucial piece of info.
I am not gonna teach anyone how to organize information. For the time being I am writing absolutely everything that comes to my mind (or is raised by customers and teammates) in a private Trello board. Even if I close everything in it 3 years from now, I would have some peace of mind knowing that I tried my damnest to never miss anything. And that's it's written down somewhere and that I am aware that it's waiting my input or work.
I was somewhat offensive simply because bugs 7 years old marked as "wontfix" without a tangible explanation is absolutely not OK for language maintaners. The jobs of the senior devs are usually very hairy because we're good in fixing messes and repairing things. I stand behind my opinion that you have to go back and fix your past mistakes every once in a while. You can't pretend you're 18-20 years old and only sprint ahead and be blinded by the glamour of the new and shiny features. Stuff periodically needs repairing, it's how it is.
Well at least I am not dowvnoted for bad language here. :D
Seriously though, I don't understand why is this being downvoted on the grounds of a subjective opinion. I stand behind my philosophy and I am returning periodically to fix things I've done in my past, both in my profession and real life.
It's also what happens when the implementation becomes the specification that others depend on, rather than having the implementation depend on a specification.
In the former, any deviation, including bugfixes, from the existing implementation is a breaking change. Indeed, without a specification, it's debatable whether any behavior is actually a bug or not.
it's too bad the JVM inlining budget isn't configurable or hintable, that seems like a poor design decision to have to work around. Then again, the JVM probably wasn't originally intended to be the foundation for a dozen other languages
HotSpot is a JIT, so that's a runtime option. You'd be asking anyone deploying a .jar containing some Clojure to add fiddly configuration.
Not impossible, but one of the heavily advertised virtues of Clojure is that it can be deployed alongside Java code without anyone having to be the wiser.
We general try to assume that the user has applied no special JVM options. Planning with that as a baseline is much better than requiring people supply a big soup of options.
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
You seem to be sidestepping some of the points made by the OP, particularly where certain bugs defy conventions laid down in other parts of the language. Garbage-in-garbage-out is fine so long as it's done in a consistent, well-understood way across the language (ie: throwing IllegalArgumentException). It seems to me that instead of being a strategic approach for dealing with such issues in a consistent way across the language (clearly this is not the case), your arguments are just an excuse for not fixing the damn issues (if that's what you're getting at). And that is truly bizarre.
The extreme version of this is PHP. Seemingly everything computes in that language with often very unexpected results (a least from what as my then PHP amateur status--coming from C).
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.
>Clojure is a language for people who know what they are doing by people who know what they are doing.
That can also be said for C, assembly and almost any language, even Brainfuck.
And when there's not a speed/memory tradeoff for not having the language do the right thing (like e.g. is the case for some decisions in C), this just amounts to having the programmer do "busywork" -- manually do the work that the compiler/interpreter should have been doing instead.
Blaming the programmer for such errors is thus a kind of "blaming the victim".
The approach "The language X is totally fine, it's the programmer's problem who doesn't know what they're doing" for such cases, that is, cases where:
1) the right thing/warning could be inferred automatically
2) there's no (or not significant) speed/memory tradeoff to do so
is a bad idea and those that propagate it should feel bad.
C absolutely is a language by/for people who know what they are doing. The fact that so many people are using it for so many things where modern safer languages now exist is not a failing of C or its designers...
Regarding misusing the set functions: There is no busy work. Instead of (set/intersect some-set some-list) you just need to do (set/intersect some-set (set some-list)) to explicitly opt in to the speed tradeoff of performing set operations on lists.
> is a bad idea and those that propagate it should feel bad.
I don't feel bad about it at all.
If Clojure hadn't followed the garbage-in/garbage-out strategy, it would have been too slow for production use. If they had added strong types early or Racket-style (read: slow) contracts, then we'd never have had the years of experience that led to the design of spec, which is new take on the problem space.
In the case of the set functions, the right thing _can not_ be inferred automatically without a speed tradeoff.
> 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.
This made me very curious about efficient set union. Here's a pretty straightforward set union in python:
def set_union(coll1, coll2):
result = set()
for item in coll1:
result.add(item)
for item in coll2:
result.add(item)
return result
Apparently that isn't the efficient way to do it -- what is?
Regardless, the article strongly suggests that one of the two arguments to clojure.set's union does offer fast set membership, since only the second argument is permitted to not be a set.
I don't feel that I know any more now than I did before you posted this "reply".
The third branch ([s1 s2]) adds the elements of the smaller collection one by one onto the larger collection. Conceptually, it does this by constructing a new copy of the result for every addition, since conj is not destructive. It is buggy and will return a result of whatever type the larger collection is, with data being duplicated if that type allows for it.
My question was, why does efficient set union of two collections require one of the two collections to support fast membership tests? As an answer to that question, you've posted code that doesn't involve membership tests at all and supplied no discussion of why it is or isn't efficient.
> why does efficient set union of two collections require one of the two collections to support fast membership tests
I haven't thought much about this but it seems intuitively true to me. You want to take the smaller set, and add each item to the larger set if it doesn't already exist. That last part is why you need a fast membership test.
If you're still unconvinced, try loading up two similarly structured database tables with a million rows and get the union of them (with and without indices). The indexed one will be orders of magnitude quicker.
> why does efficient set union of two collections require one of the two collections to support fast membership tests
Sorry, I misspoke / used bad shorthand. Union should have efficient set addition for the larger argument and set _intersection_ requires efficient membership tests.
> 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).
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.
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.
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.
There is a distinction between "Clojure is free" (talking about the burden on the consumer) and "Hickey et al work on Clojure for free" (talking about the rewards they receive for their effort). The latter is less clearly the case.
> The original purpose of open sourcing Go was different for Google (make the language more robust/better)
I'm not so sure about that. I like the 'baseball cap' argument more. The quote is about React, but it may be relevant for Go as well
> Facebook doesn't care if you use the Web, it only cares that you use Facebook.
> So why release an open source Web framework at all? Because Facebook is battling Google for engineers. So you've got a big fight between two companies over which company is the coolest place to work, and both of them are companies that your grandparents love. How are you going to win this fight? One way is to have the hippest Web framework.
> Basically, both Google and Facebook are desperate to find a baseball cap that they can put on backwards. Angular is Google's baseball cap. React is Facebook's.
So you're saying because I've run into these bugs in my own code, I clearly don't know what I'm doing and I should be using a language for someone of my intelligence?
I always found such way of thinking very offensive to your users (programmers in this case).
Have we all forgotten the principle "make the minimum amount of WTFs per minute" already?
Why is everybody so content with their technology and so quick to look down upon people who aren't familiar with the traps in it? Is it really so wrong for your technology to be easy and painless not only at the entry level, but all the way to middle-high level of professionalism?
No, that would be _wonderful_. Unfortunately, "easy" is only one dimension along which we must make tradeoffs. Clojure has chosen one point in the design space for input validation vs performance. You're free to have a different preference.
Thank you. The only problem here would probaby only be the lack of clear and strong statements about the Clojure philosophy. The C standard for example clearly shows the areas where there is an undefined behaviour and when you can expect the "garbage in, garbage out" behaviour. Is Clojure doing the same?
As a relatively mature man and a programmer for me the way a project is maintained is just as important as the ability of the technology itself.
This post helped me make a conscious decision to not work with Clojure. If I am gonna learn a new language and technologies I prefer the least possible amount of surprises, because when I inevitably stumble upon them I'll be shamed as "not active enough to research beforehand".
> The C standard for example clearly shows the areas where there is an undefined behaviour and when you can expect the "garbage in, garbage out" behaviour. Is Clojure doing the same?
Yes. In fact, this is partly what clojure.spec is about; which the original author calls out as being wrong to prioritize for some unknown reason.
You'll only be shamed (by me) if you go write a long blog post complaining about it. I don't represent the entire community. Otherwise, you'll be politely corrected and have the tradeoffs explained to you in IRC or Slack. My apologies if I've given you any other impression.
Thank you again. No, you didn't give me that impression. The author gave me the impression that this is the predominant answer he was given -- or he assumed so.
I have zero idea if that's the case, though. I'd be glad to work with a technology if I had a polite and constructive person such as yourself to help me out when I am really stuck.
i have recently been excited about clojure, but your behavior and responses of other clojure people in this topic has honestly put me off of it. i understand that things are difficult and cljure people apparently work very hard, but the tone and attitude being used is in stark contrast to what i have seen from communities like those of F# and racket. whether you're right or not, it doesn't seem to be a great approach. for people like me, this is a first taste of clojure.
My apologies. The community is actually quite welcoming to beginners.
For context, these arguments made by the original poster have been around for _years_ in the Clojure community and, frankly, those of us who have also been around for years are tired of hearing the same old complaints. It's particularly annoying because: the core team has made multiple announcements in past days/weeks about improvements to exactly what he's complaining about!
not trying to be dramatic. haha. i am mainly just excited about clojure but this has been a bit of a contentious debate which gives one pause when first getting their feet wet in a new ecosystem.
You're not getting what I'm trying to communicate.
Telling people that they're not doing a good job when you don't even know them only pisses people off, and eliminates any chance that they'll agree with you on principle.
I was hoping highlighting that would make you realize that all you were saying to me was that I'm not doing a good job, but you didn't seem to get it. Each time I summarized what you said before, you changed your story to make it sound less bad.
Ultimately I don't really care that much, this is the Internet and I don't have to interact with you after this. But I'd recommend not doing that to people IRL, they'll resent you.
> Telling people that they're not doing a good job when you don't even know them only pisses people off, and eliminates any chance that they'll agree with you on principle.
Funny, that's exactly what the original poster did to the Clojure core team.
> Each time I summarized
You call it summarizing, I call it mischaracterizing. I stand by the position that if the original poster thinks that clojure.set is bugged: they don't know what they are talking about. That doesn't mean they suck at their job, that means they haven't actually stopped to think about the particularities of those functions.
> the author goes and poo poos [...] spec, despite the fact that would "fix the bug" exactly as requested by throwing when you gave non-sets to the set functions.
As someone who is unfamiliar with clojure -- is there any particular reason a function which doesn't actually work with sets allows non-sets to be passed to it in the first place? Or this a deficiency in the type-checking? I mean, truly, this kind of thing seems like a solved problem...
This is a tradeoff made with dynamically-typed languages. Open up your dev console and try some JS for other examples: `[] + []` or `[] + {}` or `{} + []` or `{} + {}`.
There are several ways to solve it all with tradeoffs. Types can solve it with tradeoffs. Checking the types in the function can solve it with tradeoffs. Stating in the docs what arguments may be passed is another way to solve it.
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.
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!
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
> The bugs that will be fixed and the features that will be added are the ones Hickey cares about
Those two statements do not compute. A community driven language, or any technology that wishes to attract and retain users, really, should care about the things that its users care about. I can understand that the significance of these bugs might not be well understood by Clojure's maintainers, but that is the problem here. It is the task of technical maintainers to care about the problems of their users (speaking as the maintainer of several projects).
It's also worth answering that argument that Clojure is opinionated and therefore the maintainers don't need to fix certain things, because... opinions. This is bogus. Opinions are what lead you to build certain features, like transducers. Caring about quality is what should lead you to make sure they work in a way that is expected (based on how others parts of the technology work, what is least surprising to users, what is documented, etc.)
I believe you may have misread the first sentence? It was stated explicitly that Clojure is _not_ a community driven language and that the direction it moves is the direction that is best for Hickey (and Cognitect).
Misread indeed. The thesis still applies though - there's no point in delivering opinionated features if they don't work as expected, and in a consistent way.
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.
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?
Programmers are still humans at this point. Humans make mistakes. It is helpful to have software executing on computers prevent us from making mistakes rather than making it easier for us to make mistakes.
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.
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.
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.
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.
I had an itch in my mind that some rigor was lacking in his analysis as I was reading the blog post, and this captures it much better than I could have. My model has always been that methods should have well-defined behavior when the input satisfies preconditions, but may or may not error when input does not satisfy preconditions (i.e. the response could simply be undefined, rather than raising an error).
This taxonomy is much more comprehensive, and you've laid it out well. Thanks.
Dynamic languages don't really have any choice in the matter -- it's always going to be possible to pass an unanticipated type to a function, whose results may or may not be surprising or sensible
If the author means clojure.spec by "greenfield development" (I don't see what else he could mean)
EDN, Transit, reducers, transducers, clojure.spec
(I'm not saying any of these things are bad or anything, just pointing out that there was a lot of green field development done in the past few releases)
If you look into the tickets you'll find that they aren't bugs but feature requests. Labeling them "show stopping bugs" is even more misleading than the now prominent clojure.set complaint.
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 -- 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:
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.
I kinda despise non dogfooding projects. Like realizing that IBM rarely ever uses it's own modeling tools and principles to develop internal or commercial software (at least arount that time). And yes I liked lisp because of its underlying beauty. Scheme did one step further. It reuses itself in many ways. That clojure implementation isn't leveraging it's own quality feels unlispy but alas... it's also a huge effort to blend an immutable first lisp onto a JVM ecosystem. Reminds me of Scala a bit.. one lead dev quit over frustration about the state of the compiler. Maybe seeking 'beauty' is just an immature phase and after a while whatever works enough for you to sell ...
Well there's a difference between dogfooding and self-hosting. The Clojure team uses Clojure all the time. They just don't use all aspects of Clojure on Clojure itself.
For quite some time golang specifically set self-hosting as a non-goal. The rationale was that C beat go in many areas that were important to compilers (raw speed, I would guess, initially). It's only recently that they started self-hosting.
So just because you can self-host doesn't mean you should. The benefits have to outweigh the cons. Like any project, choose the best language for the job. That said, I have no idea whether the Clojure system would be better off using more of its own features or not.
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.
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.
> 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.
Ruby has a beautiful and consistent object / module system, a beautiful integration of functional and object oriented programming paradigms, and extreme powerful metaprogramming. All of those things are pretty wrapped up in its design philosophy which contains a whole bunch more than developer happiness.
Yes, Ruby has warts. So do all the other languages you listed. It is helpful to be able to see both the good and bad in languages and not get hung up battling them against each other.
Ruby essentially has no module system, and it only offers awkward tacked on support for small bits of functional programming. Hardly an integration of FP and OOP at all, much less a beautiful one.
> Ruby has a beautiful and consistent object / module system,
I don't program much with objects, so I don't know what to expect from them, but after programming in a language that treats modules seriously (Standard ML), I can't accept anything less than the following from anything that calls itself a “module system”:
(0) Implementor-side abstraction: Fine-grained control over which implementation details are exposed to different parts of a program.
(1) Client-side abstraction: The ability to define a module as a function of another, yet to be supplied, module.
Ruby's “module system” doesn't quite cut it.
> a beautiful integration of functional and object oriented programming paradigms
Functional programming in Ruby is possible, but a gigantic pain:
(0) Partially applied functions are mildly inconvenient.
(1) Higher-order functions that recursively pass themselves a modified version of their function argument(s) are not so mildly inconvenient.
(2) Now imagine actually debugging such beasts when you make a mistake. Unit tests don't help because they overemphasize the concrete cases, when higher-order functions are all about abstracting as much as possible.
At a more fundamental level, functional programming is value-oriented, and programming with compound values (not the same thing as compound objects) in Ruby isn't the path of least resistance, to put it mildly.
> Yes, Ruby has warts. So do all the other languages you listed.
I wasn't commenting on the languages themselves, but rather the philosophy of their designers. C++ has more than “warts” - the language as a whole is a monstrous abomination, but the philosophy of not accepting unneeded performance overheads is aesthetically pleasing IMO.
That is not the philosophy underlying ML or haskell. What you gave for lisp would be more appropriate for both SML and haskell than what you gave for ML/haskell.
> 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.
A bit like communism.
Whenever I watch one of Rich Hickey's signature talks, I'm enthused, but actually looking and using the concepts leaves a rather stale taste compared to the promise.
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.
I think the author is right in his assessment that it's a community problem: the Clojure core team does not want outsiders to participate in the future of the language, but does not clearly communicate this up front. I absolutely support the right for them to do this (I'm a happy user of the non-open C# language, after all), but they should clearly state that Clojure is defined by Cognitect, not the community.
Even one of the most prolific Clojure users with a tremendous track record of extremely well engineered and practically useful libraries, Zach Tellman, has been bitten by Rich Hickey's / the core team's NIH syndrome[0]. That's no way to treat the people who love your language.
What's the problem with [0]? I am not a Clojure expert, nor have I looked in detail at the patch, but Rich's concerns about megamorphic call sites in library code sound plausible.
While I don't know enough about Clojure politics to weigh in on general NIH trends, this specific patch actually has a strong rationale for further debate before acceptance.
> Clojure is Rich's personal project, which he is so kind as to share with the rest of us and in in its refinement Rich chooses to accept some amount of input from us as users. I'm somewhat ashamed to admit that it took me two years to realize this
You don't need an IDE for Clojure, IMO, huge IDEs like intellij/cursive are anti-clojure, Clojure culture encourages to write programs composed of small, modular components that can be tested individually, so mostly you keep things in your head, and if you haven't work with it in a while, is easy pick up again, Clojure doesn't need a "dot operator" to autocomplete to million of different API classes/methods. Rich Hickey makes a comparison here: https://youtu.be/VSdnJDO-xdg?t=2943
The thing is that most Clojure users are/were heavy Java users and they feel awkward without their IDEs and also some have a big java project that hey mix with clojure.
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).
> 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.)
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.
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).
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.
class Set<A> { public Set<A> union(Collection<A> c) { ... } }
class SortedSet<A> extends Set<A> {
@Override public SortedSet<A> union(Collection<A> c) { ... }
}
In other words, the more specific type overrides the operations of the more general type into more specialised operations.
defnly agree that you can get these problems in languages with static types, but i think you have to go out of your way vs dynamic languages, where these types of shenanigans are easier and (in my experience) prevalent.
e.g., in your example, because your returned type is Set, you presumably would not be relying on the result to be a sorted set! -- so there would be no problem. if you WERE relying on it being a sorted set, you would have to explicitly downcast -- i.e. would pointing a gun and at your foot and pulling the trigger. maybe you know it's unloaded, but the danger is clear from the type system. ;)
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...
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.
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.
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
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.
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.
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.
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.
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.
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.
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.
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.
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.
> 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.
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.
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.
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.
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), 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.
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?
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.
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.
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.
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.
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
> 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.
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.
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,
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.
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.
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.
> 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?
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.
Turing-completeness is a statement solely about a system's ability to perform computation, in the mathematical sense of the term (take inputs, do something for a while, return outputs, strictly in that order). It says nothing about other desirable properties like usability, extensibility, comprehensibility, etc.
You're getting down voted; probably because the way you worded your question. I'm giving you an up, because I, too, am very curious how something is possible only in Clojure and nothing else.
> To build it outside of clojure ecosystem, you would need to first build all the pieces of the clojure ecosystem
That's simply untrue, and even the author's conclusions contradict your point. You can already do all of these things with just javascript. And if you're arguing that javascript has rebuilt the entire clojure ecosystem, then what is the non-aesthetic case for clojure?
I like clojure a lot and I've made a few toy projects with it, but I see its primary value as helping me write better code in all languages I use.
It really depends on so many different factors. The javascript ecosystem is at least 100x the size of clojure's, but as a consequence, the average quality of what's available is much lower. If the pre-existing libraries and services will work for your use case, javascript is going to be faster, simply because most of the code you need has already been written.
Another important consideration is your familiarity with both. If you have a deep knowledge of javascript and its ecosystem, many of the architectural wins you get from clojure out of the box are fairly trivial to port over. I suspect the reverse is not as true, because a clojure expert dipping their toes into javascript doesn't have as clear a picture of what the limitations are and how to overcome them.
As we can already see in this thread, there's a tendency for people who use clojure to believe that some architectural concepts are literally impossible for all other languages, rather than merely less common or requiring some pre-configuration.
Thanks for pointing me to that article. I've been mulling over for years how to architect apps to remove the server, that layer never sat well with me. I've also found I couldn't discuss the problem with anyone because "clients can't access the database directly!" is such a knee-jerk reaction.
By removing the server-side layer I mean, for example, moving the "model" logic in MVC to the client. The database is still there, but it doesn't perform any application logic, so the server-side layer is moved to the client, not the database. The challenge then becomes authorization. Ideally even the database would be ad-hoc distributed yet would maintain authorization functions or at least the practical result of them, so there's no server-side whatsoever.
You still need to do all the same things as before. Some things you move to the client, other things you move to the dbms. There isn't some functionality that magically disappears when you no longer have, for example, a PHP/Ruby/Python/JS/whatever server mediating between your client and your database.
My proposal is that almost nothing moves to the dbms, everything moves to the client except perhaps authorization. Unfortunately I can't fully defend my line of reasoning without saying, here's a link to my fully described or developed alternative architecture, which I don't have. However that doesn't mean it's not possible, it seems to me to be achievable. Even if it would require changes to HTTP, the database (row, column, and cell-level authorization?), or the browser - the end result would presumably be worth it. I imagine it would be much easier to reason about an application that is data and data authorization on one side, and logic on the other.
> I imagine it would be much easier to reason about an application that is data and data authorization on one side, and logic on the other.
You're basically describing, for example, a Ember/Android/iOS + Rails + Postgres app. Logic lives in the client (Ember/Android/iOS). Data + Authorization lives in Rails + Postgres. And the Rails part of that is really not doing much. It's deciding if a request is allowed, then serializing or deserializing data from the database. I guess you can do that in the dbms, but you still need to do that somewhere.
The object-relational impedance mismatch disappears when the dbms is an immutable time series, which means a whole bunch of plumbing code does indeed evaporate
But if your server doesn't enforce data consistency -- that is, the business logic resides on the client -- you can't rely on the quality of your input. And if your authorization sanitizes the data to ensure consistency, then you've just built your middle tier for logic.
I am implementing it (search for Hypercrud) and I know of one group of people separately implementing it (search Posh and Catalysis) I know of no others, if anyone reading this is working on this and would like to share ideas please email me
All Dustin had to say to make the "impossible" argument real is to add the qualifier "practically", so while it is possible to build a highly dynamic, enterprise application entirely out of HTML/CSS (recall CSS3 is turning complete) [1] it is "practically impossible" to do so.
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
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.)
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.
I was looking for a cool new language to learn in the holidays and hopefully do something with it web related. Like a back-end. Nothing serious though. Elixir seems way different from the languages I am used to maybe that is good.
Erlang/OTP has a lot of unique features not available in any other environment and Elixir gives you the power of Erlang/OTP with much easier syntax. It's different not because they are trying to be cool, but because it's driven by the unique requirements that allow all those unique features.
My bad, that cool really meant something different from what I am used to. Like functional programming, homoiconicity, different paradigms for concurrency, etc. I don't really have any requirements it is just for fun :). Thanks for the info.
"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.
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.
You know CL doesn't have a set type, though, right? It supports lists because it has to use a different data type. This turns several O(1) set functions into O(n), however.
Also, are you sure vectors are supported in CL? Looking over Common Lisp the Language, the adjoin/member/intersection functions only mention lists.
Finally, it's not even clear CL won't return buggy values, either. From the documentation on CL's intersection function: "If either list has duplicate entries, the redundant entries may or may not appear in the result." So it's just as GIGO as Clojure.
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?
I clicked through to the github exchange he links to, and I don't think you can call that shabby treatment. Perhaps a bit too terse (though I don't think so), but it's just saying "the docs say data must meet requirement X, and that's good enough".
His other examples sound prima facie troubling (I don't know enough clojure to have a real opinion), but he's miss-characterizing that interaction.
Once upon a time, many years ago, I was taking a database implementation class at the same time as working as a sysadmin of the university. We started having a weird problem with our AIX 3.2.5 (...years ago...) machines: the NFS subsystem would deadlock and become a tar-baby; any process that touched the affected filesystem would block, including other users' processes, system processes, and (the important part) the automounter. As a result, the boxen were effectively unusable until rebooted. Considering that they were serving 8 x-terminals and remote users, that was a problem.
We eventually narrowed it down to students working on their DB implementation projects, and further to the use of mmap. If you mmap'ed a region larger than a physical file and wrote to the outside region, that NFS filesystem went belly-up. Yes, that's explicitly mentioned as a thing not to do in the mmap documentation. (On the other hand, it worked fine on a local AIX filesystem and in fact was how local file systems extended files; I'd spent the previous couple of years working with AIX filesystem developers.)
Anyway, after getting the professor to tell the students not to do that, I notified IBM tech support. The response I received was exactly, "the docs say data must meet requirement X, and that's good enough".
In a sudden fit of ethics (honestly, I have no idea what came over me), I completely failed to advertise the nifty denial-of-service attack widely. It would have been spectacularly amusing to embarrass my former employer on BUGTRAQ, say. Not to mention imagining the face of the tech support guys when they tried reading their "good enough" spiel to an IBM customer who had more than our handful of machines.
On the other hand, AIX 4 was a complete re-write and fixed the problem with the NFS filesystems. Yay.
Anyway, the bottom line is that using documentation to cover your ass for implementation infelicities is not a very good idea.
This is a nice example of when that's a garbage answer. But I wasn't saying Nolen gave a technically sound answer (not denying it either--it all depends on the details of the case and the consequences). I just meant that it wasn't shabby treatment--if his technical judgment is wrong here, that wouldn't make him a jerk.
The thing is that "the documentation says not to do that" is almost always a garbage answer---even when the docs say it in big flashing letters, like mmap. A good interface should never produce garbage, even when fed garbage. In those cases where it's a necessary answer (say, the interface has no way to have the information necessary---which certainly doesn't apply here), Nolan's answer was completely insufficient and demonstrates a serious lack of respect for the poor sot who spent the time to report the issue.
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.
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.
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.
I strongly disagree with garbage in, garbage out. We as programmers and engineers should strive for better tooling in all aspects. I firmly believe that reducing footguns and making our tooling help nudge us in the right direction are extremely important qualities.
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.