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

I feel like the article took a surprising and unusual position - "We prefer Java to Clojure now" - but, then, instead of justifying that position, instead showed how Optional lets you write more functional Java.

It's been a long time since I've written any Java, so I wonder, how is this better than Clojure?




Typing. I like Clojure. I've made a few simple libraries for it while learning. What I don't like is making web service contracts in it. The lack of typing makes the code hard to follow once you get past the first handler. Java keeps types around.


Checkout clojure.spec - you might find it very useful. The problem with types is that they are only a static/at rest description of your data. For example, it's a String or it's a Date.

But does that String contain @ character (checking for email)? Is this Date in the future or in the past (validating a credit card form)? Types say nothing about that. I'm not saying that types have zero utility, but in the vast majority of my use cases compile-time type checking doesn't go very far.


You can always create an Email type or a CreditCartExpiry type that validate in the constructor if the string contains @ or if the date is in the future or in the past. In this way you are guaranteed that you cannot pass around an invalid email address or an invalid credit card. Obviously in Java nothing guarantees that you won't forget to handle the failed state, while in languages with exhaustive pattern matching is pretty much granted if you write idiomatic code.


Type checking is only part of it, the other part is encapsulation of both data and behavior together.

Basically if you have only a few different shapes of data, Clojure is great. But the moment that you have lots of different data types that are similar but require different behavior, Clojure starts to struggle.

Let's make a trivial example. Imagine that there are 4 different ways to identify an object in your system that are recognized industry wide, you cannot eliminate or choose not to support them. These ways are by the keys :foo, :bar, :baz, and :quux. They are not interchangeable, each is unique, and each one requires a different combination of database or API access to use correctly.

In Java you would probably create an empty interface for ObjectIdentifier with tiny-type classes at Foo, Bar, Baz, and Quux. Methods that only need an identifier of any type could accept/return ObjectIdentifier, but at the integration point you could specialize on each type to hit the correct APIs/Database Tables/Etc.

In Clojure you've got if cases, multimethods, and protocols. Frankly we've had big issues with multimethods and protocols. Without enumerating every single issue we've had, they tend to increase the WTFs per day of the system, and they easily confuse both the programmer and their tooling.

So you end up with if cases, probably related to key/values in a map somewhere. But now you've got two really big problems:

1) Methods that just need an arbitrary identifier of some type end up having to pass the whole map, or use select-keys all over the place, requiring that you modify every point that touches this map when you need to add/remove an identifier.

2) You end up with an implicit ordering between these identifiers. What do you do when your map contains {:foo "foo identifier" :bar "bar identifier"}? In Java you'd have a Foo instance or a Bar instance, never both. But in Clojure you need to pick which one to use which implicitly ranks these choices, or throw an exception or similar. Now obviously if you're deserializing json you will need to make that choice in any language, but in a static one you make that choice exactly once and you're provided a measure of safety by the compiler. In Clojure those decision points leak all over your system.

Ultimately there are ways out of this issue in Clojure, and we've actually tried several. But frankly a lot of the solutions start to feel like a half-baked implementation of plain old Java interfaces and objects.


I don't like spec. I liked the other one (by Mars I think that actually checks nested objects).

Perhaps I'm just stuck in my ways, but I like to be able to see in the code I'm looking at what attributes or actions are applicable to this thing. With Clojure I lose all of that. What is in this map? Well, I better print it out to know.

Clojure's dynamic typing, and any dynamic language to me, is only useful if you're never more that 2 stack frames away from your data's source. After that it's a lot of documentation to make sure you don't cock it up.


Well, with sufficiently good spec definitions you can know (and generate examples of) exactly what is expected to be "in this map".

Oh, spec also serves as documentation!

Maybe you'll like more now. :)




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

Search: