So my ideas for const won't work

Saturday, October 31, 2009
I'm starting to realize that C-style 'const' and reflection do not play well together.
A quick review: C's semantics for const are distinctive in that they are transitive - that is, you can declare a variable as const in such a way that it applies to the members of the object being referred to. Of course, in Java and other languages you can declare a variable as 'final', but that attribute applies to the variable only, it doesn't affect the object being referred to.
Another way to think of this is that in Java, the const-ness of an object is self-contained - all of the information about what is mutable and what is not is represented in the object's type, and cannot be changed by an external entity that happens to reference the object.
The problem with reflection is that function arguments must be converted into generic Objects in order to be handled uniformly, which means that any information outside of the object is lost. There's no way to represent the const-ness of the object reference when passing it as a generic Object. This means that either you can never pass a 'const' object to a reflected function ever, or passing an const object as an argument to a reflected function silently makes it non-const, which violates the contract. The third alternative is to make the reflection interface more complex and less efficient by wrapping each const object in a special const wrapper object.
One question that should be asked is, what makes C-style const useful?
My answer to this starts with the observation that the use-cases for being able to control object mutability fall into two large groups: Group 1 is about object permissions, controlling what parts of the code are allowed to mutate an object. Group 2 is about managing shared state, especially between threads, where being able to declare an object as immutable is a handy shortcut to the general shared state problem.
In Java, one generally deals with these problems by explicitly exposing a separate interface that offers a reduced set of methods -- removing all of the methods that could cause a mutation of the object, but leaving the methods which don't cause state chanes. This involves a lot of extra typing. It also doesn't address the Group 2 use cases, since the removal of mutating methods does not guarantee that the data won't be mutated in some other way.
The value of transitive const is that it automatically creates such an interface with a single keyword: That is, given a set of methods, it can 'filter out' the ones we don't want, not really removing them but making it an error to call them.
Of course, there are many possible such filterings of an interface, but this particular filtering - removal of non-const methods - is so common that it makes sense to support it in the language as some sort of shortcut.
One possible solution to the problem would be some way in the languge to "auto-create" an interface - given an interface, return a transformed version of that interface with the mutating methods removed. That doesn't solve the Group 2 use case, but at least it avoids the extra typing. Also, my ideas about this are completely nebulous and fuzzy.

-- Talin


Post a Comment