GADTs 在函数式语言中是否等同于传统的 OOP +泛型,或者有一种情况是GADT很容易强制执行正确性约束但很难或不可能实现Java或C#?
例如,这个"良好类型的口译员" Haskell计划:
data Expr a where
N :: Int -> Expr Int
Suc :: Expr Int -> Expr Int
IsZero :: Expr Int -> Expr Bool
Or :: Expr Bool -> Expr Bool -> Expr Bool
eval :: Expr a -> a
eval (N n) = n
eval (Suc e) = 1 + eval e
eval (IsZero e) = 0 == eval e
eval (Or a b) = eval a || eval b
可以使用泛型和每个子类的适当实现在Java中等效编写,但更详细:
interface Expr<T> {
public <T> T eval();
}
class N extends Expr<Integer> {
private Integer n;
public N(Integer m) {
n = m;
}
@Override public Integer eval() {
return n;
}
}
class Suc extends Expr<Integer> {
private Expr<Integer> prev;
public Suc(Expr<Integer> aprev) {
prev = aprev;
}
@Override public Integer eval() {
return 1 + prev.eval()
}
}
/** And so on ... */
答案 0 :(得分:13)
OOP类是开放的,GADT是关闭的(就像普通的ADT一样)。
这里,“open”意味着您以后可以随时添加更多子类,因此编译器不能假定可以访问给定类的所有子类。 (有一些例外,例如Java的final
但是它阻止了任何子类化,以及Scala的密封类)。相反,ADT在某种意义上是“封闭的”,你不能在以后添加更多的构造函数,并且编译器知道(并且可以利用它来检查例如详尽性)。有关详细信息,请参阅“expression problem”。
请考虑以下代码:
data A a where
A1 :: Char -> A Char
A2 :: Int -> A Int
data B b where
B1 :: Char -> B Char
B2 :: String -> B String
foo :: A t -> B t -> Char
foo (A1 x) (B1 y) = max x y
上述代码依赖于Char
是唯一可以生成t
和A t
的{{1}}类型。关闭的GADT可以确保这一点。如果我们试图使用OOP类来模仿它,我们会失败:
B t
在这里,我认为除非采用像类型转换这样的不安全类型操作,否则我们无法实现相同的功能。 (此外,由于类型擦除,Java中的这些内容甚至不考虑参数class A1 extends A<Char> ...
class A2 extends A<Int> ...
class B1 extends B<Char> ...
class B2 extends B<String> ...
<T> Char foo(A<T> a, B<T> b) {
// ??
}
。)我们可能会考虑向T
或A
添加一些通用方法以允许此操作,但是这样做会迫使我们为B
和/或Int
实施所述方法。
在这种特定情况下,人们可能只是诉诸非泛型函数:
String
或等效地向这些类添加非泛型方法。
但是,Char foo(A<Char> a, B<Char> b) // ...
和A
之间共享的类型可能比单例B
更大。更糟糕的是,类是开放的,所以只要添加一个新的子类,集合就会变大。
此外,即使您有Char
类型的变量,您仍然不知道它是否为A<Char>
,因此您无法访问A1
的字段除了使用类型转换。这里输入的类型是安全的,因为程序员知道没有A1
的其他子类。在一般情况下,这可能是错误的,例如。
A<Char>
此处data A a where
A1 :: Char -> A Char
A2 :: t -> t -> A t
必须是A<Char>
和A1
的超类。
@gsg在关于平等证人的评论中提出要求。考虑
A2<Char>
这可以翻译为
data Teq a b where
Teq :: Teq t t
foo :: Teq a b -> a -> b
foo Teq x = x
trans :: Teq a b -> Teq b c -> Teq a c
trans Teq Teq = Teq
上面的代码为所有类型对interface Teq<A,B> {
public B foo(A x);
public <C> Teq<A,C> trans(Teq<B,C> x);
}
class Teq1<A> implements Teq<A,A> {
public A foo(A x) { return x; }
public <C> Teq<A,C> trans(Teq<A,C> x) { return x; }
}
声明了一个接口,然后只有A,B
(A=B
}类由implements Teq<A,A>
类实现。
界面需要从Teq1
到foo
的转换函数A
,以及B
类型为trans
的“及物性证明”this
和
Teq<A,B>
类型的x
可以生成对象Teq<B,C>
。这是类似于上面使用GADT的Haskell代码的Java。
根据我的意见,Teq<A,C>
时无法安全地实现该类:它需要返回空值或使用非终止作弊。
答案 1 :(得分:5)
泛型不提供类型相等约束。如果没有它们,你需要依靠垂头丧气,即失去类型安全。此外,某些调度 - 特别是访问者模式 - 不能安全地实现,并且具有适用于GADT的泛型的适当接口。请参阅本文,调查这个问题:
Generalized Algebraic Data Types and Object-Oriented Programming
安德鲁肯尼迪,克劳迪奥鲁索。 OOPSLA 2005。