It is fascinating to watch how both the java and javascript communities deal with the very different shortcomings of their respective programming languages.
Oh, it certainly wasn't negative. I was doing Scheme long before I did Java, and I'm doing quite a bit of Clojure now, so I'm certainly a long-time Lisp fan.
The thing is, will there be a time where people will stop dealing with a concrete specific language to stay at the `lisp` level, or will this keep going on forever ?
True, how about starting with the right language? :) I only write Clojure for both JVM and JS. Works like a charm. The only problem is when you need to work in a team and nobody does anything else but Java and JS.
My rule of thumb is, if you need to switch a primary production language more than once a decade[1], then you're picking them wrong.
Personally, I've been using Java for over a decade, and a few years ago I added Clojure to the mix and expect it to serve me for over a decade, as well. I like them both and believe they both qualify as good picks. I'm also adding Kotlin now, too, mostly because its switching cost and added risk are practically zero.
[1]: for reasons other than targeting a new platform
Speaking of which, Estonia recently implemented a "no legacy" directive [1], under which all government systems will be rewritten from scratch at least every thirteen years. They are taking the stance that a good implementation will last around a decade, not less, but also not much more.
Seconding Kotlin coming from Java. It takes maybe 2 days to learn everything in the language if you already know Java, and it fixes pretty much all the warts that bother me about Java (the big ones for me are immutable references, first-class/higher-order functions, and unnullable-by-default references. Type inference and string interpolations are handy as well). It also has a small enough runtime/stdlib (unlike Scala or Clojure, which I've also tried) to make it practical to use in environments where startup time matters, specifically Android.
Absolutely, I need to learn about Kotlik, it almost looks like Scala at the first glance.
I agree with you about languages and targeting 10+ years with it. Stability is worth more than anything to me, out of the box performance also important (this tend to be great for older languages) not to mention the std library quality. For these reasons Java is an excellent choice.
Clojure came to me as a total surprise, since I haven't studied CS and because I was surrounded with Perl/Java engineers for a long time I haven't heard about Lisp till 2011. It changed my approach to software engineering a lot. It is absolutely eye opening, mind blowing and all that jazz.
ROFL. "Right" language. There is no such thing. There's only technical tradeoffs and personal preferences. Everything else is simply faith and dogma.
But, I'm sure someday we'll all discover your one true God... Err... Programming language, like the lispers and smalltalkers, the rubyists and the haskellers, the C++ fanatics and the C faithful...
There is such a thing but the scope is usually limited to a problem, a project, a department or a company. A good example would be OCaml and Jane Street for the latter.
Right. So for the exact same problem domain, one company might use Java and another might use Haskell, purely because of history, the candidates they can find, and any number of other possible concerns.
None of this has anything to do with objective technical superiority of one solution or another, assuming you could make that case definitively. And if one could make that case, technical superiority is only one input into a much more complicated decision-making process.
Again: there is no one true language. There never was and there never will be. Our entire industry, hell, all industries, operate based on tradeoffs, whether those be business, technical, political, or other. Why would programming language selection be any different?
Well I might have given you the impression that I am a Clojure zealot. This is not true at all. I program in Ruby, Python, Java, Erlang, Go and OCaml on the top of Clojure. The only reason I prefer the later it because I can use the same minimal syntax to target 2 VMs and that is a huge win. It is also fun to show people the power of Lisp time to time, but that is it. If my manager or team asks me to write code in a random language I am going to learn it and do it, but for personal projects I tend to use Clojure. This is all.
I've programmed professionally in C, C++, Java, PHP, Python, Javascript and Clojure. I've been out of school for a number of years, have worked for a few companies and cofounded two startups.
This requires some explanation. What limitations are people working around here?
If you mean immutability, it's doesn't make sense to single out Java, since immutability was not significantly valued by any mainstream language until the 2000s (of course Haskell and various MLs existed before, but I'm talking about popular mindshare).
Exactly. Object oriented languages of this generation even embraced mutability. The idea was that by having encapsulated/hidden data, methods could validate data and make sure that objects are always in an inconsistent state, hence making mutability safe. (Of course, the same can be achieved in C with opaque pointers.)
It was only until concurrency/parallelization/multi-core became more important that immutability-by-default gained much traction.
Nowadays, Java feels like the victim of the OO hype: mutable by default, static methods being the only escape hatch from OO, only predefined value types (yes, I know, Java 9). People always say that Go is like pre-generics Java, but it at least doesn't make two of the aforementioned mistakes.
> make sure that objects are always in a consistent state
This only works for invariants that constrain the state of a single object. Realistically, invariants that constrain the state of multiple objects are a necessity, and no amount of in-object validation will help you enforce those.
Fascinating. A programmer gives automation/abstraction to other domains, but yet uses tools which rob him(her) of that in their own work. Amazing that we had tools to solve most of these problems decades ago.
How does anything here rob the programmer of automation and abstraction? This adds something, it doesn't detract. It's an annotation you can use where you want. Your observation is specious.
This is like a robber who takes your entire house and lets you stay under the porch only when it rains. Will you be grateful to them for giving you shelter?
Not really, no. If we're doing analogies, it's like you bought a house with no porch, and sometimes you accidentally get locked out and are stuck in the rain. These guys came along and built you a porch to put next to your house for when it does rain.
No, not "at the same rate". I wish people would stop linking Paul Graham posts as if they're gospel. In the very real world of running an engineering team, network effects matter, and only in 2015 is there a Lisp (and I don't say this as a "hater" or any of the other easy ways to dismiss someone, I like Lisp and my first collegiate language was Scheme and I've used GNU Guile in multiple projects) that can approach the same value prop as Java when those network effects, the ability to just raise your head and find a sufficient solution to almost any problem that isn't business critical and many problems that are, are taken into account.
That Lisp is written for the virtual machine originally written for Java.
I wish people would stop saying that Lisp is not suited for the real world when there are billion dollar businesses built using Lisps (other than Clojure).
It is 2015, please let us stop the FUD at least now :)
PS: I love Java and use it for my day job. Just a few days back I was reading about AutoValue and Immutabiltiy and wishing that Java had more power that would make these hacks unnecessary. I am not saying Lisp is the greatest, but Java definitely feels like assembly when you reach the edges and one has to wait for years to get updates.
There are billion-dollar businesses built with PHP, too. I wouldn't use that, either.
I don't like Java. I don't like Java at all. (I prefer Clojure or Scala, depending.) But I recognize why it is where it is, and Paul Graham saying Paul Graham things doesn't really change reality.
As someone who programmed Java a couple of years, because work: it is quite doable, since there are now good collection libraries that provide mutable collections (e.g. Google Guava).
Of course, you cannot get the same guarantees that e.g. Haskell provides. Moreover, unless you return concrete types, that Map<> being returned by a method could be mutable or immutable (it's your guess ;), or hopefully in the documentation).
The problem is, as you say, it is quite doable, as long as you have internal access to all of the code. There's no way to ensure that, for example, a function actually returns an immutable map, or actually doesn't modify a map passed to it.
(And no, Collections.immutableMap doesn't.)
(And then there are problems with the JVM bytecode as well - do you know you can pass 2 into a function expecting a boolean? And sometimes it'll get treated as true and sometimes as false? The JVM is "wonderful" like that.)
Hold on to that thought. I'll check back in with you in, say, 5 years. By then, we should have all endured enough unnecessary pain to lurch back in the other direction. :)
We're well into the era where scarce computing resources no longer limits us the way they used to. We're over some kind of hump with regards to that. Since computing power is more abundant, the limiting factor we're currently running into has to do with the cognitive overhead incurred by our current tools, since tools always lag behind somewhat. Our current tools are primarily built to be efficient with machine resources, but are not necessarily built to make efficient use of cognitive resources.
As cognitive resources become the bottleneck, it's sensible to invest in tools that decrease cognitive burden. So we're seeing an upswing in popularity with methods and techniques aimed at limiting the amount of unnecessary, incidental complexity generated. "Easier to use" is nice, but "less complex" is imperative right now.
Immutability is one of those techniques that can be leveraged to limit growth of complexity.
>Immutability is one of those techniques that can be leveraged to limit growth of complexity.
What you're saying is perfectly rational, right down to your emphasis on can. I don't take exception to immutability as one tool. My problem is with the notion that it is the only tool and the corollary that mutability is always wrong.
But, from an OOP perspective, modelling state changes is natural and desirable. So mutability seems a sensible default.
It's only the recognition of issues that can arise from state changes that has us wanting to lop off the entire feature. A rather strange and drastic "solution" in my view.
Mutability is a feature, of course. Lots of useful algorithms need mutable data structures (e.g., dynamic programming, unification, etc.), and have no counterparts that rely solely on immutable ones.
However, totally unconstrained mutability is definitely a shortcoming. The ability to look at a type signature and be immediately able to tell what might or might not change is a big win for maintainability, especially in big projects.
By the way, constraining mutability doesn't require a purely functional language. The ML family (Standard ML, OCaml) allows all expressions to have arbitrary side effects (when evaluated), but all values other than reference cells are immutable.
The one thing that tripped me up when I tried to use it is that you can't add other library annotations. For example, I couldn't change the json serialized field name using Gson's @SerializedName("custom_naming").
Maybe it's not what you looking for, but Immutables support binary [1] and JSON serialization [2] (Jackson or Gson) very well including field naming customization.
Hmm, this seems like a more heavyweight version of the immutable object support in Lombok [1], through the @Value and @Builder annotations. The website looks nicer than Lombok's, though :)
This kind of thing has worried me a little in the past (Lombok is a similar, but less focused project). My main concern is that this is not part of the pluggable annotations standard; it's an undocumented API provided by Sun javac. This means that it is prone to breakages, although I don't suppose it will particularly soon.
I know that Lombok had to change their code quite frequently to fit with different javac versions for a while, which means that this project becoming stale could be a problem.
Yep, that's fair. I took another look, Lombok is only unsupported because it inserts methods into pre-existing classes; I misremembered the limitation on the pluggable annotations. My bad!
BTW, Java now has pluggable type systems. One of them[1] adds true static, compile-time checks for immutability, and allows method to specify that they only receive immutable objects.
There are many others, including tainting types, linear types, physical unit types and more.
Neat. It would be interesting to see a comparison between this and FreeBuilder (as well as AutoValue, though I think the differences there are more obvious).
I'm all for tools that make immutability easier; the lack of immutable objects is one of my main sources of cognitive dissonance when programming in Go instead of Java.
These aren't immutable variables, they're immutable (i.e. persistent) data structures. You can't directly change the objects themselves; instead, "setters" create a new copy of the object.
Perhaps I haven't had enough coffee yet, but could you explain how an immutable data structure is any different than an object whose class definition is all final? If you can't change any property of the object, wouldn't that be the same as what this annotation does?
To be clear, it seems like a nice boiler-plate avoidance technique (which Java could always use more of). But you're implying that there's some other significant underlying problem this solves and I don't see it.
That still doesn't work all the way down. The objects you get out of the collection remain as mutable as when they were inserted.
Also, unmodifiableCollection doesn't make the collection you pass in immutable; it creates a new object through which you can access it as long as you don't modify it. Anybody holding a reference to the original object can still change the object.
I think you can even somewhat break the collection by changing objects inside it if doing that changes their hashcode (for sets and dictionaries, such a change may have to reinsert the item in the collection to make sure that you can still retrieve it)
Right, but this project doesn't solve that problem, either. It wraps the underlying collection in a builder to make it less easy to access, but it doesn't appear that it places any additional restrictions on the types of members or collection elements (from the generated code on their site anyway).
If you weren't using this library, you could achieve something similar with, e.g.,
I think this library really is about making immutable objects accessible, i.e., taking away the boiler plate. Hand-writing immutable (and potentially mutable) implementations on top of interfaces for a whole set of DTOs is super annoying.
if a class definition contains a Map that's declared final, nothing prevents anyone from mutating the map itself, if there's a public getter to it.
In order for a class to be immutable, all its fields need to be be immutable and final. Immutability got to be recursive.
Also, in Java, the class should be final, otherwise nothing prevents anyone from extending it and pass a mutable data structure instead of the immutable one you'd expect
Edit : added the "in java", as we could imagine another language where immutable doesn't require objects to be final.
Indeed, but there is no way to reflect that in the return type, besides the JavaDoc of course. If you try and modify it later, you'll get a runtime exception, which is a pretty crappy thing. One of the things I dislike most about the Collections API is that Mutable interfaces are not extensions of Immutable interfaces with just the addition of modifiers. I should be forced to say that I'm returning a Mutable version of a data structure in my return type.
> is that Mutable interfaces are not extensions of Immutable interfaces with just the addition of modifiers. I should be forced to say that I'm returning a Mutable version of a data structure in my return type.
It wouldnt work. If MutableList<E> extends ImmutableList<E>
then every MutableList is also an ImmutableList. This way you can be forced to return a MutableList, but not an ImmutableList.
Fair point, you're absolutely correct. Admittedly I haven't given this a TON of thought, because it seems like something that can't be added anymore. Maybe the interfaces should be siblings, but since Java doesn't have a disjoint type, you might run into problems if something says it implements both MutableList and ImmutableList. Not sure if Java's type system is strong enough to deal with this problem.
I think the Map interface should have been composed of two interfaces, ReadableMap and WriteableMap. Then APIs could return ReadableMap, which would lack the mutator methods altogether.
Then a Map would be a ReadableMap, but a ReadableMap wouldn't be a Map. If a method expects a Map, you would't be able to put a ReadableMap (or a WriteableMap) into it.
My solution so far is to avoid to return a map or to expect a map as argument.
> What's the advantage of that as opposed to the 'standard' java way of doing immutable via 'final'?
They're not really comparable.
This is code generation for builders, getters, etc. The idea is that you write less of the boilerplate that you'd write if you were rolling your own immutable POJOs.
Plus, unless I'm mis-reading the docs, it also handles certain collection types transparently. (Which obviously just throwing final on a property won't do.)
My IDE (IntelliJ) does a lot of the writing of immutable POJOs for me :). The builders are a bit different for sure, but I don't see what's wrong with having an object that is only "mutable" in the construction phase.
One of the most frequently trumpeted cases for immutability is WRT thread safety. Is it me, or does this need come up rarely in practice relative to the forcefulness of arguments to make everything immutable? I get that it depends on your project, etc. But, I'm talking overall.
There are other benefits to immutability, to be sure, but I appear to be the rare voice that finds the dictum to "make everything immutable" to be overstated. And, we seem to ignore the code bloat that comes along with it--builders and the like, as well as performance implications.
Of course we solve the challenges that mutability can introduce by lopping it off altogether, but at what cost? It's really not too far from solving problems that object misuse can introduce by getting rid of objects.
Replace all setters with builders, annotations, etc. Do not ever change an object's state. Ever! Run this Mutability Detector on your code to ensure you are not allowing something to be mutable. At all costs, kill those language features that allow object state to be changed!
At what point have we jumped the shark?
Maybe others believe in moderation as do I, and I just happen to be reading more of the forceful champions of immutability lately. But, they seem pretty loud and I have learned that edicts to never or always do something warrant great suspicion.
Some language features are just bad and should be removed entirely. Moderation is often the answer, but not always. https://xkcd.com/690/ .
> Of course we solve the challenges that mutability can introduce by lopping it off altogether, but at what cost? It's really not too far from solving problems that object misuse can introduce by getting rid of objects.
At what cost then? I can point to specific scenarios where objects are useful and languages without them (e.g. Haskell) solve the problem less elegantly. Can you say the same for mutability? Have you got a specific business problem that you've solved with and without mutability and found that the mutability made it better?
(FWIW my position is: no unmanaged mutability beyond method scope. Local mutability is fine, but anything that crosses method boundaries should be explicitly managed using e.g. State)
FWIW if you advocate managed local mutability, then you also have folks who will tell you even that is evil. They would say internal mutability is still mutability. And, of course, it's true that you do also have to manage the same concurrency issues as with any mutability.
>At what cost then?
If a language offers a construct for achieving a certain goal, and you opt to bypass that construct to create a parallel construct, then you are necessarily creating additional cost. But, it's actually worse than that because it goes beyond creation of parallel constructs: it also includes working to actively suppress the built-in language constructs.
And now we are creating immutability tools and new annotations, and running Mutability Detection, etc. as if mutability itself is a defect and stamping it out automatically improves code quality.
>Have you got a specific business problem that you've solved with and without mutability
Yes. Every bit of code I've written before the age of Mutability-is-Pure-Evil. That code worked well without the additional code bloat, object copying, and performance penalties associated with defeating natural language constructs. No annotations. No builders everywhere. Just pure, clean, intuitive code whereby other developers were required to actually understand the design before using it.
You need to set a property? Then, set it. Don't construct a completely new object with a builder, just to update the zip code. In a multithreaded environment? Sorry, you need to understand concurrency issues. No amount of dumbing down objects is going to make up for your lack of understanding there.
Otherwise, let's stop calling immutability OOP. What people really want is to pass around a struct and have procedural code wrapped in other objects perform operations on it.
And, sorry. Moderation is key. Your use of "internal mutability" is actually a nod to moderation. When people find themselves repeating that something is always evil, it should trip a mental alarm. Sure, there are cases wherein immutability might be a reasonable way to solve a problem, but definitively stating that it is always the right approach is shark-jumping.
> And, of course, it's true that you do also have to manage the same concurrency issues as with any mutability.
No, it's not. I'm talking about within a single method, which inherently means belonging to a single thread.
> But, it's actually worse than that because it goes beyond creation of parallel constructs: it also includes working to actively suppress the built-in language constructs.
Again, some language constructs are just bad. E.g. I believe Gosling has said he wishes he never put checked exceptions in Java.
> You need to set a property? Then, set it. Don't construct a completely new object with a builder, just to update the zip code. In a multithreaded environment? Sorry, you need to understand concurrency issues. No amount of dumbing down objects is going to make up for your lack of understanding there.
On the contrary, more restrictive models really are easier to understand.
>I'm talking about within a single method, which inherently means belonging to a single thread
If your method is modifying data non-atomically, then you can still have issues with data consistency when another thread attempts to access the object in the middle of your update. You're going to need to synchronize access in some manner.
>Some language constructs are just bad...checked exceptions...
Checked exceptions are not an integral part of OO design. Objects, along with the concept of properties (i.e. state) are.
>more restrictive models really are easier to understand
> If your method is modifying data non-atomically, then you can still have issues with data consistency when another thread attempts to access the object in the middle of your update.
Externally-visible data should not be mutable.
> That's simply not a universal truth.
Let's talk specifics then. My preferred model is: every piece of persistent (beyond a single method scope) mutable state is owned by an actor, and accesses to that state are performed by sending State values to that actor and receiving values back. The type system enforces that any state modification happens in the appropriate actor; since State is an ordinary value you can refactor safely without worrying that you're changing concurrency boundaries. Monadic syntax (for/yield) makes it easy to compose state changes that should happen atomically, and since the call to actually perform the modification is explicit, it's easy to see which changes are atomic and which are not.
This doesn't automatically solve the problem. If two threads invoke methods which mutate (or read) the internal state, then inconsistencies can result if not properly synchronized.
>Let's talk specifics
You originally stated that more restrictive models are easier to understand. My point is not that they never are (as in some universal truth). It's that the real answer is that it depends. And, in the context of this discussion, simply declaring that the restrictions that immutability imposes are automatically beneficial to understanding is a non-sequitur.
But, WRT to the specifics you provided, your model may indeed provide structure that can aid in understanding the code. But, I don't think it's the degree of restrictiveness that determines this, as much as that there is some model in the first place. Of course, whether it's the best choice in a given situation is a relative proposition.
Interestingly, however, you allow for mutability. And the real "magic" is happening not through immutability alone, but careful assignment of mutability operations in conjunction with other concurrency idioms that you expect to overlay atomicity.
So, essentially we agree: declaring mutability as the root of all evil is overkill. Instead, the real prescription relies upon carefully considered/managed mutability, used with the language's intended concurrency features.
One of the most frequently trumpeted cases for immutability is WRT thread safety. Is it me, or does this need come up rarely in practice relative to the forcefulness of arguments to make everything immutable? I get that it depends on your project, etc. But, I'm talking overall.
Yes, I more than once had to fix code in an natural language processing library, because multi-threaded Jersey services led to either crashes or partial annotations literally ending up in the wrong request. In the cases where I had access to the code, I rewrote the classes to be immutable (all state is created and passed between method calls), which fixed it.
In one natural language parser that we couldn't patch, I had to create one full parser instance per thread (weighing in at ~1-2GB), because the class was mutable and did not use correct locking. (So, we ended up creating a couple of parsers at startup and putting them in an object pool.)
What you're describing are just plain bad coding practices by people who didn't understand concurrency. Proper locking and synchronization are the real fixes.
Again, that's not to say that immutability never has a place.
Concurrency is hard. Immutability skates around the whole problem. A parser should be immutable, parsing state should be limited to a particular invocation of the parser.
Of course, mutable state cannot be abolished everywhere (often for efficiency reasons). In such cases, you should indeed understand the concurrency primitives.
As a C++ programmer, I make sure that things that can reasonably be const are const, because that reduces the number of places where things can go wrong.
That said, sometimes state simply has to change, and anything else is just ideology.
Having worked in a couple different large Java projects, there are certainly tradeoffs to both sides: make too many things immutable and you will not end up in a happy place GC-wise. And add the requisite "it depends on the project" to the remainder of this comment :)
But if I were to start a project, I would start with immutability by default and go from there, chiefly because when you find yourself needing to add additional threads and share data, I consider the transition from mutable to immutable would be more difficult than the inverse.
Another perspective: mutability is a feature that can be used in certain contexts (and QUITE useful there) but nothing intrinsic to the feature (in Java, at least) enforces the contexts' prerequisites (i.e. thread-safe access), and thus it is easy to end up with runtime errors when the environment of your project shifts.
Immutability, OTOH, makes fewer contextual assumptions (or perhaps more tractable ones).
Said perhaps more pithily: when mutable-by-default, your resultant architecture will frequently hinge on mutability. If you instead start with immutability, the immutability will be less of a crutch than the mutability would have been.
>nothing intrinsic to [mutability] (in Java, at least) enforces the contexts' prerequisites (i.e. thread-safe access)
I guess that's where I differ: To my mind the mutability construct is not supposed to provide this. Instead, enforcing thread-safety should involve the use of concurrency constructs.
And, that's the thing: OOP represents a stateful paradigm, in the sense that operations may be performed which mutate the state of an object. When we bring out the Hammer of Immutability to prevent potential concurrency issues, we are basically saying that we no longer want to be OO because of an unrelated requirement. Let's turn the objects into structs that can only be passed around and copied so we don't accidentally screw something up. This, rather than using the language's actual concurrency constructs to safely share data.
>it is easy to end up with runtime errors when the environment of your project shifts
I can't remember when this has happened to me and I'm genuinely having trouble seeing the case. If I have an object which suddenly must be shared across another thread, then I generally cease to access it on the initial thread without some sort of synchronization.
So, I just don't see how you'd suddenly share data across threads under any model without very careful consideration of what's being shared and why. What operations will be performed with the data as input, what will be the output, and how/when do we communicate this to other threads?
Once you've done that analysis, then you have a blueprint for how to safely share data the right way.
In my experience, Immutables really helps address some of Java's verbosity problem. Introducing it into my project cut 2,500 LOC (~10% of total code base), https://github.com/glowroot/glowroot/commit/7bdd3ff
A similar example in Scala would be a class that has only 'val' types and no 'var' types, but read through the stackoverflow post to see the complexities.
So, I work at a company that uses Java for all its server-side technology (~2 million lines today), but we don't use annotations for code-gen. Can someone explain to me why I would want to do that instead of a hypothetical IDE feature/plugin?
* DRY for cross-cutting concerns, like database transactions, logging, dependency injection, serialization, validation, web controllers/endpoints... basically, macros for Java.
* Less code means less chance for error - even if that code was generated at some point in the past, it will need to change in the future, and the future developer may do the change manually and might introduce mistakes. Using annotations can help enforce patterns.
Another advantage of using annotations to generate code at compile time, is that any (simple) errors get detected at compile time instead of runtime. So, potentially less tests you need to write.
immutability is some clear gap in java, but I'm curious on what immutable has that checker framework [1] doesn't.
Looks like immutables transform the code in even more spaghettis.
Just looking at the "get started", it looks like the author voluntarily ignores all the java conventions around variables and getters/setters.
I might be wrong on this, but I suspect this framework heavily relies on introspection, which in my experience has always led to very hard to debug and explore code.
>> what immutable has that checker framework doesn't.
Functionality overlap is not particularly clear with Checker framework, in fact Immutable users often use Checker framework also. There are other tools like AutoValue, FreeBuilder, Lombok where I can see more similarities, but with important differences.
>> Looks like immutables transform the code in even more spaghettis.
I would not agree, but you should look at real world codebases like [1] or [2] which use immutables.
>> Just looking at the "get started", it looks like the author voluntarily ignores all the java conventions around variables and getters/setters.
Immutables support getters convention and you can configure any other convention you have, see [3].
>> I might be wrong on this, but I suspect this framework heavily relies on introspection, which in my experience has always led to very hard to debug and explore code.
Immutables uses standard APIs of annotation processing. You can take a look at example generated code [4].
I tried to use annotation processors, but they were very IDE-unfriendly. According to the linked user guide it's better now. It's an interesting alternative to runtime bytecode generators or proxies.
It is also horrifying.