> Clojure doesn't have to do this, because Clojure dependencies get distributed as source-code
Clojure doesn't have to do this because it was designed to allow separate compilation as much as possible[1], and because it hardly ever changes binary representation in backwards-incompatible ways. Protocols and multimethods are indeed handled at the call-site, but in such a way that a change to the protocol/multimethods don't require re-compilation of the callsite[2]. Similarly, Kotlin, a statically typed language distributed in binary, is also designed to allow separate compilation as much as possible[3]. If a feature would break that property (e.g. require re-compilation of a callsite when an implementation changes at the call target), that feature simply isn't added to the language.
This is a design that admits that extra-linguistic features (like separate compilation) are as important as linguistic abstractions (sometimes more important).
BTW, separate compilation isn't only a concern with Java class files. Object file linking also places limits on how languages can implement abstractions yet still support separate compilation. Some languages place less emphasis on this than others.
[1]: In fact, as a Lisp, Clojure's unit of (separate) compilation isn't even the file but the top-level expression. No top-level expression should require re-compilation if anything else changes.
In my opinion choosing a different language than the host (in this instance Java) has to bring enough advantages to balance out the disadvantages, like less (idiomatic) libraries, less documentation, less tools, less developers available on the market. Not to pick on Kotlin, I'm sure there are people that love it, however I personally don't see what advantages a language like Kotlin brings over Java 8, being CoffeeScript versus Javascript all over again. With Clojure or Scala we are talking about languages going into territories that Java will never venture into, for the simple reason that Java is designed to be mainstream, which really means appealing to the common denominator. Of course, as we've seen with CoffeeScript, the market can be irrational, but it started to go away already, after a new version of Javascript plus the realization that everything brought by those small syntactic differences was bullshit.
Going back to Clojure and the need or lack thereof to recompile call-sites, given that Clojure is a dynamic language that doesn't have to concern itself with much static information and that does get distributed as source-code, I feel that this is an apples versus oranges discussion. But anyway, lets get back to protocols. So protocols do generate corresponding Java interfaces, just like Scala's traits. Except that Clojure being a dynamic language, there isn't much to do when your functions look like this to the JVM: https://github.com/clojure/clojure/blob/master/src/jvm/cloju...
There's also the issue that Clojure's standard library has been more stable. Well, that can be a virtue and in the eye of the beholder can be seen as a good design, however it has many things that need to be cleaned out. As a Clojure newbie I couldn't understand for example why I can't implement things that work with map or filter or mapcat, or things that can be counted, or compared, only to find out that its protocols and multi-methods aren't used in its collections and that there isn't a Clojure specific thing I can implement to make my own things that behave like the builtins. It's also disheartening to see that the sorted-set wants things that implement Java's Comparable. Clojure's collections have sometimes surprising behavior - like for example I might choose a Vector because it has certain properties, but if you're not careful with the operations applied you can end up with a sequence or a list and then you have to back-trace your steps (e.g. conj is cool, but the concept should have been applied to other operators as well IMHO). Transducers are freaking cool, however I feel that the protocol of communication isn't generic enough, as I can't see how it can be applied to reactive streams (e.g. Rx) when back-pressure is involved (might be wrong, haven't reasoned about it enough). As any other language or standard library, Clojure is not immune to mistakes and personally I prefer languages that fix their mistakes (which I'm sure Clojure will do).
EDIT: on "separate compilation", not sure why we're having this conversation, but generally speaking Scala provides backwards compatibility for everything that matches Java. Adding a new method to a class? Changing the implementation of a function? No problem. On the other hand certain features, like traits providing method implementations or default parameters are landmines. Along with Java 8 things might improve, as the class format is now more capable. As I said, there's a roadmap to deal with this and with targeting different platforms (e.g. Java 8 versus Javascript versus LLVM) through TASTY, but I don't know when they'll deliver.
Re collections: There are Java interfaces you can implement to make your own collections that work as built-ins. In general, nothing in the Clojure compiler or runtime is written based on concrete collection classes, only on the internal interfaces. It's perfectly feasible to write a deftype that implements Counted and Associative and whatever else and make your own things that work with all the existing standard library. There are plenty of examples of this.
Re sorted-set: Any 2-arg Clojure function that returns -1/0/1 comparable semantics will work.
Re staying "in vectors": The subtle blurring of the sequential collections between the collections and sequences is part of what makes so much of working with data in Clojure easy. However, there are certainly times when you want more control over this; fortunately in 1.7 with transducers you have the ability to control your output context (with functions like into or conj) and this is easier now than ever.
Re rx: several people have already implemented the use of transducers with Rx. I know there's a RxJs and I'm pretty sure I saw something re JavaRx although I can't put my finger on it right now.
Hey, please consider my rants to be of good faith, plus I'm a rookie :-)
On Rx, the step method is synchronous. It works for the original Rx protocol and it works for RxJS because RxJS has implemented backpressure with the suspend/resume model (e.g. in the observer you get a handle with which you can suspend or resume the data source), but otherwise the model is pretty much like the original, in that the source calls `onNext(elem)` continuously until it has nothing left, so it is pretty much compatible with transducers. RxJava started as a clone of Rx.NET, but is now migrating to the Reactive Streams protocol, which means that the observer gets a `request(n)` function and a `cancel()`.
And this changes everything because `request(n)` and `cancel()` have asynchronous behavior, very much unlike the step function in transducers. And furthermore, implementing operators that are asynchronous in nature requires juggling with `request(n)` and with buffering. For example the classic flatMap (or concatMap as called in Rx) requires to `request(1)`, then subscribe to the resulting stream and only after we're done we need to request the next element in the original stream.
Then there's the issue that the protocol can be dirty for practical reasons. For example `onComplete` can be sent without the observer doing a `request(n)`, so no back-pressure can happen for that final notification and if the logic in the last onNext is happening asynchronously, well this can mean concurrency issues even for simple operators. Although truth be told for such cases there isn't much that the transducers stuff can do.
I might be wrong, I'd love to see how people can implement this stuff on top of transducers.
> for the simple reason that Java is designed to be mainstream,
Not really. Java was designed to be practical, and it ended up being mainstream.
Most languages want to become mainstream (including Scala), most of them just fail to achieve that goal for a variety of reasons, often due to their design decisions (but not always).
> Most languages want to become mainstream (including Scala), most of them just fail to achieve that goal for a variety of reasons, often due to their design decisions
A language will often become used in one particular domain, often not the domain it was designed for. Take another JVM language Groovy -- it's used a lot for scripting throwaways used in testing, including build files for Gradle, and was extended with a meta-object protocol for Grails which has risen and fallen in sync with Rails. But then its backers retrofitted it with a static typing system and have tried to promote it for building systems, both on the JVM and Android but virtually no-one's biting.
I disagree, Java was designed to be marketable to the layman and for a very good reason ...
Software companies can scale either horizontally (e.g. hiring more people) or vertically (hiring the best). Because of the classical bell curve distribution, really good people are hard to find, therefore horizontal scalability is very much preferred (and obviously not everybody can have "the best"). Unfortunately you can't really grow a company both horizontally and vertically. The reason for it is because getting good results from mediocre people requires lots of processes, coordination/synchronization and politics of the sort that doesn't appeal to really good people and that's because really good people have a choice, because of the supply-demand equation which favors them.
In the wild you'll also notice another reason for why horizontal scalability is preferred. Corporate departments and consultancy companies are usually scaled horizontally because they get paid per developer/hour and unfortunately there are only 24 hours in a day. Hence the bigger the team, the bigger the budget. On the other hand small teams happen in startups where you often see 2-3 people doing everything. Small teams also happen in big software companies, when speaking of either privileged seniors or folks that handle real research.
Big teams are getting things done, as Alan Kay was saying, on the same model as the Egyptian pyramids, with thousands of people pushing large bricks around. Small teams on the other hand tend to handle complexity by means of better abstractions. Unfortunately better abstractions are harder to understand, as they are first of all, unfamiliar.
So back to Java ... it's actually a pattern that you tend to notice. Such languages are afraid of doing anything that wasn't proven already in the market. The marketing for such languages also relies on fear and doubt of the alternatives, which Sun took advantage of, appealing to the insecurity of the many. This is one language that refused to implement generics because they were too hard and then when they happened, the implementation chose use-site variance by means of wildcards, with optional usage, possibly the poorest choice they could have made. Generic classes are invariant of course, except for arrays. This is the language that doesn't do operator overloading, as if "BigInteger.add" is more intuitive than a "+", but on the other hand it does have a + for Strings. This is the language in which == always does referential equality, so you've got tools converting every == to .equals because that's what you need most of the time. The language which until recently didn't have anonymous functions because anonymous classes should be enough. This is the language in which everything needs to be in a class, even though the concept of "static method" makes no sense whatsoever. This is the language in which at least 30% of all libraries are doing bytecode manipulation to get around the language's deficiencies. Such languages also avoid metalinguistic abstractions like a plague, such as macros, because OMG, OMG, people aren't smart enough for that (though truth be told, you're expanding your language every time you write a function, but don't tell that to the weak). Also, AOP is a thing that has a name in Java, which is pretty funny, given that in other languages it tends to be effortless function composition.
As an environment it turned out to be great, eventually. If you want another shinning example, take a look at Go. Same story.
In other words Clojure or Scala will never reach the popularity of something like Java. Sorry, people can wish for it, but it will never happen. And that's OK.
I disagree. Java's design wasn't timid -- it was radical in its intentional reduction of expressivity compared to the most popular language around, because the much more expressive language was proven to be far too costly to maintain for the duration software has to be maintained.
But you are certainly right that Java was also meant to be familiar and unthreatening. It allowed itself to do that because it realized -- and was proven right -- that extra-linguistic features such as a GC and dynamic linking are essentially the features that contribute to productivity more than abstractions.
> Software companies can scale either horizontally (e.g. hiring more people) or vertically (hiring the best).
While this may be true, it has little to do with Java directly. For years I worked with the best algorithmeticians, physicists and mathematicians (physics simulation, real-time control). We were very much involved with the hard problems we were trying to solve with novel algorithms, and frankly couldn't be bothered with spending precious resources on elegant abstractions. Our problems was one of essential complexity -- not accidental complexity -- so we needed a simple language that can be easily shared and understood by everyone, one that was fast, and one that got the job done.
Believing that better developers opt for more powerful languages is what I call the "Blub developer paradox": the blub developer has only worked on simple, blub, problems (CRUD applications, web etc.), and hence believes his cool abstractions are the mark of the "smart developer". If he'd only look at developers working on seriously hard algorithms, he'd realize that abstractions are secondary, and it is developers above him on the essential complexity spectrum that opt for simpler languages with weaker abstractions.
That better developers choose the more powerfully-abstracting languages is completely and utterly false. The best developers I've ever known -- those who really came up with radical, game-changing solutions that truly advanced the state-of-the-art -- were all C and Java people, and I don't think it is a coincidence. Their mind is focused on the algorithm rather than on alluring, but ultimately distracting abstractions.
However, saying that only people who work on boring problems have the free mental resources to spend on nifty abstractions is as valid a generalization as the one you've made. There are many reasons to choose a particular programming language.
Personally, though, I believe that while the choice of the programming language matters, it matter so much less than design, choice of algorithms and availability of other tools. I no longer equate software with the code it's written in. A running program has many factors affecting its quality and cost, and the code used to write it is just one of them. Still, if I need to write a web app, I'd choose Clojure over Java every time. If I need to write a military command-and-control app, or a power-station control app? I'd probably go with Java.
You're attributing to me and my message things I haven't said, which is a sign of an unproductive conversation.
> For years I worked with the best algorithmeticians, physicists and mathematicians (physics simulation, real-time control)
If we are talking about experience, I've built an RTB system with soft real-time requirements handling tens of thousands of transactions per second and in which Scala was awesome because we used better abstractions for handling multi-threading, concurrency and processing of huge amounts of data in real-time.
Right now I'm working on a project for E.On that monitors and controls power plants. We are talking about real-time processing of signals, modeling of state machines that evolve according to those signals and machine learning for finding the optimal dispatch algorithm and for better fault detection. Scala has been awesome because functional programming along with its very static type system allowed us to better handle the event-based nature of the system and the business logic which is like a freaking fractal of complexity.
I'm now moving on to another project from the health-care industry that handles, you probably guessed it, monitoring of patients by analyzing signals coming from medical devices. Also in Scala, though there's lots of flexibility required in configuring the system and I might propose Clojure for some parts because read-eval.
Thinking that you're the only one that interacted with "the best algorithmeticians, physicists and mathematicians" or that your problems are more interesting than what other people are doing is snobbery. I never said that the best people choose different languages, all I said is that the best people choose better abstractions and that some languages are meant for building better abstractions. Speaking of which, for "blub CRUD applications" I would never choose something like Clojure or Scala, simply because for those types of apps PHP or Ruby have got that covered.
I don't think I'm the only one etc.. It's just so happens that all the best people I've known to work on algorithms preferred languages with minimal abstractions. I very well acknowledge that others, who are no less talented, might prefer more abstractions.
However, I am saying that the assumption that better developers invariably prefer more abstractions and more powerful languages is absolutely wrong.
Personally, I've had a terrible experience with Scala, found its abstractions distracting, its complexity hindering readability, its DSLism obstructing code sharing and staff migration among various teams, and the grapple with the tool stack a horrible waste of time -- all with little benefit and lots of harm. I know that my experience isn't universal, but it's far from unique, either.
Clojure is a completely different story, partly because its designers are not purists and not interested (at least not as a primary concern) in PL research, but even with Clojure I don't think we have enough data to conclude that its cleverness (which, thankfully, is much lower than Scala's[1]) is a net gain for large projects that require maintenance for a decade or more by large teams.
All in all, I've seen that a solid, fast runtime with a good compilation and linking story, excellent profilers and deep monitoring (all provided by the JVM) yield much bigger gains than most new languages. Given that those extra-linguistic features are free and switching a language is extremely costly, I'm always hesitant about whether it's worth it. Staying on the JVM reduces the switching costs, and good interoperation with Java -- like Clojure has -- reduces it further, so I find the risk to be worth it for some less-risky projects.
[1]: Then again, so is every other language in history.
In a static language like Scala or Haskell you usually work with a type-class, which is pretty cool because you can provide implementations for types you don't control (not implying that Scala's Ordering is perfect). Instead of Java's Comparable interface I was expecting a protocol, which are almost equivalent to type-classes, or a multi-method.
Of course, in many implementations you usually also get a version of a constructor that uses a provided comparison function. However certain things, like plain numbers or strings, have a natural ordering to them, hence the need to have the sorted-set in Clojure also work with Java's Comparable. But again, I was expecting a Clojure specific protocol.
The reason for why this happens, I believe, is because protocols weren't there from the beginning, being a feature introduced in Clojure 1.2. From what I understood, in ClojureScript protocols are "at the bottom" as they say, so there's hope :-)
Thanks! So if I read your comment correctly there is nothing inherently wrong with the Comparable interface it's just that Clojure's sorted sets and maps could have used protocols instead. I can see why that would be useful for extending existing types (as you mentioned).
OTOH it’s not often I’ve seen third-party types that I wanted to be comparable but were not. In general I think that if a type does not implement a core interface you have to consider the possibility that the designer chose not to implement it for a reason.
Protocols were added in Clojure 1.2, well after the Clojure collections or stdlib were created. In a perfect world, Clojure itself could leverage protocols more widely across the stdlib. For practical reasons, this is difficult now.
Maybe a mitigating factor is that any Clojure 2-arg function extends AFunction which implements Comparator. So any Clojure function that returns -1/0/1 will work transparently. Example, use - as a comparator:
Yes, that helps in this case, but that doesn't mean being statically compiled allows you to disregard extra-linguistic downsides.
> however it has many things that need to be cleaned out.
No! There are things that could have been better; sure. But they don't need to be cleaned out because that would break backwards compatibility. Backwards compatibility is a very, very, very important thing. So much more important than a "perfect implementation" (something that can never be achieved anyway). This is something that is very hard for PL purists to understand, but extra-linguistic features and guarantees trump 100% consistency almost every time.
> It's also disheartening to see that the sorted-set wants things that implement Java's Comparable.
That's only disheartening if you're a purist. If you care about extra-linguistic concerns, such as Java interoperation, this is a mark of great design. You see how the language designers took into account concerns outside the language itself. Every Clojure map is a Java map and vice versa. Every Clojure sequence is a Java collection and vice versa. Every Clojure sorted set is a Java sorted set and vice versa. This is great design around constraints and what Rich Hickey always talks about. The language itself is no more important than how it fits within the larger ecosystem and its constraints.
> as I can't see how it can be applied to reactive streams (e.g. Rx) when back-pressure is involved
Clojure is an imperative language first and functional second. It is even more imperative than Scala (in spite of Scala's mutability) -- at least with Scala's (recent-ish) emphasis on pure-functional concepts (a terrible idea, BTW, but that's a separate discussion). Back-pressure is therefore automatic (and implicit) with pull-based channels (that transducers can be applied to).
> I prefer languages that fix their mistakes
Because you're a purist. I prefer languages that see the big picture consider what's important in the grand scheme of things, and realize that code is important but secondary to working programs. As Rich Hickey is not a PL researcher, I am sure that those mistakes will only be fixed if they don't break compatibility or Java interoperability, which are more important for the industry than a perfectly clean language.
> not sure why we're having this conversation
Because supporting separate compilation -- if not perfectly then at least treating it as a top-priority concern -- is one of the key ways to ensure binary compatibility between runtime-library versions.
> On the other hand certain features, like traits providing method implementations or default parameters are landmines.
That's exactly my point. Separate compilation (as many other crucial extra-linguistic concerns) is just not a priority for Scala. Kotlin, OTOH, implements default arguments on the receiver end rather than at the call-site, so changing default values in the target doesn't necessitate recompiling the caller.
Kotlin announced that until the upcoming 1.0 release there will be breaking changes, but not after. Binary compatibility will not be 100% guaranteed, but it's a high priority.
Clojure doesn't have to do this because it was designed to allow separate compilation as much as possible[1], and because it hardly ever changes binary representation in backwards-incompatible ways. Protocols and multimethods are indeed handled at the call-site, but in such a way that a change to the protocol/multimethods don't require re-compilation of the callsite[2]. Similarly, Kotlin, a statically typed language distributed in binary, is also designed to allow separate compilation as much as possible[3]. If a feature would break that property (e.g. require re-compilation of a callsite when an implementation changes at the call target), that feature simply isn't added to the language.
This is a design that admits that extra-linguistic features (like separate compilation) are as important as linguistic abstractions (sometimes more important).
BTW, separate compilation isn't only a concern with Java class files. Object file linking also places limits on how languages can implement abstractions yet still support separate compilation. Some languages place less emphasis on this than others.
[1]: In fact, as a Lisp, Clojure's unit of (separate) compilation isn't even the file but the top-level expression. No top-level expression should require re-compilation if anything else changes.
[2]: The implementation of protocols: https://github.com/clojure/clojure/blob/master/src/clj/cloju...
[3]: Even Java doesn't support 100% separate compilation. There are rare cases where changes to one class requires recompilation of another.