7

I am confused about the notion of immutability. Consider the following structure of a simplistic calculator:

Class diagram

Here are the interfaces:

interface IOperationalInterface {
    int Sum(int a, int b);
}

interface IAuditInterface {
    int SumInvocations();
}

IOperationalInterface.Sum should compute the sum of two integers and IAuditInterface.SumInvocations should return the total number of received Sum calls.

Here is a trivial implementation:

class CalculatorImpl : IOperationalInterface, IAuditInterface {
    private int invocations = 0;

    public int Sum(int a, int b) {
        invocations++;
        return a + b;
    }

    public int SumInvocations() {
        return invocations;
    }
}

Is CalculatorImpl immutable? Obviously not, because its state changes with each invocation of the Sum method. Is the Sum operation pure? According to Wikipedia it is not, since it changes the state of a mutable object, and that side-effect is observable through the IAuditInterface. Obviously the SumInvocations operation is also not pure, since it may return different results.

In summary, CalculatorImpl is mutable and all of its methods are impure.

However, from the viewpoint of CalculatorClient, who talks to it only through the IOperationalInterface, it appears to be immutable and the Sum operation appears to be pure in the sense that it could not observe any side effects through that interface.

On the other hand, from the perspective of AuditClient, it is completely different: it is obvious that the object implementing the AuditInterface is mutable and its SumInvocations operation is impure, and this follows directly from the specification of the IAuditInterface.

So, it is possible to partition an interface/specification of a mutable class such that some parts of it will appear mutable and some not. In this case, considering only the Sum operation and leaving out the requirement that the invocations should be counted, we get something that does not have any side-effects.

Now, on implementing the CalculatorClient one can take into account the fact that the object behind the interface appears to be immutable. At least one could not tell the difference.

So my question is: does it make sense to talk about "immutability" of interfaces or is it a bad idea? How else can I communicate the fact that there will be no observable side-effects through that interface? And if it's bad, what could go wrong?

UPDATE

Thanks for your answers/comments! I see now that there is no way to say that the IOperationalInterface is pure; the conditions of purity are much too strong to apply in this case. However the question remains whether there is a weaker notion (maybe "immutability"?) which is applicable.

  • 2
    I don't want to copy/paste the whole answer, so: stackoverflow.com/a/7540563/319403 – cHao Jul 31 '14 at 9:30
  • Ok, there's no such thing as an "immutable" interface. But you still could differentiate between a "no-observable-side-effects" interface and a "regular" one? – proskor Jul 31 '14 at 9:41
  • IMO, no, and for the same reason. The "no-observable-side-effects" part can't be guaranteed or enforced while inheritance is allowed. And with an interface, inheritance is basically required. – cHao Jul 31 '14 at 9:46
  • The distinction for no /observable/ side effects is mostly for things like logging, caching, lazy eval, et cetera. Read observable as "functional"--as in, if you removed them, the program's correctness would not change. In this case, your side effects are behavioral and your function is impure. – Phoshi Jul 31 '14 at 9:48
  • 1
    @proskor: I believe that interfaces can specify what a subclass must do, and any deviation from that is an error on the subclass's part. But they can't make any promises about what a subclass will not do. If you can reframe "no observable side effects" as a specific, positive requirement (like, say, "Given two arbitrary ints a and b, any two invocations of Sum(a, b) must return the same value"), that's valid. But "this method must not have any observable side effects" is invalid, IMO. – cHao Jul 31 '14 at 10:29
5

However, from the viewpoint of CalculatorClient, who talks to it only through the IOperationalInterface, it appears to be immutable and the Sum operation appears to be pure in the sense that it could not observe any side effects through that interface.

Whatever you can observe through the interface is irrelevant to the concept of purity. If Sum logged results to a file, it would still be impure even though you wouldn't be able to observe any changes through the interface.

Purity is a very strong condition. If you say that something is pure, I'll take you at your word and assume nothing bad will happen if I run it from multiple threads, cache the results and only call it once for any given set of inputs, or that I can call it 1,000,000 times without exhausting the OS's file handles. Those assumptions can backfire when using CalculatorImpl.

There's nothing wrong with partitioning the interfaces the way you have, but you should be precise. The specification you want for IOperationalInterface is probably closer to: "The return value of these methods must depend only on their arguments, but their execution may have side effects." You can be more specific about the side effects and say that the methods must not perform any kind of I/O. But I wouldn't say that it's pure, because you're very clearly not using it that way.

  • Ok, then maybe there is a weaker notion than purity? For example, something like "the operations always return the same result for the same arguments and the order of invocations does not matter"? – proskor Jul 31 '14 at 13:51
  • @proskor Yeah, that sounds much better. There's probably no name for that concept but that's fine. You might want to specify whether the number of invocations matters though. Will something break if I decide to cache the results? – Doval Jul 31 '14 at 13:53
  • 3
    @proskor There is, it is called Determinism. You can say IOperationalInterface is Deterministic. – RMalke Jul 31 '14 at 16:25
  • @RMalke Good idea, but aren't all functions deterministic (except, maybe, random number generators and IO)? – proskor Jul 31 '14 at 16:48
  • @RMalke: Does "deterministic" have a useful meaning here? I was thinking about that word, but one could quite convincingly argue that the object itself is also part of the function's input (and, particularly since it's modified, output). – cHao Jul 31 '14 at 16:49
4

You're sort of getting hung up on definitions without worrying about why you care if a function is pure or not. A pure function gives more freedom to the caller at the expense of restrictions on the implementer. A function with side effects gives more freedom to the implementer at the expense of restrictions on the caller.

Most everyone is familiar with the restrictions on the implementer of a pure function, but many are not familiar with the freedoms it grants the caller. It frees the caller to cache results and not call the function a second time. It frees the caller to create as many copies of the object as they want, perhaps on different threads or even different systems, to speed up a computation. It frees the caller to not have to worry about keeping the object in scope, because they can always recreate an identical one later.

If you're not going to grant those types of freedoms to the caller, it does no good to label an interface immutable. You're putting restrictions on the implementer without any commensurate benefits to the caller, so you may as well lift the implementer restrictions.

  • This is what I meant by saying that on implementing the CalculatorClient one can take into account the fact that the object behind the interface appears to be immutable. For example, assuming that the operational interface is "immutable", it is easier to make the client implementation thread-safe. However if the interface is "mutable", it is more difficult or even impossible. But again, the question is whether there is such thing as an "immutable" or "pure" interface. – proskor Jul 31 '14 at 13:45
  • But you can't take it into account, because it isn't really immutable, and isn't easier to make thread safe, because invocations++ isn't guaranteed to be atomic, and you don't have the freedom to make and destroy copies at will, because you'll lose the count. Something that appears to be immutable but really isn't is worse than not being immutable at all. – Karl Bielefeldt Jul 31 '14 at 14:39
  • To explicitly answer your question, yes pure interfaces exist, at least as a gentleman's agreement if not enforced by compiler, but there's no such thing as a pure interface with an impure implementation. As soon as you create a side effect, you would be breaking the contract of the pure interface and therefore technically no longer implementing it. – Karl Bielefeldt Jul 31 '14 at 14:39
  • @proskor: The appearance of immutability doesn't do much at all for thread-safety, if the implementation actually has side effects. Thread safety requires that mutations either not occur at all, or be done in such a way that concurrency can never cause the system's state to be incorrect. (In your example, since invocations++ is not atomic, two threads using a CalculatorImpl -- even if only as an IOperationalInterface -- can cause the count to be incorrect.) – cHao Jul 31 '14 at 15:28
  • 1
    @proskor: What you seem to want would probably be better described as "consistency" or "self-consistency". "Immutable" and "pure" are strong words, not used lightly (and almost exclusively reserved for implementations). The former applies to the whole object (not just the interface), and the latter makes promises about a function's (lack of) effects on any part of the entire system. – cHao Jul 31 '14 at 16:38
1

The implementation is clearly not immutable, so let's talk about the "pureness" of the Sum function.

It can only make sense to talk about the implementation, not the interface. For instance, what if I created a new implementation of the interface with this Sum implementation?

public int Sum(int a, int b) {
    invocations++;
    if(invocations >= 1000)
    {
        return a + b + 1;
    }
    return a + b;
}

The interface hasn't changed, but the method is no longer "pure".

In a purely technical sense, even your naïve implementation isn't pure because eventually invocations will overflow and you'll get an exception when you call Sum instead of the expected result.

  • This is not only impure, this is simply incorrect. The specification for Sum was that it should compute the sum of two integers, and this implementation cleary does not comply to it. The possible overflow is a technical implementation issue, it has nothing to do with the problem: it is possible to restrict the specification or change the implementation accordingly such that overflow does not occur. – proskor Jul 31 '14 at 11:54
  • @proskor - your example was just an example, as was mine. Your comment about the contract of Sum is correct, but your comment about the overflow issue is not correct. How can you have a Sum function with a contract that says you can only call it a certain number of times? Some (n)th call will eventually throw an exception, so it's not a pure function (it won't give you the same result every time you call it). At any rate, this is all about implementation, not interface, which is my point. – Scott Whitlock Jul 31 '14 at 12:05
  • What I meant is that the count of invocations could overflow, i.e. after the (n)-th call it could be reset to 0 instead of throwing an exception. The Sum operation remains a pure function. But even that is a marginal issue. – proskor Jul 31 '14 at 12:09
  • 2
    @proskor - Ok, what if my implementation logged the Sum inputs and result to a file, and then I got some kind of out-of-space or file-not-found kind of exception? Sure, I could wrap it in a catch block. The point is that the purity of it depends entirely on the implementation. I could actually make SumInvocations() pure by always returning 42. It wouldn't be correct, but neither would your implementation if used in a multi-threaded environment. Again, it has nothing to do with the interface, only the implementation. – Scott Whitlock Jul 31 '14 at 12:17
1

I believe what you want is a variant of Design by Contract (C# interfaces are a poor man's version of them). In C# it could be achieved via custom attributes and a static analyzer extension (e.g. for Roslyn or ReSharper):

[Pure] //This is just a sample name, not to be confused with System.Diagnostics.Contracts.PureAttribute
interface IOperationalInterface
{
    int Sum(int a, int b);
}

Your custom static analyzer extension would then check if implementation(s) are side-effect free across all the interface methods. This might get complicated depending on the level of guarantee you would like to achieve (e.g. simply checking there are no mutating operators used versus walking any method invocation chains).

If you find this approach viable for your scenario, then consider checking existing contract-related tools for .NET (non of them works well though):

JetBrains: http://www.jetbrains.com/resharper/webhelp/Reference__Code_Annotation_Attributes.html Microsoft: http://research.microsoft.com/en-us/projects/contracts

  • Yes, this is what I had in mind, however I am not sure whether the notion of a "pure" or "immutable" interface is sound. There are opinions that this is not the case, but I am not convinced yet. :) – proskor Jul 31 '14 at 13:16
  • @proskor The concept is sound. Pure/immutable interface is an interface with an added contract of no side effects guarantee. – Den Jul 31 '14 at 13:20

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.