> You can in effect write Java in Clojure, but the language seems to be discouraging you. Things start to look ugly and hard to parse

Clojure is really a functional language at heart, so when you need to go down to an imperative model for performance I agree that's when things start to feel not quite right, and you begin to fight the language a little, the lack of standard imperative loops become an obvious annoyance. But Clojure's stance is that it's hosted for a reason, and just write those imperative pieces in Java which is designed from the ground up for imperative programming, Clojure doesn't try to pretend like it can do imperative better than Java.

I'd say on the performance scale, Clojure tries to be a fast functional dynamic language. A lot of the dynamic behavior have a runtime cost, so there's a limit here in terms of raw performance, where the best it can hope to achieve is be as fast as statically typed Java.

I think as a fast functional dynamic language it succeeds, and this is where most of the effort has gone in terms of optimization.

> When you know you're not just getting some seq, and you know you're going to get a vector - then you should use vector specific functionality. But I sort of don't get the impression the core library is compartmentalized this way

The official cheatsheet is a good resource for this: https://clojure.org/api/cheatsheet you can look in it under Collections -> Vector

> For instance I don't think there are a lot of function that take a vec on input, so in the vector case .. uhh what is the vector specific way to access the first element quickly? (not being snarky, genuinely asking)

Like the cheatsheet shows, there's a few different vector functions for getting elements:

([1 2 3] 0) ;> 1
(get [1 2 3] 0) ;> 1

There's not a function dedicated to getting the first element like there is for sequences though. So you'd just get the element at index zero.

For manipulating data in vectors, there's mapv and filterv and reduce, and you can leverage all of the transducer functions as well.

Basically the trick for performance with vectors is to use transient when creating them, use reduce to iterate over them (or a function based on reduce like mapv, filterv and all transducers), and use their index for getting elements from them.

> Tangentially, you might be a good person to ask: Do you happen to know why Java arrays aren't first class with their own syntax sugar like [],{},#{} etc ?

Clojure is very opinionated towards functional programming and immutability. Arrays are an imperative construct with mutation and specific memory address lookups as their basic premise. Clojure is also opinionated towards data driven/value driven modeling. Arrays aren't very good for that, they are homogeneous and they don't have value semantics.

Basically from the point of view of Clojure, arrays are a specialized tool that you should only need to use in special cases, because of some special non-functional requirement. And because they don't follow the standard Clojure data expectations: immutable and heterogeneous with value semantics, Clojure wants it that when an array is used, it is very explicit and obvious, so it uses different functions for them, that also happen to be better suited for arrays.

> I often want to use them, but I feel I'm working against the grain of the language and doing something wrong. But I don't really appreciate the tradeoff involved

I mean, you shouldn't use Clojure if you don't favour the functional programming style. One of the tradeoff is slightly worse performance, because computers are imperative machines. People who use Clojure are happy with it, because they believe they get better modularity, reuse, safety and productivity from functional programming, and lose very little efficiency and performance that it is a good trade off, where users won't notice the difference, but the programmer gains a lot of benefits.

Clojure cares about making sure that it delivers a fast and efficient implementation of a dynamic functional programming language. That's why it provides a good implementation of persistent collections, which are non-trivial to write, that's why it batches lazy-sequences, that's why it uses the JVM JIT runtime which does a great job at dealing with lots of small temporary objects and optimizing away extra indirections. That's why it goes out of its way to allow type hints for faster dispatch, of providing records over maps, array-maps for small maps, transients for local mutation over immutable collections, transducers and reducing fast iteration for data manipulation, primitive handling in loop/recur, etc.

I think you'd describe Clojure's performance target as: sufficiently performant. The claim is that it can give you dynamic and immutable data semantics that is sufficiently efficient to be used for practical applications, with paths towards selective optimizations where needed, like allowing full mutability in deftypes, and such.

The one thing it almost never does though is give you unmanaged mutability. It's pretty resilient to that, that's why you won't find anything faster than a volatile! (except for arrays). It doesn't want you to start having shared memory bugs in concurrent contexts. Though libraries exist for those.

Finally, I wouldn't say it discourages the use of arrays, when arrays is what you need it gives you the tools to do so. But when arrays is what you want because you have performance OCD, but don't actually need that level of performance, it would rather you used functional, immutable, heterogeneous with value semantics data-structures and functions instead, and will nudge you towards those.

Hope that answered your questions.