How to implement Exp in Bool or Iff 来自论文可扩展性
How to implement Exp in Bool or Iff from the paper Extensibility for the Masses
我目前正在阅读论文 大众的可扩展性。对象代数的实用可扩展性,Bruno C. d. S. Oliveira 和 William R. Cook(可在互联网上的许多地方找到 - 例如此处:https://www.cs.utexas.edu/~wcook/Drafts/2012/ecoop2012.pdf)。
在第 10 页,他们写道:
Adding new data variants is easy. The first step is to create new classes Bool
and Iff
in the usual object-oriented style (like Lit
and Add
):
class Bool implements Exp {...}
class Iff implements Exp {...}
Exp
的实现似乎留给了 reader 作为练习。然而,我不清楚 Exp
在本文的这一部分是如何定义的。我的问题是:
Bool
和Iff
应该如何实现?
这是我试过的方法:
Exp
的第一个定义
在论文的开头,Exp
接口是这样定义的:
interface Exp {
Value eval();
}
这里,Value
是由另一个接口定义的:
interface Value {
Integer getInt();
Boolean getBool();
}
然而,这篇论文很快就偏离了 Exp
的定义,转而支持基于访问者的定义。
Bool
的可能实现
根据该定义,应该如何实施 Bool
class?
这样的事情似乎是一个开始:
class Bool implements Exp {
boolean x;
public Bool(boolean x) { this.x = x; }
public Value eval() {
return new VBool(x);
}}
然而,问题变成了如何正确实施 Value
?
论文只显示了这个:
class VBool implements Value {...}
对我来说,实施似乎并不完整:
class VBool implements Value {
boolean x;
public VBool(boolean x) { this.x = x; }
public Boolean getBool() {
return new Boolean(x);
}
public Integer getInt() {
// What to return here?
}
}
正如我上面的尝试所示,不清楚从 getInt
到 return 的内容。我想我可以 return null 或抛出异常,但这意味着我的实现是 partial.
无论如何,Exp
的第一个定义似乎只是作为论文中的一个激励示例存在,然后继续定义更好的替代方案。
Exp
的第二个定义
论文第 4 页将 Exp
重新定义为内部访问者:
interface Exp {
<A> A accept(IntAlg<A> vis);
}
其中IntAlg<A>
是另一个接口:
interface IntAlg<A> {
A lit(int x);
A add(A e1, A e2);
}
到目前为止,事情似乎很清楚,直到我们开始实施 Bool
和 Iff
...
Bool
的可能实现
根据Exp
的这个定义,我们应该如何实现提议的Bool
class?
class Bool implements Exp {
boolean x;
public Bool(boolean x) { this.x = x; }
public <A> A accept(IntAlg<A> vis) {
// What to return here?
}}
无法凭空变出 A
值,因此必须与 vis
交互才能产生 A
值。但是,vis
参数只定义了 lit
和 add
方法。
lit
方法需要一个 int
,它在 Bool
中不可用。
同样,add
需要 两个 A
值,这些值也不可用。再一次,我发现自己陷入了僵局。
Exp
的第三个定义?
然后,在第 8 页,论文显示了这个例子:
int x = exp(base).eval();
这里,exp(base)
returns Exp
,但是eval
是哪个定义呢?
显然,Exp
仍然(或再次?)有一个 eval
方法,但现在 return 是 int
。看起来像这样吗?
interface Exp {
int eval();
}
这篇论文没有给出这个定义,所以我可能误解了什么。
Bool
的可能实现
我们可以用 Exp
的定义来实现 Bool
和 Iff
吗?
class Bool implements Exp {
boolean x;
public Bool(boolean x) { this.x = x; }
public int eval() {
// What to return here?
}}
同样,不清楚如何实现该接口。当然,可以 return 0
代表 false,1
代表 true,但这只是一个任意决定。这似乎不合适。
我是否缺少 Exp
的第四个定义?还是论文中还有其他一些我没有理解的信息?
顺便说一句,如果我在尝试中犯了错误,我深表歉意。我一般不写Java代码。
@马克。让我试着澄清你的困惑点。
Exp 的定义
我们在第10页假设的Exp的定义是:
interface Exp {
Value eval();
}
这在图 1 中的论文前面已经介绍过。可以忽略带有访问者的 Exp 的替代版本。我们只是用它来谈论访问者以及与Object Algebras的关系,它在论文的后面部分没有发挥作用。
另外,关于第8页的代码,我认为论文中有错别字。你是对的:我们似乎假设了一个 returns int
的 eval 方法。当我们写这篇论文时,我们使用了多个版本的 Exp,可能我们错误地使用了另一个版本的代码。第8页的代码,适配论文中的表述,应该是:
int x = exp(base).eval().getInt();
价值的选择
我们打算用于实现值 class 的代码使用了异常,类似于您自己的尝试,并且它确实如论文中所述是部分的。在本文中,我们提出的要点是关于源表达式的可扩展性和类型安全性。对于值,我们想要的只是值足够丰富以支持源表达式,并且假设值本身是不可扩展的(稍后在第 7 节中我们将简要讨论可扩展值)。我们在论文中选择的表示旨在保持代码简单,并避免在处理错误管理(与可扩展性正交)时分心。
同意这不是一个很好的代表,但至少对于当时的 Java 版本来说,这似乎是一个合理的妥协。在现代 Java 中,我认为要走的路是将 Value 建模为代数数据类型。 Java 16 支持 pattern matching and sealed types,可用于在函数式编程中对代数数据类型进行本质建模。所以你可以用类似的东西来模拟价值:
sealed interface Value {}
record VInt(int x) implements Value {}
record VBool(boolean b) implements Value {}
回想起来,为了避免像您现在这样的混淆,我认为直接使用界面来展示论文会更好:
interface Exp {
int eval();
}
并使用 if
构造 à la C,其中整数扮演布尔值的角色。这将首先避免对 Value 表示的干扰。
论文中值的表示
无论如何,回到论文的表述,以及你关于偏颇的观点,接下来我将展示代码的最小但完整的实现。为了说明偏颇不是根本问题,我也从使用未检查的异常切换到检查的异常。
首先我们可以定义如下值:
interface Value {
// You can choose checked exceptions or unchecked exceptions.
// In the paper we opted for unchecked exceptions.
// But here, I show that you can also use checked exceptions,
// if you want to ensure that you deal with exception cases.
int intValue() throws Exception;
boolean boolValue() throws Exception;
}
class VInt implements Value {
int x;
public VInt(int x) {
this.x = x;
}
public int intValue() throws Exception {
return x;
}
public boolean boolValue() throws Exception {
throw new Exception();
}
public String toString() {
return Integer.valueOf(x).toString();
}
}
class VBool implements Value {
boolean b;
public VBool(boolean b) {
this.b = b;
}
public int intValue() throws Exception {
throw new Exception();
}
public boolean boolValue() throws Exception {
return b;
}
public String toString() {
return Boolean.valueOf(b).toString();
}
}
然后你可以用对象代数写代码如下:
// Integer Expressions
interface IntAlg<Exp> {
Exp lit(int x);
Exp add(Exp e1, Exp e2);
}
interface Exp {
Value eval() throws Exception;
}
class IntEval implements IntAlg<Exp> {
public Exp lit(int x) {
return () -> new VInt(x);
}
public Exp add(Exp e1, Exp e2) {
return () -> new VInt(e1.eval().intValue() + e2.eval().intValue());
}
}
// Boolean Expressions
interface BoolAlg<Exp> extends IntAlg<Exp> {
Exp bool(boolean b);
Exp iff(Exp e1, Exp e2, Exp e3);
}
class BoolEval extends IntEval implements BoolAlg<Exp> {
public Exp bool(boolean b) {
return () -> new VBool(b);
}
public Exp iff(Exp e1, Exp e2, Exp e3) {
return () -> e1.eval().boolValue() ? e2.eval() : e3.eval();
}
}
public class Main {
public static <Exp> Exp m(IntAlg<Exp> alg) {
return alg.add(alg.lit(4),alg.lit(3));
}
public static <Exp> Exp m2(BoolAlg<Exp> alg) {
return alg.iff(alg.bool(false),m(alg),alg.bool(true));
}
public static void main(String[] args) {
Exp e = m(new IntEval());
try {
System.out.println(e.eval());
} catch (Exception exp) {
System.out.println("Ops! There is an error...");
}
Exp e2 = m2(new BoolEval());
try {
System.out.println(e2.eval());
} catch (Exception exp) {
System.out.println("Ops! There is an error...");
}
}
}
正如您在 main 的定义中看到的,我们最终需要处理异常。事实上,如果你想更好地处理错误,你应该更改解释器中的代码以捕获那里的异常,然后给出错误消息,如“你不能将布尔值添加到整数......”和很快。这确实涉及额外的代码(这与技术的要点没有太大关系),但可以轻松完成。
另请注意,我使用 lambda 来避免使用匿名 classes 的一些样板。在 Java 中,具有单个方法实现的匿名 classes 可以写成 lambda。
如果您不喜欢使用异常的代码,并且更喜欢更函数式的方法,您也可以使用 Java 的可选 class 来模拟失败(就像你会用像 Haskell 这样的函数式语言来做)。在这种情况下,您可以使用以下内容:
// Values
interface Value {
Optional<Integer> intValue();
Optional<Boolean> boolValue();
}
// The expression interface
interface Exp {
Optional<Value> eval();
}
我们现在使用 Optional 而不是部分操作。使用此选项的 Java 代码(没有模式匹配)不会那么好,但也许使用新的模式匹配功能它不会那么糟糕。
我目前正在阅读论文 大众的可扩展性。对象代数的实用可扩展性,Bruno C. d. S. Oliveira 和 William R. Cook(可在互联网上的许多地方找到 - 例如此处:https://www.cs.utexas.edu/~wcook/Drafts/2012/ecoop2012.pdf)。
在第 10 页,他们写道:
Adding new data variants is easy. The first step is to create new classes
Bool
andIff
in the usual object-oriented style (likeLit
andAdd
):class Bool implements Exp {...} class Iff implements Exp {...}
Exp
的实现似乎留给了 reader 作为练习。然而,我不清楚 Exp
在本文的这一部分是如何定义的。我的问题是:
Bool
和Iff
应该如何实现?
这是我试过的方法:
Exp
的第一个定义
在论文的开头,Exp
接口是这样定义的:
interface Exp {
Value eval();
}
这里,Value
是由另一个接口定义的:
interface Value {
Integer getInt();
Boolean getBool();
}
然而,这篇论文很快就偏离了 Exp
的定义,转而支持基于访问者的定义。
Bool
的可能实现
根据该定义,应该如何实施 Bool
class?
这样的事情似乎是一个开始:
class Bool implements Exp {
boolean x;
public Bool(boolean x) { this.x = x; }
public Value eval() {
return new VBool(x);
}}
然而,问题变成了如何正确实施 Value
?
论文只显示了这个:
class VBool implements Value {...}
对我来说,实施似乎并不完整:
class VBool implements Value {
boolean x;
public VBool(boolean x) { this.x = x; }
public Boolean getBool() {
return new Boolean(x);
}
public Integer getInt() {
// What to return here?
}
}
正如我上面的尝试所示,不清楚从 getInt
到 return 的内容。我想我可以 return null 或抛出异常,但这意味着我的实现是 partial.
无论如何,Exp
的第一个定义似乎只是作为论文中的一个激励示例存在,然后继续定义更好的替代方案。
Exp
的第二个定义
论文第 4 页将 Exp
重新定义为内部访问者:
interface Exp {
<A> A accept(IntAlg<A> vis);
}
其中IntAlg<A>
是另一个接口:
interface IntAlg<A> {
A lit(int x);
A add(A e1, A e2);
}
到目前为止,事情似乎很清楚,直到我们开始实施 Bool
和 Iff
...
Bool
的可能实现
根据Exp
的这个定义,我们应该如何实现提议的Bool
class?
class Bool implements Exp {
boolean x;
public Bool(boolean x) { this.x = x; }
public <A> A accept(IntAlg<A> vis) {
// What to return here?
}}
无法凭空变出 A
值,因此必须与 vis
交互才能产生 A
值。但是,vis
参数只定义了 lit
和 add
方法。
lit
方法需要一个 int
,它在 Bool
中不可用。
同样,add
需要 两个 A
值,这些值也不可用。再一次,我发现自己陷入了僵局。
Exp
的第三个定义?
然后,在第 8 页,论文显示了这个例子:
int x = exp(base).eval();
这里,exp(base)
returns Exp
,但是eval
是哪个定义呢?
显然,Exp
仍然(或再次?)有一个 eval
方法,但现在 return 是 int
。看起来像这样吗?
interface Exp {
int eval();
}
这篇论文没有给出这个定义,所以我可能误解了什么。
Bool
的可能实现
我们可以用 Exp
的定义来实现 Bool
和 Iff
吗?
class Bool implements Exp {
boolean x;
public Bool(boolean x) { this.x = x; }
public int eval() {
// What to return here?
}}
同样,不清楚如何实现该接口。当然,可以 return 0
代表 false,1
代表 true,但这只是一个任意决定。这似乎不合适。
我是否缺少 Exp
的第四个定义?还是论文中还有其他一些我没有理解的信息?
顺便说一句,如果我在尝试中犯了错误,我深表歉意。我一般不写Java代码。
@马克。让我试着澄清你的困惑点。
Exp 的定义
我们在第10页假设的Exp的定义是:
interface Exp {
Value eval();
}
这在图 1 中的论文前面已经介绍过。可以忽略带有访问者的 Exp 的替代版本。我们只是用它来谈论访问者以及与Object Algebras的关系,它在论文的后面部分没有发挥作用。
另外,关于第8页的代码,我认为论文中有错别字。你是对的:我们似乎假设了一个 returns int
的 eval 方法。当我们写这篇论文时,我们使用了多个版本的 Exp,可能我们错误地使用了另一个版本的代码。第8页的代码,适配论文中的表述,应该是:
int x = exp(base).eval().getInt();
价值的选择
我们打算用于实现值 class 的代码使用了异常,类似于您自己的尝试,并且它确实如论文中所述是部分的。在本文中,我们提出的要点是关于源表达式的可扩展性和类型安全性。对于值,我们想要的只是值足够丰富以支持源表达式,并且假设值本身是不可扩展的(稍后在第 7 节中我们将简要讨论可扩展值)。我们在论文中选择的表示旨在保持代码简单,并避免在处理错误管理(与可扩展性正交)时分心。
同意这不是一个很好的代表,但至少对于当时的 Java 版本来说,这似乎是一个合理的妥协。在现代 Java 中,我认为要走的路是将 Value 建模为代数数据类型。 Java 16 支持 pattern matching and sealed types,可用于在函数式编程中对代数数据类型进行本质建模。所以你可以用类似的东西来模拟价值:
sealed interface Value {}
record VInt(int x) implements Value {}
record VBool(boolean b) implements Value {}
回想起来,为了避免像您现在这样的混淆,我认为直接使用界面来展示论文会更好:
interface Exp {
int eval();
}
并使用 if
构造 à la C,其中整数扮演布尔值的角色。这将首先避免对 Value 表示的干扰。
论文中值的表示
无论如何,回到论文的表述,以及你关于偏颇的观点,接下来我将展示代码的最小但完整的实现。为了说明偏颇不是根本问题,我也从使用未检查的异常切换到检查的异常。
首先我们可以定义如下值:
interface Value {
// You can choose checked exceptions or unchecked exceptions.
// In the paper we opted for unchecked exceptions.
// But here, I show that you can also use checked exceptions,
// if you want to ensure that you deal with exception cases.
int intValue() throws Exception;
boolean boolValue() throws Exception;
}
class VInt implements Value {
int x;
public VInt(int x) {
this.x = x;
}
public int intValue() throws Exception {
return x;
}
public boolean boolValue() throws Exception {
throw new Exception();
}
public String toString() {
return Integer.valueOf(x).toString();
}
}
class VBool implements Value {
boolean b;
public VBool(boolean b) {
this.b = b;
}
public int intValue() throws Exception {
throw new Exception();
}
public boolean boolValue() throws Exception {
return b;
}
public String toString() {
return Boolean.valueOf(b).toString();
}
}
然后你可以用对象代数写代码如下:
// Integer Expressions
interface IntAlg<Exp> {
Exp lit(int x);
Exp add(Exp e1, Exp e2);
}
interface Exp {
Value eval() throws Exception;
}
class IntEval implements IntAlg<Exp> {
public Exp lit(int x) {
return () -> new VInt(x);
}
public Exp add(Exp e1, Exp e2) {
return () -> new VInt(e1.eval().intValue() + e2.eval().intValue());
}
}
// Boolean Expressions
interface BoolAlg<Exp> extends IntAlg<Exp> {
Exp bool(boolean b);
Exp iff(Exp e1, Exp e2, Exp e3);
}
class BoolEval extends IntEval implements BoolAlg<Exp> {
public Exp bool(boolean b) {
return () -> new VBool(b);
}
public Exp iff(Exp e1, Exp e2, Exp e3) {
return () -> e1.eval().boolValue() ? e2.eval() : e3.eval();
}
}
public class Main {
public static <Exp> Exp m(IntAlg<Exp> alg) {
return alg.add(alg.lit(4),alg.lit(3));
}
public static <Exp> Exp m2(BoolAlg<Exp> alg) {
return alg.iff(alg.bool(false),m(alg),alg.bool(true));
}
public static void main(String[] args) {
Exp e = m(new IntEval());
try {
System.out.println(e.eval());
} catch (Exception exp) {
System.out.println("Ops! There is an error...");
}
Exp e2 = m2(new BoolEval());
try {
System.out.println(e2.eval());
} catch (Exception exp) {
System.out.println("Ops! There is an error...");
}
}
}
正如您在 main 的定义中看到的,我们最终需要处理异常。事实上,如果你想更好地处理错误,你应该更改解释器中的代码以捕获那里的异常,然后给出错误消息,如“你不能将布尔值添加到整数......”和很快。这确实涉及额外的代码(这与技术的要点没有太大关系),但可以轻松完成。
另请注意,我使用 lambda 来避免使用匿名 classes 的一些样板。在 Java 中,具有单个方法实现的匿名 classes 可以写成 lambda。
如果您不喜欢使用异常的代码,并且更喜欢更函数式的方法,您也可以使用 Java 的可选 class 来模拟失败(就像你会用像 Haskell 这样的函数式语言来做)。在这种情况下,您可以使用以下内容:
// Values
interface Value {
Optional<Integer> intValue();
Optional<Boolean> boolValue();
}
// The expression interface
interface Exp {
Optional<Value> eval();
}
我们现在使用 Optional 而不是部分操作。使用此选项的 Java 代码(没有模式匹配)不会那么好,但也许使用新的模式匹配功能它不会那么糟糕。