An application may operate in two modes - "real time" where it looks at every update to the state of the world, or "sampled" where it only looks at the state of the world every T milliseconds.
If I were writing Haskell (or any language with ADTs) I would model this as
data Mode = RealTime | Sampled Int
which can be used as follows in a type-safe way
case mode of
RealTime -> -- do realtime stuff
Sampled interval -> -- do sample stuff with 'interval'
I say that it is "type-safe" because if you are running in real-time mode, you are prevented from trying to access the interval
field (which is provided at just the time you need it if you are operating in sampled mode).
How can I model the same thing in Java in a type-safe way? That is, I want
interval
field when in real-time mode, andIs this possible in Java? If not, what is the idiomatic way to achieve this kind of type-safety?
The traditional way to emulate closed algebraic data types in a language like Java that only provides open classes (that can be inherited anytime) in a type-safe way is the Visitor pattern:
abstract class Mode {
public abstract <T> T accept(ModeVisitor<T> visitor);
}
final class RealTime extends Mode {
public RealTime() {}
public <T> T accept(ModeVisitor<T> visitor) {
return visitor.visit(this);
}
}
final class Sampled extends Mode {
private final int interval;
public Sampled(int interval) {
this.interval = interval;
}
public int getInterval() {
return this.interval;
}
public <T> T accept(ModeVisitor<T> visitor) {
return visitor.visit(this);
}
}
// The recursion principle itself
abstract class ModeVisitor<T> {
public abstract T visit(RealTime mode);
public abstract T visit(Sampled mode);
}
// Concrete uses of the recursion principle
final class ModeShow extends ModeVisitor<String> {
private ModeShow() {}
public static String show(Mode mode) {
return mode.accept(new ModeShow());
}
public String visit(RealTime mode) {
return "RealTime";
}
public String visit(Sampled mode) {
return "Sampled " + mode.getInterval();
}
}
As @user3237465 remarked, several encodings of data types are possible, which happen to coincide when the data type isn't recursive: The Church encoding is a fold: it allows you to accumulate a value by recursion over the Church-encoded datatype. The Scott encoding corresponds to actual pattern matching. In any case, visitors can be used to implement all of these encodings. Thanks for the nudge, @user3237465!
You can declare an interface Mode
public interface Mode {
void doSmth();
}
And then you can have two classes implementing that interface
public class Realtime implements Mode {
@Override
public void doSmth() {
// Do realtime stuff
}
}
public class Sampled implements Mode {
private final int interval;
public Sampled(final int interval) {
this.interval = interval;
}
@Override
public void doSmth() {
// Do sampled stuff
}
}
Then you declare a variable of type Mode
final Mode mode = ... // Initialize however you want, calling one of the constructors.
And then, since mode
is of type Mode
, you will only be able to access what's declared in the interface. So you can just call mode.doSmth()
and, depending on how you initialized mode
, it will run realtime or sampled.
If the classes share some code, then you can declare an abstract
class instead of an interface
, and have both classes extend
that class. So then they can call the common methods that are declared in the abstract
class (which should be declared with protected
visibility, so that only subclasses can see them)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With