LoginSignup
0
0

More than 3 years have passed since last update.

JavaでExpression Problemを解決する方法

Posted at

Expression Problemとは

静的型付き言語で、再コンパイルなしで、新しいデータ型を追加したり、新しい関数を追加できるようにするにはどうしたらよいか

という問題である。
何も考えずにクラスを設計すると、
1. データ構造は追加しやすいが操作が追加しにくい
2. 操作は追加しやすいがデータ構造は追加しにくい
のどちらかになりやすい。
データ構造も操作も追加すくするにはどうしたらいいか、というのがExpression Problemの問題意識である。

Javaでの解決方法

HaskellやOCamlやScalaなどで高度な言語機能を用いた解法が既に有名(?)だが、Javaの言語機能でも同様にExpression Problemが解決できることを示す。

  1. サブタイピング+戻り値の共変性を使う方法
  2. サブタイピング+Genericsを使う方法

上記2種類の方法が存在する。この記事では2の方法を紹介する。

例題

まずは単純なインタプリタを例にして考える。コード簡略化のためLombokを使用しているので注意。

import lombok.AllArgsConstructor;
import lombok.Data;

interface Exp {
    int eval();
}

@Data
@AllArgsConstructor
class Add<E extends Exp> implements Exp {
    private E e1;
    private E e2;

    @Override
    public int eval() {
        return e1.eval() + e2.eval();
    }
}

@Data
@AllArgsConstructor
class Lit implements Exp {
    private int x;

    @Override
    public int eval() {
        return x;
    }
}

AddクラスのフィールドがEという型パラメータになっているところがミソ。

操作の追加

このクラスに対してprint操作の追加を行う。

interface ExpP extends Exp {
    String print();
}

class LitP extends Lit implements ExpP {

    LitP(int x) {
        super(x);
    }

    @Override
    public String print() {
        return String.valueOf(getX());
    }
}

class AddP<E extends ExpP> extends Add<E> implements ExpP {

    AddP(E e1, E e2) {
        super(e1, e2);
    }

    @Override
    public String print() {
        return getE1().print() + " + " + getE2().print();
    }
}

既存クラスをいじらずに問題なく追加できた。

クラスの追加

Sub(引き算)を表すクラスを追加する。

@Data
@AllArgsConstructor
class Sub<E extends Exp> implements Exp {
    private E e1;
    private E e2;

    @Override
    public int eval() {
        return e1.eval() - e2.eval();
    }
}

class SubP<E extends ExpP> extends Sub<E> implements ExpP {

    SubP(E e1, E e2) {
        super(e1, e2);
    }

    @Override
    public String print() {
        return getE1().print() + " - " + getE2().print();
    }
}

どちらの場合でも、既存クラスを修正せずに操作の追加/クラスの追加が行えている。
これらを使う側のクラスは以下のようになる。

public class Main {

    public static void main(String[] args) {
        new Add<Exp>(new Lit(1), new Lit(3)).eval();
        new Sub<Exp>(new Lit(1), new Lit(3)).eval();

        new AddP<ExpP>(new LitP(1), new LitP(3)).print();
        new SubP<ExpP>(new LitP(1), new LitP(3)).print();
    }
}

高い拡張性が必要な状況(データ構造と操作の両方を拡張することが予想される)では、このデザインパターン(名称不明?)を覚えておいて損はないのではないか、と思う。
なお「サブタイピング+戻り値の共変性を使う」方法の方にも興味がある方は、下記の参考文献を参照されたし。(※この記事の内容は下記論文の丸パクリです)

参考文献

Wang, Yanlin, and Bruno C. D. S. Oliveira. "The expression problem, trivially!." Proceedings of the 15th International Conference on Modularity. ACM, 2016.

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0