如何解决如何在 Bool 或 Iff 中实现 Exp in the Paper Extensibility for the Masses
我目前正在阅读论文面向大众的可扩展性。对象代数的实用可扩展性 by Bruno C. d. S. Oliveira 和 William R. Cook(可在互联网上的许多地方找到 - 例如此处:https://www.cs.utexas.edu/~wcook/Drafts/2012/ecoop2012.pdf)。
在第 10 页,他们写道:
添加新的数据变体很容易。第一步是创建新类 Bool
和 Iff
以通常的面向对象风格(如 Lit
和 Add
):
class Bool implements Exp {...}
class Iff implements Exp {...}
Exp
的实现似乎留给读者作为练习。然而,我不清楚 Exp
在本文的这一部分中是如何定义的。我的问题是:
应该如何实现Bool
和Iff
?
这是我尝试过的:
Exp
的第一个定义
在论文的早期,Exp
接口是这样定义的:
interface Exp {
Value eval();
}
这里,Value
是由另一个接口定义的:
interface Value {
Integer getInt();
Boolean getBool();
}
然而,这篇论文很快就背离了 Exp
的这个定义,转而支持基于访问者的定义。
Bool
的可能实现
根据该定义,应该如何实现 Bool
类?
这样的事情似乎是一个开始:
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
返回什么。我想我可以返回 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
的可能实现
我们应该如何根据 Bool
的这个定义来实现建议的 Exp
类?
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)
返回 Exp
,但这是 eval
的哪个定义?
显然,Exp
仍然(或再次?)有一个 eval
方法,但现在它返回 int
。看起来像这样吗?
interface Exp {
int eval();
}
论文没有给出这个定义,所以我可能误解了一些东西。
Bool
的可能实现
我们可以使用 Bool
的这个定义来实现 Iff
和 Exp
吗?
class Bool implements Exp {
boolean x;
public Bool(boolean x) { this.x = x; }
public int eval() {
// What to return here?
}}
同样,目前还不清楚如何实现接口。当然,您可以将 0
返回为 false,将 1
返回为 true,但这只是一个随意的决定。这似乎不太合适。
我遗漏了 Exp
的第四个定义吗?或者论文中是否有其他一些信息让我无法理解?
顺便说一句,如果我在尝试中犯了错误,我深表歉意。我通常不编写 Java 代码。
解决方法
@马克。让我试着澄清你的困惑点。
Exp的定义
我们在第 10 页中假设的 Exp 的定义是:
interface Exp {
Value eval();
}
在论文前面的图 1 中介绍过。可以忽略带有访问者的 Exp 的替代版本。我们仅用它来讨论访问者以及与对象代数的关系,在本文后面的部分中没有发挥作用。
另外,关于第8页的代码,我认为论文中有一个错字。您是对的:似乎我们假设了一个返回 int
的 eval 方法。当我们写这篇论文时,我们使用了多个版本的 Exp,可能我们错误地使用了另一个版本的代码。第 8 页中的代码,根据论文中的表述进行了修改,应该是:
int x = exp(base).eval().getInt();
价值选择
我们打算用于实现 Value 类的代码使用异常,与您自己的尝试类似,并且确实如论文中所示那样是部分的。在论文中,我们的重点是关于源表达式的可扩展性和类型安全性。对于值,我们想要的只是值足够丰富以支持源表达式,并且假设值本身不可扩展(稍后在第 7 节中我们简要讨论了可扩展值)。我们在论文中选择的表示旨在保持代码简单,并避免在处理错误管理(与可扩展性正交)时分心。
很高兴这不是一个很好的表示,但至少对于当时的 Java 版本,这似乎是一个合理的妥协。在现代 Java 中,我认为要走的路是将 Value 建模为代数数据类型。 Java 16 支持 pattern matching and sealed types,它可用于对函数式编程中的代数数据类型进行本质建模。因此,您可以使用以下内容对 Value 进行建模:
sealed interface Value {}
record VInt(int x) implements Value {}
record VBool(boolean b) implements Value {}
回想起来,为了避免像您遇到的那种混淆,我认为最好仅使用界面来展示论文:
interface Exp {
int eval();
}
并使用 if
构造 à la C,其中整数扮演布尔值的角色。这首先可以避免因价值的表示而分心。
论文中价值的表示
无论如何,回到论文的表述,以及你关于偏袒的观点,我接下来展示了一个最小但完整的代码实现。为了说明偏向性不是一个基本问题,我还从使用未检查异常切换到检查异常。
首先我们可以定义如下值:
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();
}
}
然后你可以用 for Object Algebras 编写代码如下:
// 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 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的定义中看到的,我们最终需要处理异常。事实上,如果您想更好地处理错误,您应该更改解释器中的代码以捕获那里的异常,然后给出错误消息,例如“您不能将布尔值添加到整数...”和很快。这确实涉及额外的代码(与技术的要点不太相关),但很容易做到。
另请注意,我使用 lambdas 来避免一些带有匿名类的样板文件。在 Java 中,具有单个方法实现的匿名类可以写为 lambda。
如果您不喜欢使用异常的代码,并且您更喜欢更函数式的方法,您还可以使用 Java 的 Optional 类来模拟失败(就像您在 Haskell 等函数式语言中所做的那样) .在这种情况下,您可能会遇到以下情况:
// Values
interface Value {
Optional<Integer> intValue();
Optional<Boolean> boolValue();
}
// The expression interface
interface Exp {
Optional<Value> eval();
}
我们现在使用 Optional 而不是使操作部分化。带有此选项的 Java 代码(没有模式匹配)不会那么好,但也许有了新的模式匹配功能,它不会那么糟糕。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。