Phantom Types in Java, Revisted

Posted on July 5, 2010 by Tommy McGuire
Labels: software development, notation, toy problems, java, programming language
Following my previous post on Phantom Types in Java, Raoul Duke commented,
also, i'm trying to figure out how to do http://ocaml.janestreet.com/?q=node/11 sorts of readonly vs. readwrite in java. but i also need frozen vs. unfrozen (different that the readonly vs. readwrite) so i'm quickly getting myself confused and lost. oh well.
I also had a few thoughts I wanted to add.

In the first place, I realized that the "tag" values of the Vector types are unneeded. Since Java's Class class is parameterized, I could have used the class itself as the tag:
public <U extends Vector> Vec<U> convert(Class<U> u)
{
return new Vec<U>(this.x, this.y);
}
In the second, I spent some time fooling with the Java QuickCheck library, and came to the conclusion that the floating-number handling in Jeff Foster's code seems a little dodgey. Mine certainly was. And, finally, I realized that floatys were not any more fun than I had remembered.

Anyway, back to Raoul Duke's comments: Yaron Minsky's post, like my Vector, is something that is probably better done using inheritance in a language that supports it. So, I'm not going to actually answer Raoul's comment; instead I'll propose a slightly different problem. Suppose you have a data structure that you need to be mutable in some circumstances and immutable in others. For example, it might be something you want to initialize or reinitialize at one point and use at another, using the type system to indicate that the initialization should not use the current values of the structure and the use should not be able to modify the structure. (Work with me here.)

Roughly speaking, this is what I came up with:
public class PRef<V> {

public static interface ReadOnly { } // [1]
public static interface WriteOnly { }
public static interface ReadWrite extends ReadOnly, WriteOnly { }

private int i; // [2]

private PRef(int i) { this.i = i; }

public static PRef<ReadWrite> ref(int i) { return new PRef<ReadWrite>(i); }
public static PRef<ReadOnly> readOnly(int i) { return new PRef<ReadOnly>(i); }
public static PRef<WriteOnly> readWrite(int i) { return new PRef<WriteOnly>(i); }

// [3]
@SuppressWarnings("unchecked")
public static PRef<ReadOnly> readable(PRef<? extends ReadOnly> ref) { return (PRef<ReadOnly>) ref; }
@SuppressWarnings("unchecked")
public static PRef<WriteOnly> writeable(PRef<? extends WriteOnly> ref) { return (PRef<WriteOnly>) ref; }

// [4]
public static int get(PRef<? extends ReadOnly> ref) { return ref.i; }
public static void set(PRef<? extends WriteOnly> ref, int v) { ref.i = v; }

public static void main(String[] args) {

PRef<ReadWrite> rw = ref(4);
System.out.println(get(rw));

PRef<ReadOnly> ro = readable(rw);
System.out.println(get(ro));

// set(ro,10000); // [5]

PRef<WriteOnly> wo = writeable(rw);
set(wo,5);

// get(wo); // [6]

System.out.println(get(rw));
set(rw,6);
System.out.println(get(rw));
}
}
First, with [1], I used interfaces rather than classes for my marker types, because I wanted to make ReadWrite a subtype of both ReadOnly and WriteOnly. This is the only way in Java that I can think of to link the three types while obeying the Liskov substitution principle. I suspect there is a clean way of implementing this problem using traditional inheritance techniques, but I would be willing to bet that it requires tricks as complex as a phantom type.

Second, my data structure is an int [2]. Like I wrote above, bear with me. Picture something larger and more complex.

Point [3] refers to the worst part of this class, in my opinion. The two casts in the conversions to ReadOnly and WriteOnly seem unavoidable, although they should be completely safe.

The get and set methods [4] are the key to the class's use. The get method can be invoked on an object that has a subtype of ReadOnly; the set method on an object that has a subtype of WriteOnly. Because ReadWrite is a subtype of both, both methods are available if the object has that tag.

Here is another weirdness: get, set, and most of the other methods are static. I had to do that because I could not figure out a way to apply tag types to the type of the receiver object.

Anyway, the actual test comes in the main method, where the ReadWrite, ReadOnly, and WriteOnly versions behave as expected. The two commented-out lines [5] and [6] result in compile-time errors, the first trying to set a read-only object and the second trying to read a write-only object. Yay.

I am a bit perplexed by the frozen/unfrozen since I'm not sure what the combination of writable and frozen means, but I'm game:
...
public static FrozenRef ref(int i) { return new FrozenRef(i); } // [1]
...
public static void set(FrozenRef ref, int v) { ref.i = v; } // [2]
...
// [3]
public FrozenRef freeze() { return new FrozenRef(this.i); }
public static FrozenRef freeze(FrozenRef ref) { return new FrozenRef(ref.i); }
...
The majority of the code is the same as the previous class, with the addition of Frozen and Unfrozen tag types and a spare type parameter. The ref method that creates the references now returns an initially unfrozen reference [1]. The set method [2] now requires that the object be both WriteOnly (or ReadWrite) and Unfrozen.

The key to freezing the structure is to make a copy of it, in both versions of the freeze method. The copy is necessary, since the program almost certainly retains an alias, a copy of the object with unfrozen type parameters. As long as a writable reference to the object exists, it cannot be considered frozen. Once the frozen instance is created, it cannot be unfrozen, and thus cannot have the set method called on it. And the crowd goes wild.
active directory applied formal logic ashurbanipal authentication books c c++ comics conference continuations coq data structure digital humanities Dijkstra eclipse virgo electronics emacs goodreads haskell http java job Knuth ldap link linux lisp math naming nimrod notation OpenAM osgi parsing pony programming language protocols python quote R random REST ruby rust SAML scala scheme shell software development system administration theory tip toy problems unix vmware yeti
Member of The Internet Defense League
Site proudly generated by Hakyll.