Javaで合計タイプを定義する方法はありますか? Javaは自然に製品タイプを直接サポートしているようで、enumはsumタイプをサポートできると思い、継承は多分それができるように見えますが、少なくとも1つのケースがあります '詳述すると、合計型とは、Cのタグ付き共用体のように、異なる型のセットの1つを正確に持つことができる型のことです。
data Either a b = Left a | Right b
しかし、基本レベルでは、製品タイプとして実装する必要があり、そのフィールドの1つを無視するだけです。
public class Either<L,R>
{
private L left = null;
private R right = null;
public static <L,R> Either<L,R> right(R right)
{
return new Either<>(null, right);
}
public static <L,R> Either<L,R> left(L left)
{
return new Either<>(left, null);
}
private Either(L left, R right) throws IllegalArgumentException
{
this.left = left;
this.right = right;
if (left != null && right != null)
{
throw new IllegalArgumentException("An Either cannot be created with two values");
}
if (left == right)
{
throw new IllegalArgumentException("An Either cannot be created without a value");
}
}
.
.
.
}
私はこれを継承で実装しようとしましたが、ワイルドカード型のパラメータ、または同等のものを使用する必要があります。Javaジェネリックでは許可されません:
public class Left<L> extends Either<L,?>
私はJavaのEnumをあまり使用しませんでしたが、JavaのEnumが次善の候補のように見えますが、希望はありません。
この時点で、これはObject
値を型キャストすることによってのみ可能になると思います。一度だけ、安全に、できる方法がない限り、これを完全に回避したいと思います。これをすべての合計タイプに使用します。
Either
を1つのプライベートコンストラクターを持つ抽象クラスにし、クラス内に「データコンストラクター」(left
およびright
静的ファクトリーメソッド)をネストして、プライベートコンストラクターを表示できるようにします。そうでなければ、型を効果的に封印することができます。
抽象メソッド either
を使用して、完全なパターンマッチングをシミュレートし、静的ファクトリメソッドによって返される具象型を適切にオーバーライドします。便利なメソッドを実装します( fromLeft
、 fromRight
、 bimap
、 first
、 second
)either
に関して。
import Java.util.Optional;
import Java.util.function.Function;
public abstract class Either<A, B> {
private Either() {}
public abstract <C> C either(Function<? super A, ? extends C> left,
Function<? super B, ? extends C> right);
public static <A, B> Either<A, B> left(A value) {
return new Either<>() {
@Override
public <C> C either(Function<? super A, ? extends C> left,
Function<? super B, ? extends C> right) {
return left.apply(value);
}
};
}
public static <A, B> Either<A, B> right(B value) {
return new Either<>() {
@Override
public <C> C either(Function<? super A, ? extends C> left,
Function<? super B, ? extends C> right) {
return right.apply(value);
}
};
}
public Optional<A> fromLeft() {
return this.either(Optional::of, value -> Optional.empty());
}
// other convenience methods
}
快適で安全!それを台無しにする方法はありません。
class Left<L> extends Either<L,?>
を実行しようとしていた問題については、署名<A, B> Either<A, B> left(A value)
を考慮してください。型パラメーターB
は、パラメーターリストに表示されません。したがって、何らかのタイプA
の値を指定すると、anyタイプB
に対してEither<A, B>
を取得できます。
和型をエンコードする標準的な方法は、代数的データ型をそのeliminator、つまり、パターンマッチングを行う関数。 Haskellの場合:
left :: a -> (a -> r) -> (b -> r) -> r
left x l _ = l x
right :: b -> (a -> r) -> (b -> r) -> r
right x _ r = r x
match :: (a -> r) -> (b -> r) -> ((a -> r) -> (b -> r) -> r) -> r
match l r k = k l r
-- Or, with a type synonym for convenience:
type Either a b r = (a -> r) -> (b -> r) -> r
left :: a -> Either a b r
right :: b -> Either a b r
match :: (a -> r) -> (b -> r) -> Either a b r -> r
Javaこれは訪問者のように見えます:
public interface Either<A, B> {
<R> R match(Function<A, R> left, Function<B, R> right);
}
public final class Left<A, B> implements Either<A, B> {
private final A value;
public Left(A value) {
this.value = value;
}
public <R> R match(Function<A, R> left, Function<B, R> right) {
return left.apply(value);
}
}
public final class Right<A, B> implements Either<A, B> {
private final B value;
public Right(B value) {
this.value = value;
}
public <R> R match(Function<A, R> left, Function<B, R> right) {
return right.apply(value);
}
}
使用例:
Either<Integer, String> result = new Left<Integer, String>(42);
String message = result.match(
errorCode -> "Error: " + errorCode.toString(),
successMessage -> successMessage);
便宜上、型パラメーターを毎回言及することなく、Left
およびRight
値を作成するためのファクトリーを作成できます。結果を生成せずにパターンマッチングのオプションが必要な場合は、Consumer<A> left, Consumer<B> right
の代わりにFunction<A, R> left, Function<B, R> right
を受け入れるmatch
のバージョンを追加することもできます。
さて、継承ソリューションは間違いなく最も有望です。やりたいことはclass Left<L> extends Either<L, ?>
、これは残念ながらJavaの一般的なルールのためにできません。ただし、Left
またはRight
のタイプが「代替」可能性をエンコードする必要があるという譲歩をすれば、これを行うことができます。
public class Left<L, R> extends Either<L, R>`
今、私たちはLeft<Integer, A>
からLeft<Integer, B>
、実際にはseの2番目のタイプのパラメーターではないため。内部でこの変換を行うメソッドを定義して、その自由を型システムにエンコードできます。
public <R1> Left<L, R1> phantom() {
return new Left<L, R1>(contents);
}
完全な例:
public class EitherTest {
public abstract static class Either<L, R> {}
public static class Left<L, R> extends Either<L, R> {
private L contents;
public Left(L x) {
contents = x;
}
public <R1> Left<L, R1> phantom() {
return new Left<L, R1>(contents);
}
}
public static class Right<L, R> extends Either<L, R> {
private R contents;
public Right(R x) {
contents = x;
}
public <L1> Right<L1, R> phantom() {
return new Right<L1, R>(contents);
}
}
}
もちろん、実際にコンテンツにアクセスし、値がLeft
またはRight
であるかどうかを確認するための関数を追加して、instanceof
を明示的に振りかける必要がないようにします。どこでもキャストしますが、少なくともこれで開始するには十分です。
継承canを使用して合計タイプ(非結合ユニオン)をエミュレートしますが、対処する必要があるいくつかの問題があります。
Optional.get()
を検討してください。理想的には、このメソッドは、値がsome
ではなくnone
であることがわかっている、互いに素なタイプでのみ使用できます。しかし、それを行う方法はないので、一般的なOptional
型のインスタンスメンバーです。 「case」が「none」であるオプションで呼び出すと、NoSuchElementException
がスローされます。TL; DR:Javaの関数型プログラミングは快適な体験ではありません。