Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Simulating ADTs in Java

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

  • To define a class (or enum) that distinguishes between the two modes, and
  • To prohibit access to the interval field when in real-time mode, and
  • To have all of this checked by the compiler.

Is this possible in Java? If not, what is the idiomatic way to achieve this kind of type-safety?

like image 829
Chris Taylor Avatar asked Jul 15 '15 11:07

Chris Taylor


2 Answers

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!

like image 193
pyon Avatar answered Sep 22 '22 02:09

pyon


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)

like image 37
xp500 Avatar answered Sep 24 '22 02:09

xp500