Hacker News new | past | comments | ask | show | jobs | submit login

" However, we had a strong aversion to using Java itself, so the real short list was Scala or Clojure."

Sadly they couldn't articulate any actual problems with Java...

I find it weird that so many developers focus on the "writability" of the language. I mean stuff that java gets criticized for, like verbosity etc. Which I don't even find so bad especially since the newer jdk versions.

But in my opinion the _readability_ of the result is the most important factor. I rather read copy/pasted code than some smart clojure onliner which nobody dares to touch or really understands.

You only write the program once, but you have to read it over and over again when maintaining it and developing new features, and java's maybe a little verbose syntax really helps here, and also the fact that there really is only one way to do a thing (unlike eg. perl).

The tooling, documentation, frameworks, libraries and runtimes are all top notch with java. All other languages seem to have problems in one (or more) of these areas.




After you've been writing it for a while, reading Lisp / Clojure at a glance is not nearly as scary as it seems at first. You get used to it, and that one-liner becomes a paragon of elegant simplicity rather than a strange collection of symbols.

It's just a different mode of scanning than what we've become accustomed to by working in mainly ALGOL-derived languages. With practice, it becomes second nature (and ALGOLs seem needlessly verbose by comparison :)


I've tried unsuccessfully to learn the zen of Lisp.

When you read a Lisp one liner, to me it seems like there are many implicit details you have to hold in your head. "Ok after this parenthesis now you have a list of maps keyed by symbols with string values. Ok go up one parenthesis and now it's transformed to a list of maps with..." Continue unwrapping for 3 levels. Get messaged by coworker. "Ok. Where was I? Fuck."

Rich Hickey said something along the lines of "If a function only deals with mutability in local variables (for performance), it's still a pure function". I prefer reading imperative functions that are still pure in that sense and spell out each transformation line by line to reading highly compact code written in powerful languages. Maybe I'm just dumb.


Why dumb? Perhaps you only need longer time to adapt to the new status... until the new way to structure the code becomes your default way to present functions.


Writing is the key, I wrote in 10 programming languages and lisp still looked alien to me. It was only once I was forced to write out examples in a programming languages class that it started making sense. You really have to try writing in something novel like a lisp dialect to assess whether or not it makes sense!


'Couldn't articulate' vs 'didn't articulate' are two different things.

The tooling, documentation, frameworks, libraries, and runtimes are all there if you want to use them in Clojure, too. So that's not a point in Java's favor, just the JVM's favor.

It is quite possible to write reasonably readable, terse, correct Java programs. I rarely ever see them in the wild though; what I've mostly seen are klugey Spring or JavaEE based monstrosities that are horribly inefficient, and prone to hard to debug concurrency issues.

The average results seen with the language -is- a consideration; while it's hardly scientific (the teams choosing Java tend to be from all walks, and disproportionately from large enterprise teams where no one gets any real say in the matter, that's just the common denominator that management has allowed; the teams choosing, say, Clojure, tend to be smaller and made of good developers who are intellectually curious, and have found themselves in places they can make that decision, so obviously the average for the latter will likely be better), it's still a consideration. If you've written in a wide range of languages, you've -seen- this in yourself; what the language makes easy vs what it makes hard influences how you do things.


Clojure allows me to express thoughts that I wouldn't know how to express in for, while, if, variables and objects.

And on readability, what do you prefer?

  List<Integer> even = new List()
  for(int i = 0; i < 100; i++){
    if((i%2)==0){
      even.add(i);
    }
  }
  return even;
vs.

  (filter even? (range 0 100))


That's not a fair comparison. What about:

IntStream.range(0, 100).filter(i -> i % 2 == 0).collect(Collectors.toList());

Disclaimer: I'm not writing Java nowadays, so this is my quick prototype from a quick google-session, it probably won't compile :D

Edit: looks like I'm second, my google-session took too long :-)

Maybe bonus points for C#: Enumerable.Range(0, 100).Where(i => i % 2 == 0);


True... although that is to me even less readable than the first Java version. The clojure version still wins by a large distance in my book because of its elegance.


That's not bad, but in the clojure version, I have to know/learn three functions: filter, even? and range

In the Java version, I need to know/learn IntStream.range, IntStream.filter, IntStream.collect, the even lambda, and Collectors.toList

Of course, this is comparing one-liners, so doesn't necessarily translate to larger programs. Just pointing out that I don't find this one particular example to be in Javas favour.


> IntStream.range(0, 100).filter(i -> i % 2 == 0).collect(Collectors.toList());

This has been added to java to catchup with functional programming languages.


I know those are functional concepts, but it feels more like trying to catch up with C# (LINQ) to me.


That's better, but Clojure still has less noise, and in my experience that adds up significantly in the long run.


Less noise, true. But the grand-grand-parents' point is that some of that noise provides context, which (arguably) improves readability.


The examples don't support that assertion though.


But what is even?

I am sure functional programming has its natural appealing in areas like recursion, but this example doesn't seem too convincing for winning readability, I can always write this in python

filter(even, range(0, 101)))

where even is a function I wrote.

Please give an example for "Clojure allows me to express thoughts that I wouldn't know how to express in for, while, if, variables and objects." if you can.


Your Python example is functional programming! You're doing exactly the same thing as in Clojure, just not in a Lisp syntax.

Therefore, in your question, you could substitute Clojure with functional programming, and the examples in which FP allows you to express thoughts you wouldn't know how to express in for, while and so on -- are plentiful.

I may not be answering your question, but there's a reason for that: the functional programming aspect isn't Clojure's main selling point. It's the other ideas. Software transactional memory, immutability, its emphasis on state and identity, the protocol system, transients, concurrency, transducers, the list goes on.


You are referring to higher-order function I assume. I don't think that is a property of FPL.

I am definitely not dismissing FPL, I am just arguging about the actual examples given were not strong enough to dismiss Java being hard to read.


The big difference is that Clojure defaults to immutable data structures. While you can chain functions together in Java and Python, you still have to manually keep track of all the references.

The main benefit of modern functional languages is in their use of revisioned data structures. This has same benefits as garbage collection.

Without GC you have to manually track where you allocate and deallocate things, and this is highly error prone. With GC the runtime does this for you, and you can focus on other things.

Same thing applies with mutable and immutable data. With mutable data structures all the changes are done in place, and you have to keep track of everywhere it's used to update it predictably. With immutable data structures you're "copying" the data any time you make a change, and the runtime keeps track of the references for you.


There's nothing preventing you from writing analogous data structures in any language.

No one wants to admit that their language choice is because of the cultural norms in their language community, but if you can hide some complexity behind a language construct, you can hide the same complexity behind a function call. It's just a question of how much of the work required to do that has already been done by the community.

You're just not going to win the "my language can do x more concisely than a function called x in your language!" argument. You're better off with "this is easy and my language and you'd have to write a function to do it in yours."


There's nothing preventing you from writing analogous data structures in any language.

The problem is the language culture and what is idiomatic. I write Python in my day job and while I could certainly write immutable data structures in Python and code in a way that treats data as immutable, it would be going against what is idiomatic in the language, which means that it won't play well with libraries and that my coworkers can still trample over my data without my knowledge. That is, the language gives me no guarantees whatsoever that my immutability is actually respected and that other code somewhere else that I did not write (either application code written by a coworker or library code written by a stranger) respects the immutability.

I find this argument similar to asking why bother writing in any high level language when you can implement the same things in assmembly. Hell, why don't we all just program turing machines directly?

Languages carry a lot more with them than just the features - they carry idioms and culture and community trends. All of these (and even small syntax differences) influence how we think about problems and how we create and structure solutions. They also influence how my code will interact with other peoples (and theirs with mine).


Exactly. But there are also cultures within languages. Just because your language has a clique doesn't mean you have to hang out with them.


True, but how far away you can get or how well you can play with libraries or other code can be limited.


You're right, _you_ can write immutable data structures in any language. But for example, immutable.js and/or mori do practically nothing to fix the mutability problems with javascript, since immutability is not the default.


Higher order functions are _the_ defining property of FPL. Even though a lot of FPL people might want you to believe it's types.

Lambda calculus (the theoretical foundation for all functional languages) was initially untyped.


Arguably, first class higher order function (and immutability) is defining characteristics of functional programming.

Although, I don't think the functional/ imperative is necessary a good axis for categorizing languages. There are potentially more differences between Clojure/Ocaml than Python/Clojure, for example.


They are no longer exclusive to FPLs but higher order functions are definitely a property of FPLs


I'd say the argument for FP in general is not that it allows for the expression of thoughts that are impossible to express with for, while, if, etc., but rather that functional programs are still able to be reasoned about at the point of complexity where imperative programs become completely intractable.

Not an FP expert so I don't know if this is entirely accurate or how it plays out in reality.


even? is a predicate that test if the parameter is an even number, as the name suggests. ? is part of the function name, not a syntax symbol (there's not much syntax in a Lisp, after all). By convention, function name ends with ? are predicates (or Boolean functions) in Clojure.

Any competent Clojure programmer would likely have known most of the Clojure core functions by heart: map, reduce, filter, etc. There are maybe about a hundred of them?

Unlike imperative programming languages, where you use three primitives (assignment, condition and loop) to construct everything, in Clojure, you use these core functions to construct your program. In a sense, the process of learning Clojure is the process of learning the use of core functions. These functions are simply at a higher level of abstraction than the three primitives, hence some people say that Clojure is more expressive, because it's closer to human thoughts than Von Neumann machine specificity.


> Any competent Clojure programmer would likely have known most of the Clojure core functions by heart

I really like Clojure, but I've always found the relatively huge set of core functions to be a major barrier to entry. I always have this nagging feeling of "there's probably a core function I'm missing to make doing this or that easier", and end up spending a lot of time poring over documentation instead of just programming. Maybe that's just how it is, and the answer is to push through it until you know the functions better, but there's something to be said for shipping a language with fewer, simpler, more primitive operations.


It's definitely a taste thing. I found going from the batteries-included Python to the very limited strict C89 ansi-pedantic-standard rather annoying, but at the same time I got a lot of pleasure building a game from scratch in SDL instead of using a full-fledged engine that would have replaced a lot of my code with one-liners or even automagically managed bits in the background. I think a more overkill approach is better economically than being conservative, and in Lisp especially including a ton of common macros like 'or out of the box saves every other programmer from writing their own. I could go the rest of my life without seeing yet another custom <insert data structure here> in C or C++ which has a good implementation natively in almost every other language. I wish JavaScript had more builtin as I'm often in the position that things I take for granted as being there in Python, Java, or Clojure require me to roll my own in JS and that can easily take hundreds of lines of my own code or dependencies depending on what it is. (e.g. anything involving large integers.) I'm greatly thankful for regex support in my daily languages. I remember in a high school programming contest, one guy didn't know Java had regexes (or even what regexes were) while I knew them from using them in PHP to validate form inputs... Needless to say, he never got a fully working parser in the time he had for the problem.

You may find https://github.com/jonase/kibit helpful for some things. Otherwise I'd recommend just dedicating time to exploring the builtins so that you can resist the urge to wander while you should be programming -- there was a very interesting series a few years back some blogger made that covered all of Python's builtin modules, doing it yourself (or maybe there's one now for Clojure) is worthwhile. I also like to visit things like http://www.clojure-toolbox.com/ from time to time to see if there's anything new and exciting in areas of my interest.


From a cognitive point of view, learning a large number of idioms is more manageable for human beings than having to reinvent the mental constructs from primitives every time. This human capacity of dealing with idioms is what called "chunking" in psychology (https://en.wikipedia.org/wiki/Chunking_%28psychology%29)

Human has unlimited memory capacity and fast memory access, whereas human logical processing power is fairly limited and error prone. That's why any real human language has a large vocabulary.


"there's probably a core function I'm missing to make doing this or that easier"

Would you prefer if there wasn't such a function to make it easier? You lose nothing if there isn't (or you don't know about it) and gain if there is. Why is this a barrier to entry?


I would prefer if there were very few un-namespaced functions which are limited to those I'm likely to use on any given line of code, and an intuitive namespace hierarchy for everything else. Basically, I think the core functions should be similar to reserved syntax is in other languages: a limited set of things that form the structure of most code, with more specific stuff imported from elsewhere. Note I'm not saying that the clojure way is "wrong", just that this[0] is a daunting set of information to be expected to "know by heart".

[0]: https://clojure.github.io/clojure/clojure.core-api.html


Ah, I see. I can't disagree with that :)


We were talking about java here. ^^ So python is offering a lot more already with it somewhat first class functions. What would you guess even? does, btw?

But sure, let's make things a bit harder. As below, let's produce a sequence of the form [true false false true true true false false false false ...] of exact length 1000 without computing any more than exactly 1000 elements.

  (take 1000 (mapcat (fn [i] (repeat i (odd? i))) (range)))
Of course the really interesting stuff is hard to do in one liners. But I think one of the more mind bending things is probably core.async. Go style coroutines and channels implemented as a library on top of the language. Which allows for concurrent programming inside clojurescript which is compiled to JS and thus inherently single-threaded.

http://swannodette.github.io/2013/08/02/100000-processes/


> (take 1000 (mapcat (fn [i] (repeat i (odd? i))) (range)))

And this example is suppoed to support Clojure's readability?


You do it in a different language that isn't haskel or ml and show me the result, then we can judge.

You'd btw probably write it as.

  (->> (range)
       (map #(repeat % (odd? %)))
       (apply concat)
       (take 1000)
If you want it more readable.


You missed a closing paren :)


It shows that Lisp programmers really don't care about balancing parenthesis, because editors do that for them.


Yeah, I really love the new parinfer stuff :D


whops ^^°


My point was "even" and "filter" can be built by someone, just to be fair about the whole readability. If "even" is already built into the language, then that's a given.

Readability and language understanding are not separable. I for one have a hard time actually understand what the code does above (although I can guess it). But I am not sure how ^ translate into [true false false true true true false false false false ...] sequence because, I thought we were calculating oddity of 1000 numbers.

[1 2 3 4 5 6 7 8 9 10 ...] ==> [T F T T F T F T T T ]

Perhaps the lack of understanding Clojure syntax to comprehend what the code is doing.


Exactly the point. In many other languages, every programmer can write their own "even" and "filter" with their own idiosyncrasy. However, in Clojure, every one uses the same set of high level functions in the standard library, because they are sufficiently rich to express most of the programming concepts, without having to roll your own.

In Clojure, readability is obtained through standardization. Every Clojure programmer instantly knows what the functions in the standard library do. Because there's not much syntax in a Lisp, code is very easy to read once one knows the functions.


> In many other languages, every programmer can write their own "even" and "filter" with their own idiosyncrasy.

"filter" is incredibly useful, and most modern languages (including Java 8) have it built in.

"even?" is sometimes useful, and languages are kind of hit-and-miss about it, but its usually small enough to inline, and the biggest cost of not having it built-in is probably things like people using modulus rather than first-bit testing in Java which, IIRC, produces somewhat slower code for that check.


It's part of the std lib. But yeah you're basically on the same page as me. Java doesn't have the ability to be extended in the ways that clojure or python can.

Python also allows for functional programming to a certain degree, but clojure is a lot more powerful when it comes to language extensions.

Threads as a library (for js environments) for clojure. https://github.com/clojure/core.async

Logic programming for clojure. https://github.com/clojure/core.logic

Declarative UI building, with dataflow semantics. http://reagent-project.github.io

Additionally immutability makes your life a lot easier. Parallel computing in clojure is a no brainer, you just change your map to pmap, and that's it.


Thank you. I will look at the immutability part. I remember taking Racket in school a few years ago and that was a major thing about FPL like Racket / Lisp.

I have always wanted to look at building something again in one of the popular FPLs.


> As below, let's produce a sequence of the form [true false false true true true false false false false ...] of exact length 1000 without computing any more than exactly 1000 elements.

So, again, python:

  [i % 2 == 0 for i in range(1000)]
(If you insist on Java, its fairly straightforward, though not a one-liner AFAIK, using Java 8 generators.)


You misread the question. I didn't mean (map even? (range 1000)), but a sequence of length 1000 where the elements are 1 * true, 2 * false, 3 * true, 4 * false and so on.

To compare it with what your code produces.

[true false false true true true false false false false ...]

[true false true false true false true false true false ...]

Edited for more clarity.


Right, sorry, in python that's something like:

  from itertools import chain, repeat

  chain.from_iterable([repeat(i % 2 != 0, i) for i in range(1,1000)])
In Java 8, I think the solution with streams would be fairly straightforward, something like:

  IntStream.range(1,1001).flatMap(i -> Stream<Boolean>.generate( () -> (x & 1) != 0 ).limit(i))


Doesn't that python code give you a list of 500,500 elements, rather than a list of 1000 elements as originally specified?


In both cases yes, and in both cases because I made the same stupid mistake when both languages have good means to do infinite streams and "take" equivalents. (And that's worse in the Java case, since I used those in the inner function but not the outer one.)

So, I think I need to take a break from comment-box programming for a while; its clearly not working out for me today.


Great Clojure feature:

    user=> (source even?)
    (defn even?
      "Returns true if n is even, throws an exception if n is not an integer"
      {:added "1.0"
       :static true}
       [n] (if (integer? n)
            (zero? (bit-and (clojure.lang.RT/uncheckedLongCast n) 1))
            (throw (IllegalArgumentException. (str "Argument must be an integer: " n)))))


Even as someone who prefers a functional style, that quote is pretty unfortunate. Any Turing complete language is going to be able to express the same ideas (with varying degrees of gracefulness, resource usage, ease of debugging, etc).


Of course, dynamically typed languages will win on one liners against Java but in the long run, the absence of types can really bring an entire code base (and project) down, which is why Clojure is hardly used at all and why Java is everywhere.

Besides, Clojure doesn't even win in the one line department against modern JVM languages (e.g. Kotlin).


> which is why Clojure is hardly used at all and why Java is everywhere.

Surely that has nothing to do with Java being 20 years old, and Clojure 1.0 being 6 years old?

> Clojure doesn't even win in the one line department against modern JVM languages (e.g. Kotlin).

Example, please.


> Surely that has nothing to do with Java being 20 years old, and Clojure 1.0 being 6 years old?

Lisp has been around for 50+ years, hasn't really helped its popularity much. But I'm willing to bet with you that in 14 years from now, Clojure will have even fewer users than it has today.

> > Clojure doesn't even win in the one line department against modern JVM languages (e.g. Kotlin).

> Example, please.

Here it is in Kotlin:

    (0 .. 20).filter { it % 2 == 0 }


> Lisp has been around for 50+ years, hasn't really helped its popularity much. But I'm willing to bet with you that in 14 years from now, Clojure will have even fewer users than it has today.

Computer science has too short a history to make any sound arguments from history. However, if you insists on arguing from history, let's look at the long term trend. Would you agree that the trend of programming language is progressing to higher and higher level of abstraction and further and further away from the machine details? From machine code, to assembly language, to C, to Java, and now FP languages, this trend of mainstream programming language evolution is very clear. In this sense, if you take Lisp as a pioneer of function programming, it was simply ahead of its time.

If you are implying that Lisp syntax is somehow an impediment to its popularity. That may well be. It's a personal preference issue. Some people like more syntax in their language, some people like less. If you are in the later camp, Lisp is surely very attractive. For example, in your kotlin one-liner, there are just too much syntax to understand "()", "..", ".", "{}", etc. Life is too short, I just don't want to learn all these idiosyncrasy invented by someone just for the sake of being different. I wished I had knew Lisp sooner, certainly not having programmed for 20 years.

I am sure you will lose that bet.


Even if your assumption about the trend being to higher levels of abstraction (which is highly questionable) is correct, this doesn't give any credence to the prediction that it will make Lisp finally popular. Yes, Lisp is a 4GL but even if everyone is rushing toward 4GL, there are plenty to choose from and it's not clear that they will flock toward Lisp.

Even if CS is still pretty recent (50+ years), there is still something to be said about a language that never managed to become mainstream in that period. Whether Lisp was ahead of its time is irrelevant. The question is: will there ever be a time where Lisp will be ripe for its time?

I think that window closed a while ago. It's not about the parentheses or its FP side, it's just that the world is moving toward statically typed languages with sophisticated type inference, and it's a league that Lisp simply doesn't belong to.


> it's just that the world is moving toward statically typed languages with sophisticated type inference

The billion lines of Javascript, Python, Ruby and R would like to differ.

Each of the first three has probably more users than all languages (ML family, haskell, scala) with an advanced type system combined.

I love dependent typing, I truly do. But the right tool for the job, and a formally verified coq program is certainly suited for a rocket, but not for the high iteration and velocity environments most people program in.


> Would you agree that the trend of programming language is progressing to higher and higher level of abstraction and further and further away from the machine details?

That's...less than clear. The extreme high level of abstraction in PLs is consistently increasing, or at least non-decreasing, over time, but the mean/median used in actual programming may not be.


With some minor assumptions, my assertion would be correct. Yours needs a lot of strong assumptions to be mathematically feasible.

Basically, you are saying, when the max of a sequence is increasing, the mean/median stays or even decreases. That would require that the frequencies on the lower end to increase much faster than that on the higher end, which is certainly not the case. For example, the number of C programmers is growing faster than the number of Java programmer today? No one would think that's correct.


I'd say clojure won that one liner.


There will have fewer users according to Google Trend:

https://www.google.com/trends/explore#q=java&cmpt=q&tz=Etc%2...

And Clojure, not many is interested in it:

https://www.google.com/trends/explore#q=clojure&cmpt=q&tz=Et...

Actually I'm on the Clojure side. News comer will always pick a language which is simpler, more expressive, newer. Java is no longer such a language comparing to others.


I still find the clojure version (ever so slightly) simpler. Mostly because this has magic implicit it variable. In real code, this of course wouldn't matter, though.


One hand picked example from someone who prefers closure, which only works because there is a keyword "even".


It's a function in clojure.core, not a keyword. Sorry I didn't have to write a class for that.


Ok you provide a example in java, and I'll bet you a beer that I can write it more concise.


In my experience, the readability of Java is not that great, perhaps due to verbosity. Especially when you want to create a DSL with some type-safety guarantees.

Wrapping a head around a factory that is configured by fluent-like-api, with interfaces scattered around 10 different files usually took me longer than understanding idiomatic clojure.

When I was working in clojure, only thing I was really missing was the type-checking. But when I work in java, i miss the REPL much more, than i missed type-checking in clojure :)


> But when I work in java, i miss the REPL much more,

Both IDEA and Eclipse have had a REPL for a decade, not sure what exactly you were missing(?).


I missed mostly three things:

1. simple data definition. Clojure has EDN, python has nice syntax as well, and javascript has json.

2. pretty print

3. succinct function definition/execution, and standard array of functional tooling (map, filter, reduce, etc)

Maybe the 3rd thing did get better with Java 8?


The small amount of development I've done with Clojure convinced me that you just can't ignore Java - you're going to end up spending time reading Java docs, and debugging Java stacktraces. If you really dislike Java I don't think choosing Clojure is a great option, either (I don't know about Scala).


(Metabase dev here)

> you're going to end up spending time reading Java docs

That's actually a benefit IMHO. If no one has created a nice Clojure library or wrapper for something you want to do it's at least possible to use the massive number of existing Java libraries directly from Clojure.


"Sadly they couldn't articulate any actual problems with Java..."

The first thing they said about Go applies to Java: "Go felt promising but too low level for us to express our query language productively."

You certainly can write that sort of code in Java, as evidenced by the fact that many people have and continue to. But you can get a very big win on that sort of code in a lot of other languages, especially if you're resource-constrained.


How is Java low level. Java is a high level language. What is "low level" about Java? Do you mean statically typed makes it low level? No it doesn't. It makes it safer though. Low level means direct access to hardware and having to deal with network protocols manually and shit like that. None of which you almost ever do or would want to do with Java considering it runs on a cross platform runtime mostly isolated from the actual OS of the host machine.


It means that when it comes to writing code to deal with a query language, which involves having an AST and manipulating it every which way, Java gets creamed by many other languages. Both the Lisp series of languages (which Clojure is in) and the ML series of languages have wildly better stories for dealing with AST manipulation, as in, better enough than Java that it's worth learning those languages just for that, if that's the task you have.

Whether or not Java is a "bad" choice is a definition issue, that it is handily beaten by many other languages is pretty concrete.

I'm not saying this as a Clojure partisan, or a Java-hater, or anything like that. I'm saying it as one who has used enough of the relevant languages to know it's not even close.

For something more concrete, I'm more on the ML side, so work through https://en.wikibooks.org/wiki/Write_Yourself_a_Scheme_in_48_... and you'll see what I mean, even if you just do the first couple of chapters. For all that Haskell may be weird in some ways, consider what it means for a tutorial of a language to be talking about parsing in the second section. You'd never get that with Java.


Java has a series of control mechanisms that originate from idiomatic assembly. for, while, if, variables.

In clojure for example you rarely use these low level constructs.

What is simpler to read? Which one is more high-/low-level

  List<Integer> even = new List()
  for(int i = 0; i < 100; i++){
    if((i%2)==0){
      even.add(i);
    }
  }
  return even;
or

  (filter even? (range 0 100))


The GP qualified that this is better with newer JDK versions. For instance, in Java 8 you can do:

    IntStream.range(0, 100).filter(i -> i % 2 == 0);


I find i -> i % 2 == 0), still less readable than even?.

But let's step up the game a bit. Let's produce a sequence of the form [true false false true true true false false false false ...] of exact length 1000.

  (take 1000 (mapcat (fn [i] (repeat i (odd? i))) (range)))


Using the Java 8 'stream' API:

   IntStream.iterate(0,  i -> i + 1)
	.mapToObj( i -> Collections.nCopies(i, i % 2 == 0) )
	.flatMap(Collection::stream)
	.limit(1000)
Generate an infinite series of incrementing numbers, starting from 0, map each to a stream of boolean indicating true/false, flatten that stream of streams, then limit the output.


Nicely done :D! You're the first one to get it right.

Yeah, I'd still argue that the clojure stream api is more readable though ;).

  (->> (range)
       (map #(repeat % (odd? %)))
       (apply concat)
       (take 1000)
The thing is that these additions slowly creep into java, which is great. But you still have to wait for the mercy of the language designers.

Clojure is so flexible that it is possible to bring these things in as libraries, resulting in a very lightweight and stable core, and a very rapidly evolving ecosystem.

Also the persistent data-structures of Clojure make life so much easier. It's basically a language build around persistent maps and arrays.


> Also the persistent data-structures of Clojure make life so much easier. It's basically a language build around persistent maps and arrays.

When programming in Java (which I did for 14 years), I always thought about the little machines I was making and how they interacted. In Clojure, I'm thinking about what shape the data should be and what stack of stencils and folds (as a visual metaphor) I need to get it into that shape.

That is to say, Java is object-oriented, Clojure is data-oriented. So take data-orientation and add easy-mode concurrency and you have something wonderful.


To close standard libs gap, let's assume I have static functions for odd() and take() as well as Java8 stream APIs statically imported. After this, it looks pretty compact and readable:

    take(10, i -> range(0, i)
                    .mapToObj(j -> odd(i))
                    .collect(joining(" ")))
    .collect(joining(" "));
For reference, odd and take will look like this:

    private static String odd(int i) {
        return Boolean.toString(i % 2 != 0);
    }

    private static <T> Stream<T> take(int n, Function<Integer, T> f) {
        return Stream.iterate(1, i -> ++i).map(f).limit(n);
    }


To paraphrase Hickey, "I find German less readable than English; that doesn't mean it's unreadable."


How about this then?

    IntStream.range(0, 100).filter(::even);


Not quite what was asked. The intended outcome is a sequence of length 1000 where the elements are 1 * true, 2 * false, 3 * true, 4 * false and so on.

To compare it with what your code produces.

[true false false true true true false false false false ...]

[true false true false true false true false true false ...]


How much assembly have you done? "for"/"while"/etc are coming out of structured programming.


Yes, but that in turn was inspired by the patterns and idioms in assembly that didn't fuck up your codebase.

Simple conditional forward jumps, if, simple conditional backward jumps, while, and simple conditional backward jumps depending on a variable, for.


Imperative != low level.


Because of the way our current hardware is build, it is actually Imperative <=> low level (ignoring forth for a bit).

Because impressive constructs have a somewhat straightforward compilation path to assembly. Even when GC if involved.


I think he meant in the sense of low-level because of the language primitives offered versus what is possible with clojure (which lets you define your own essentially).

Edit: I see others have offered more thorough answer while I was reading sorry for the redundancy ;p


I find the readability is where Java's verbosity really hurts it. E.g. if you bring up the horribly verbose getters/setters in Java many people will respond that you can autogenerate them - which is true as far as it goes. But you can't "autoread" them, and if you have a thousand getters/setters of which ten provide different functionality to the standard field access, that makes the code very hard to read.


I don't find much validity to this argument that constantly comes up. Out of the box, you have two options and neither are bad... (1) Make your variable package/public/protected scope and refactor one day if you have to. (2) Use getters/setters, possibly with shorter names (if your fancy framework allows that) and more terse formatting.

Is this so bad?

  int size(){ return size; }
  void size(int newSize){ size = newSize; }
The setter shouldn't be so common anyhow because immutability is better.

Besides, if you have a thousand getter/setters then your problem might not be Java.


Most coding standards don't like that, and if you can't autoformat your code then you have bigger problems. I do agree that some of the problems are cultural rather than inherent to the language, but even if we do that,

    public class Potato {
      private final int size;
      public Potato(int size) { this.size = size; }
      int size() { return size; }
    }
makes poor reading in comparison to

    class Potato(val size: Int)
(I didn't mean a thousand getters/setters in a single class, but across the codebase as a whole)


> I find the readability is where Java's verbosity really hurts it.

I'd agree, but on the JVM there are plenty of statically-typed languages with clean interop that permit more concise expressions than Java, so I don't see conciseness alone as a reason to go to a dynamic JVM language. (That said, Java is getting better at this over time, though its still, to me, a pretty big negative for Java.)


For me, readability is directly tied to good abstractions (and meta/programming facilities for making good abstractions) and a certain degree of conciseness.

If I have to read 5-10 lines of intermediate code to figure out what's going on, it's less readable than if there's a short expression.

Some languages seem to be driven by fear that if you provide programmers powerful facilities for abstraction that rise to the level of augmenting the language that developers will no longer be able to figure out what's going on without reading a lot of code, so they solve this problem by forcing developers to write (and therefore read) a lot of code.

Java seems to be one of those languages.


Yes, Java is a mature language.

But any perusal of rosettacode.org is fairly indicative that other languages, while less mature, have adopted some good ideas.

For example, here's Quicksort in both Java and Elixir:

    public static <E extends Comparable<? super E>> List<E> quickSort(List<E> arr) {
    if (!arr.isEmpty()) {
        E pivot = arr.get(0); //This pivot can change to get faster results
     
     
        List<E> less = new LinkedList<E>();
        List<E> pivotList = new LinkedList<E>();
        List<E> more = new LinkedList<E>();
     
        // Partition
        for (E i: arr) {
            if (i.compareTo(pivot) < 0)
                less.add(i);
            else if (i.compareTo(pivot) > 0)
                more.add(i);
            else
                pivotList.add(i);
        }
     
        // Recursively sort sublists
        less = quickSort(less);
        more = quickSort(more);
     
        // Concatenate results
        less.addAll(pivotList);
        less.addAll(more);
        return less;
     }
    return arr;
     
    }
now Elixir:

    defmodule QuickSort do
      def qsort([]) do
        []
      end
      def qsort([pivot | rest]) do
        { left, right } = Enum.partition(rest, fn(x) -> x < pivot end)
        qsort(left) ++ [pivot] ++ qsort(right)
      end
    end
The advantage here is NOT just an "economy of typing."


The readability argument is overused and maybe misunderstood to me.

An experienced programmer is supposed to know the language well as well as the API and frameworks.

From my experience Java is quite verbose and the standard frameworks are among the most complex. It is typical for a clojure/scala/python program to require 1/2 or 1/3 the lines of codes than Java. And they require much less configuration files and annotations.

How much time a developper spend to read his operating system code? How much time a developper spend to read the API implementation code of his language or even the framework ? No time because the code is great, work as expected and doesn't require to be read all the time.

That's what a more expressive language provide. At greater set of standard language construct, API and frameworks that allow you to express the same in a much more concise way. This is much easier to read because the fundations are rock solid and the meaning of a function doesn't evolve over time, it is also nearly bug free. You reduce the accidental complexity.

When you choose a less expressive language like Java, even with the most advenced frameworks you have much more unreliable custom code. You are in fact increasing the readability issue, not reducing it.

Sure there the fact that many more developper master Java and the associated frameworks than say Scala or Clojure. But this also act as a filter.

You can do the same thing with a team of 5 experienced clojure developpers than with 20-30 java dev. A part come from the language expressivity itself. A part is that it is much more likely to get bad developpers in Java that will mess your code and make you loose time. It is easy to find Java dev, a bit less to find great Java developpers. Another part is that the bigger the team, the more is lost in communication, meetings and politics than actually doing things.

So that your choice ! Do you prefer 5 clojure dev, maybe paid 20-30% more or 30 java dev with 5 managers (paid 20-30% more) competiting to get budget and projects ?


This may be due to Android being limited by Java 6, but I find Java code to be so verbose that it hurts readability. Unnecessary try/catch/ignore just to close a resource, tons of factories, classes with static methods because you can't add the functionality to string, etc.

What weirds me out is that they didn't even consider Kotlin an option, since it is essentially a language so that you can code Java, but without the javaness.


You might call it 'unnecessary' but if that exception's going to be thrown somebody's going to catch it, and you don't want it to be the android runtime.


Well, not everyone thinks Java is easy to read. I've doing my job with JavaScript and CoffeeScript for more than two years and I still hates JavaScript, let alone Java. And CoffeeScript is way better than it, even it's simply just some syntax sugars. I'm learning Clojure and it's even more expressive than CoffeeScript and that's why I like reading Clojure.


Ouch, got downvoted in under a minute. Guess it's not cool to say anything positive about java! :) I still would have liked it if the authors of the article had said something about why they had a "strong aversion" for java.


I'm not a down voter, but there have been volumes written about the pros and cons of Java. Every possible corner of every possible argument has been explored ad nauseum. I'm grateful they didn't waste my time rehashing the endless arguments. As far as I'm concerned, Blub beats both of these languages.


Forced object orientation?


Clojure wins on readability as well as writability. The easiest way it does that is by drastically reducing the amount of code you have to write for whatever random thing you'd otherwise do in Java. It opens the door even further for additional savings compared to other languages in a similar place like Python or Ruby by providing macros.

Freedom from the Kingdom of Nouns alone gives you a big win in readability. Naming things is hard, and when the thing you care about isn't the Class but the Method then bothering to name a wrapper Class is additional overhead, an extra "what is this in my code?" that you need to make room for in your brain -- additionally it's common practice in Java world to put that Class in its own file all by itself, often in a totally different namespace, and often physically far away from where it's actually used when looking at the files on the filesystem, which further increases the cognitive load of knowing where your code lives. IDEs in the land of Java are helpful only because they are necessary because without them the cognitive load is just too much for any reasonably complicated project.

Freedom from mandatory static typing of the sort Java has saves you from pointless class wrappers, pointless POJOs that must have names and live in their own files and namespaces, you are free to just use maps of keywords to values. Free to work with the data directly rather than an ad-hoc aliasing scheme that might decide to hide things you need or surprise you with nulls. When you want protocol or interface adherence, it is there for you, but it is not mandatory for everything.

Maintaining Clojure is easier because there's less of it. A new hire can be pointed to your code of n files and told to spend a week reading and playing in the REPL, versus pointed to n2 files and told to spend a month reading and inserting breakpoints here and there to understand all the chaotic flows across what and where. You have a legitimate shot at picking 10 random Clojure files out of the project and understanding a bit about the data flows in them in relationship to the project as a whole, but in Java you're going to need a lot more context, especially if you get unlucky and pick out 10 MyCustomException classes. What is there in Clojure can be very dense, but it can be apparent in code review when you went too far, and then it's easy to refactor and you don't need any auto-refactor tools. You just substitute expressions, add comments, and name only what needs to be named, you can do it all without constantly needing to restart your program because (unless you're using JRebel) you added a function here or changed a parameter there.

Java is good for risk-averse corporations -- a single average Dev on an average size team can't do very much, many corporate programmers average around 1000-2000 lines of code per year* even with their IDEs helping them, and because of that they can cheaply be replaced or moved to another part of the project.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: