5

Let's say I have an interface A. Instances of classes implementing this interface will be passed on to an algorithm that relies on these instances being immutable. For instance, it'll be invoking A.execute(command) in a recursive manner, a number of times that depends on the given parameters. If execute() changes the instance's state, the algorithm won't work as expected.

As immutability can't be enforced (at least in Java) by the interface, I'm thinking of just writing that in the class documentation, as a contract.

Is this reasonable or am I imposing an implementation detail?

  • 3
    In my experience anything the compiler won't enforce, won't be enforced, and even if its the caller's fault, you'll still have the hassle. – whatsisname Aug 29 '17 at 2:48
  • 1
    Rereading this question, I'm confused, and deleted my answer. "It'll be invoking A.execute(command)" What is it? Is it A or the algorithm? Please provide more details or more skeleton code. And why can't A.execute() simply decide, for correctness, not to change it's state? – user949300 Aug 29 '17 at 16:15
  • Maybe the safest solution is for the algorithm to make a copy of A before starting. (Or, better, for A to make a copy of itself, before calling the algorithm, I'm still confused who is calling whom here...). – user949300 Aug 30 '17 at 17:57
  • 2
    I am sure the algorithm doesn't require that instances are immutable, but only that they don't change. – gnasher729 Aug 30 '17 at 18:42
  • 1
    That would be called a specification, and it does demonstrate how base classes can enforce a contract more easily than an interface. – Frank Hileman Aug 30 '17 at 19:15
13

Yes, this is definitely reasonable.

Just look at the Streams API, for example, many constraints are expressed as JavaDocs instead of types. The Comparator interface is another well-known example that specifies almost all of its constraints in the docs rather than in the types. Or, take List.add, which doesn't specify in its type that implementation actually need to add an element to the list, or List.sort, which doesn't specify in its type that implementations need to actually sort the list, and so on and so forth.

Java programmers know that types don't tell the whole story and that you need to consult the documentation for a full picture.

  • 2
    I read the long Comparator doc you cited, and see no mention of mutability. (And grep came up with nothing) The Streams docs discuss a mutable result, but I didn't see anything about mutable implementations. Please elaborate. Nor do your comments about List.add() and List.sort() seem relevant. If you are arguing that one can't enforce immutability by types, only comments, I pretty much agree. But specifying that an implementation of an Interface should be immutable is unreasonable. It can't even count how many times it was called, or cache recent calculations? – user949300 Aug 29 '17 at 7:49
  • 5
    @user949300 I think he means that Comparator also expresses constraints in its doc. These constraints don't necessarily relate to immutability. See the doc for Comparator#compare, it states that The implementor must ensure that sgn(compare(x, y)) == -sgn(compare(y, x)) for all x and y. (This implies that compare(x, y) must throw an exception if and only if compare(y, x) throws an exception.) – garci560 Aug 29 '17 at 8:43
  • 4
    @user949300 Your missing the point, it doesn't matter if it talks about immutability at all, its the fact that the documentation "enforces" rules and puts constraints on implementations that is important. They could say "every implementation must output hello world" and it wouldn't matter, it would still serve prove the point of this answer, if the language can't enforce it, its appropriate to "enforce" a rule through documentation. – whn Aug 30 '17 at 14:32
  • 1
    @snb: If the type system of the language you are using is too weak to express your constraints, then you need to find some other way to express your constraints. There's no way around that, except using a language with a better type system. All of the various properties mentioned here can be expressed in the type systems of Idris, Epigram, Guru, Coq, and Isabelle, for example. There are also languages like Spec♯ which integrate Contracts into the language and can even check a subset of them statically. But, if you need to express constraints, and you can't express them in the type system, … – Jörg W Mittag Aug 30 '17 at 14:32
  • 1
    … then you have to express it somewhere, and that pretty much leaves documentation (or the source code itself, but that is no solution for interfaces, which don't have source code). – Jörg W Mittag Aug 30 '17 at 14:33
4

It is certainly reasonable for interfaces to specify that implementations don't do "a thing". This pattern can be seen in C, C++, and Fortran a lot. You will often see things called a precondition(before), postcondition(after), and rarely pericondition (what happens while the function/class runs). This has been further expanded upon in languages that actually implement language constructs in the form of contracts. Often you will see pre and post conditions on a C++ template function, which defines what can and can't be used inside of it, what properties the template must have (generics in java), etc, despite there being no way to enforce it (without very hard template meta programming that is).

In your case however, I'm not sure that the entire class need be immutable, or if only your function should be immutable instead of the entire class.

In your situation, instead of providing documentation defined constraints, you might be able to define a class that will contain the data you don't want to be accessed (assuming you even know what that is) that only implements accessors (which could all be final) and have that class implement the interface (or provide abstract method) for "execute". You couldn't guarantee immutability across the whole class, but you could enforce it on a subset of values you know about. You could also use the final keyword in the same abstract class to achieve a similar effect (but it might be harder to accomplish)

In other languages (like C++) even without contracts you can force a function to be "const" which means that it doesn't modify the calling class. Java makes such things particularly difficult to accomplish.

  • A base class is more appropriate. Glad someone pointed out the best solution. – Frank Hileman Aug 30 '17 at 19:15
1

If all you need is execute not changing the state then requiring the instance to be inmutable is not reasonable. Even in the more complex cases, requiring that no method in the interface changes the state would be enough.

When you publicly document these requirements they are not implementation details anymore, they are promoted to public specifications or contracts.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.