1. What are you trying to do?
This post summarizes some arguments expressed in this conversation, as requested there.
Proposal
- Introduce interfaces corresponding to the Guava immutable collection types:
ImmList, ImmGraph, and so on, reflecting their respective contracts and extending JDK interfaces where appropriate.
- Make the existing immutable types implement these interfaces. So for example
ImmutableList<E> implements ImmList<E> which extends List<E>.
- Suggest users of Guava who build public APIs to return the
Imm* types instead of the Immutable* ones (replacing Wiki Item 4 for example).
Benefit
This decomposes interface and implementation, which is generally considered good: my API users have to know the contracts satisfied by the types that I accept and receive, but they should not depend on a specific implementation of these contracts. This way, I can (as an API implementer) swap underlying implementations without breaking compatibility; and users can pass other implementations to my API than those that I knew of when building it. These new implementations may, for example, be more efficient for some specific use cases, or may provide more services (as compatibility does not forbid extending the contract).
This argument applies to the Guava immutable types. Although they are high quality general implementations, “There are for sure cases in which an alternative implementation would work better”. Also, having interfaces may enable use cases that are extremely difficult to achieve without first convincing the Guava team to change their implementation.
Counter-arguments
- As the
Immutable* classes are high-quality, they should be adequate for most purposes, with only occasional improvements possible. In such a case, a library should define its own ImmList class. / “the benefits of alternate implementations are likely to be fairly minor, especially when balanced against the risks of people mis-implementing the interface. I expect that anyone with really specific requirements will probably build their own immutable implementation and use that, and that seems like a reasonable outcome.”
- It does not solve the main problem of allowing for future flexibility for the API designer: the API designer will presumably, following the proposed strategy, advertise its types to return
ImmutableList until she has a reason not to; when she will see such a reason, it will be too late as returning ImmList instead would break compatibility.
- It does not solve the second main reason for the proposal (in my view), which is consistency with general advertised good practices of API design and showing a good example to the rest of the world by decomposing interface and implementation and being modest enough to admit that perhaps some day someone will need another implementation; liberal enough to admit that users should be given the right tools based on the right principles and be accountable for their own decisions; and consistent enough that we do not do one thing and still advise the rest of the world to do something else in their own case (imagine that developers all start to think that it’s not their responsibility to help their users swap implementations when needed, but on the contrary that they should actively make this more difficult to prevent incorrect mocking and to discourage using other implementations than their own).
- It duplicates types and work and misses an opportunity to standardize types, if multiple libraries do this. It exactly feels like the kind of work that should be done once for the whole world by the team designing the
Immutable* classes rather than locally each time it is required.
- It is quite inelegant for the API builder to have to manually copy the whole
ImmutableList contract to my own ImmList class.
- It creates unneeded complexity for the user of the API as she has to realize that the
ImmList type from API-1 is logically the same than Guava ImmutableList and can be used (logically) interchangeably (and will wonder why two types are needed for the apparent same service).
- It disconnects logical interchangeability and compiler-known compatibility as there is no way to substitute
ImmutableList (which could be still used somewhere else in the program) for ImmList.
- It is not transparent, provided the API user likes to clarifies her types by declaring her variables as
ImmutableList rather than List; she has now sometimes to declare them as ImmList instead.
- Just use alternatives to Guava
Immutable* types, such as Eclipse collections, when there is a need.
- If one wants to keep future flexibility by possibly changing implementation later without breaking compatibility, then by that reasoning, one is systematically forbidden to use Guava
Immutable* types. Does Guava really want to promote this kind of reasoning? Guava needs to choose between When designing your API, decide between binding forever to our implementation or just get rid of our types; VS Good API design maximises, wherever possible, the possibility of future switching of implementation, even when you still do not see a specific reason for such possible change.
- People will start mocking the types.
- As admitted, people don’t usually mock
List types. (And see the points above, more philosophically.)
- The receiver must act defensibly when receiving non-sealed interfaces such as the proposal or any other classical types such as
List, whereas Guava’s Immutable types give strong guarantees about integrity and security.
- When users really want non-type-substitutability, my proposition does not prevent it. In general however, the general advice of decomposing interface and implementation should apply, and users should allow for type-substitutability.
- “the behavior being asked for is already provided by the root collection interfaces (…) Guava introducing marker interfaces for its immutable types would provide no new behavior, could satisfy no expectations (…) For those who want to make those promises softly, they can use the core interfaces and JavaDoc appropriately and leave it up to the receiver to decide how confident they are in relying on those claims.” / “I don't think that an
ImmList extension of List would add a lot of value: interfaces-as-contracts work better if the existence/absence of methods matches the intended semantics/capabilities”
- Returning a type and documenting that it is immutable is much less clear to the API user than making it known through the type itself, and ImmutableSet for example has method
asList() that is not in Set. True, it would be more elegant to have mutable and immutable interfaces from the start, but having at least the modification methods marked deprecated in the Imm* types is a better second-best option than nothing.
- Lot of time needed to do this right (“Done in an ad-hoc fashion, it's a very easy way to paint yourself into a corner.”; “We'd want to do new interfaces right, so we'd want a full understanding of the design space. That would mean a lot of investigation of Guava (e.g., should the interfaces live in common.base so that Splitter can use them?)”)
- Perhaps I am missing something, but I do not see why it is not as simple as moving the public contract of the
ImmutableSomething class to the ImmSomething interface. (I am not arguing for just doing it in the next five minutes; I obviously agree that some brainstorming with Guava and the community is required just to make sure that this idea will not create problems and to take one-off decisions such as where to put the new interfaces, but I do not view it as a huge work that requires taking meticulous case-by-case decisions for each class and method.)
- This could be done just for one class (judged easier), before deciding whether to extend to other classes, just to assess the time it takes or whether it does create difficulties.
- As I understand, the alternative is not doing it at all, so the risks of doing it perhaps imperfectly have to be compared to the certain problems resulting from not doing it, and unsubstitutability of the implementation should be considered an important design flaw.
2. What's the best code you can write to accomplish that without the new feature?
I hope that it is okay that I do not respect the template, given that it does not seem perfectly appropriate here and that I have taken efforts to summarize the arguments and counter-arguments in another way here above.
3. What would that same code look like if we added your feature?
I hope that it is okay that I do not respect the template, given that it does not seem perfectly appropriate here and that I have taken efforts to summarize the arguments and counter-arguments in another way here above.
(Optional) What would the method signatures for your feature look like?
Concrete Use Cases
I hope that it is okay that I do not respect the template, given that it does not seem perfectly appropriate here and that I have taken efforts to summarize the arguments and counter-arguments in another way here above.
Packages
com.google.common.collect, com.google.common.base, com.google.common.graph
Checklist
1. What are you trying to do?
This post summarizes some arguments expressed in this conversation, as requested there.
Proposal
ImmList,ImmGraph, and so on, reflecting their respective contracts and extending JDK interfaces where appropriate.ImmutableList<E>implementsImmList<E>which extendsList<E>.Imm*types instead of theImmutable*ones (replacing Wiki Item 4 for example).Benefit
This decomposes interface and implementation, which is generally considered good: my API users have to know the contracts satisfied by the types that I accept and receive, but they should not depend on a specific implementation of these contracts. This way, I can (as an API implementer) swap underlying implementations without breaking compatibility; and users can pass other implementations to my API than those that I knew of when building it. These new implementations may, for example, be more efficient for some specific use cases, or may provide more services (as compatibility does not forbid extending the contract).
This argument applies to the Guava immutable types. Although they are high quality general implementations, “There are for sure cases in which an alternative implementation would work better”. Also, having interfaces may enable use cases that are extremely difficult to achieve without first convincing the Guava team to change their implementation.
Counter-arguments
Immutable*classes are high-quality, they should be adequate for most purposes, with only occasional improvements possible. In such a case, a library should define its ownImmListclass. / “the benefits of alternate implementations are likely to be fairly minor, especially when balanced against the risks of people mis-implementing the interface. I expect that anyone with really specific requirements will probably build their own immutable implementation and use that, and that seems like a reasonable outcome.”ImmutableListuntil she has a reason not to; when she will see such a reason, it will be too late as returningImmListinstead would break compatibility.Immutable*classes rather than locally each time it is required.ImmutableListcontract to my ownImmListclass.ImmListtype from API-1 is logically the same than GuavaImmutableListand can be used (logically) interchangeably (and will wonder why two types are needed for the apparent same service).ImmutableList(which could be still used somewhere else in the program) forImmList.ImmutableListrather thanList; she has now sometimes to declare them asImmListinstead.Immutable*types, such as Eclipse collections, when there is a need.Immutable*types. Does Guava really want to promote this kind of reasoning? Guava needs to choose between When designing your API, decide between binding forever to our implementation or just get rid of our types; VS Good API design maximises, wherever possible, the possibility of future switching of implementation, even when you still do not see a specific reason for such possible change.Listtypes. (And see the points above, more philosophically.)List, whereas Guava’s Immutable types give strong guarantees about integrity and security.ImmListextension ofListwould add a lot of value: interfaces-as-contracts work better if the existence/absence of methods matches the intended semantics/capabilities”asList()that is not inSet. True, it would be more elegant to have mutable and immutable interfaces from the start, but having at least the modification methods marked deprecated in the Imm* types is a better second-best option than nothing.ImmutableSomethingclass to theImmSomethinginterface. (I am not arguing for just doing it in the next five minutes; I obviously agree that some brainstorming with Guava and the community is required just to make sure that this idea will not create problems and to take one-off decisions such as where to put the new interfaces, but I do not view it as a huge work that requires taking meticulous case-by-case decisions for each class and method.)2. What's the best code you can write to accomplish that without the new feature?
I hope that it is okay that I do not respect the template, given that it does not seem perfectly appropriate here and that I have taken efforts to summarize the arguments and counter-arguments in another way here above.
3. What would that same code look like if we added your feature?
I hope that it is okay that I do not respect the template, given that it does not seem perfectly appropriate here and that I have taken efforts to summarize the arguments and counter-arguments in another way here above.
(Optional) What would the method signatures for your feature look like?
Concrete Use Cases
I hope that it is okay that I do not respect the template, given that it does not seem perfectly appropriate here and that I have taken efforts to summarize the arguments and counter-arguments in another way here above.
Packages
com.google.common.collect, com.google.common.base, com.google.common.graph
Checklist
I agree to follow the code of conduct.
I have read and understood the contribution guidelines.
I have read and understood Guava's philosophy, and I strongly believe that this proposal aligns with it.
I have visited the idea graveyard, and did not see anything similar to this idea.