如何在 Bool 或 Iff 中实现 Exp(来自论文 Extensibility for the Masses)

Mar*_*ann 3 oop extensibility computer-science

我目前正在阅读《大众的可扩展性》一文。对象代数的实用可扩展性作者:Bruno C. d. S. Oliveira 和 William R. Cook(互联网上很多地方都可以找到 - 例如:https: //www.cs.utexas.edu/~wcook/Drafts/2012/ecoop2012.pdf)。

他们在第 10 页写道:

添加新的数据变体很容易。第一步是创建新类BoolIff采用通常的面向对象风格(如LitAdd):

class Bool implements Exp {...}
class Iff implements Exp {...}
Run Code Online (Sandbox Code Playgroud)

看起来,它的实现Exp是留给读者的练习。然而,我不清楚Exp本文的这一部分是如何定义的。我的问题是:

Bool应该如何Iff实施?

这是我尝试过的:

第一个定义Exp

在论文的早期,Exp接口是这样定义的:

interface Exp {
    Value eval();
}
Run Code Online (Sandbox Code Playgroud)

这里,Value是由另一个接口定义的:

interface Value {
    Integer getInt();
    Boolean getBool();
}
Run Code Online (Sandbox Code Playgroud)

然而,该论文很快就偏离了这个定义,Exp转而支持基于访问者的定义。

可能的实施Bool

根据该定义,应该如何实现该类Bool

像这样的事情似乎是一个开始:

class Bool implements Exp {
    boolean x;
    public Bool(boolean x) { this.x = x; }
    public Value eval() {
        return new VBool(x);
}}
Run Code Online (Sandbox Code Playgroud)

但问题是如何正确实施Value

该论文仅表明了这一点:

class VBool implements Value {...}
Run Code Online (Sandbox Code Playgroud)

对我来说,实施似乎并不完整:

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?
    }
}
Run Code Online (Sandbox Code Playgroud)

正如我上面的尝试所示,不清楚从 . 返回什么getInt。我想我可以返回 null 或抛出异常,但这意味着我的实现是部分的

无论如何,第一个定义Exp似乎仅作为本文中的一个激励示例存在,然后继续定义更好的替代方案。

第二个定义Exp

在第 4 页,该论文重新定义Exp了内部访问者:

interface Exp {    
    <A> A accept(IntAlg<A> vis);
}
Run Code Online (Sandbox Code Playgroud)

IntAlg<A>另一个接口在哪里:

interface IntAlg<A> {
    A lit(int x);
    A add(A e1, A e2);
}
Run Code Online (Sandbox Code Playgroud)

到目前为止,事情似乎很清楚,直到我们开始实施BoolIff......

可能的实施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?
}}
Run Code Online (Sandbox Code Playgroud)

没有办法凭空变A出一种价值,因此必须进行交互vis才能产生一种A价值。vis然而,参数仅定义和lit方法add

lit方法需要int,但在 中不可用Bool

同样,add需要两个 A值,这也是不可用的。我再次发现自己陷入了僵局。

的第三个定义Exp

然后,在第 8 页,论文展示了这个例子:

int x = exp(base).eval();
Run Code Online (Sandbox Code Playgroud)

这里,exp(base)返回Exp,但是这是哪个定义eval

显然,Exp仍然(或再次?)有一个eval方法,但现在它返回int。看起来像这样吗?

interface Exp {
    int eval();
}
Run Code Online (Sandbox Code Playgroud)

该论文没有显示这个定义,所以我可能会误解一些东西。

可能的实施Bool

我们可以用 的定义来实现Booland吗?IffExp

class Bool implements Exp {
    boolean x;
    public Bool(boolean x) { this.x = x; }
    public int eval() {
        // What to return here?
}}
Run Code Online (Sandbox Code Playgroud)

同样,尚不清楚如何实现该接口。当然,人们可以返回0错误和1真实,但这只是一个任意的决定。这似乎不太合适。

Exp我缺少第四个定义吗?或者论文中还有其他一些我无法理解的信息吗?

顺便说一句,如果我在尝试中犯了错误,我深表歉意。我通常不写 Java 代码。

Bru*_*ira 5

@标记。让我尝试澄清您的困惑点。

\n

指数的定义

\n

我们在第 10 页中假设的 Exp 定义是:

\n
interface Exp {\n    Value eval();\n}\n
Run Code Online (Sandbox Code Playgroud)\n

这在本文前面的图 1 中已经介绍过。带有访问者的 Exp 的替代版本可以忽略。我们只是用它来讨论访问者以及与对象代数的关系,它在本文的后面部分中不起作用。

\n

另外,关于第8页中的代码,我认为论文中存在拼写错误。你是对的:我们似乎假设了一个返回的 eval 方法int。当我们写这篇论文时,我们使用了 Exp 的多个版本,并且可能我们错误地使用了另一个版本的代码。第 8 页中的代码根据论文中的表示进行调整,应为:

\n
int x = exp(base).eval().getInt();\n
Run Code Online (Sandbox Code Playgroud)\n

价值选择

\n

我们打算用于实现 Value 类的代码使用异常,与您自己的尝试类似,并且它确实是部分的,如本文中所示。在本文中,我们提出的观点是关于源表达式的可扩展性和类型安全性。对于值,我们想要的只是值足够丰富以支持源表达式,并且假设值本身不可扩展(稍后在第 7 节中我们将简要讨论可扩展值)。我们在本文中选择的表示形式旨在保持代码简单,并避免处理错误管理(与可扩展性正交)时的一些干扰。

\n

令人欣慰的是,这并不是一个很好的表示,但至少对于当时的 Java 版本来说,这似乎是一个合理的妥协。在现代 Java 中,我认为正确的方法是将 Value 建模为代数数据类型。Java 16 支持模式匹配和密封类型,它们可用于在函数式编程中对代数数据类型进行本质建模。所以你可以用类似的东西来建模价值:

\n
sealed interface Value {}\nrecord VInt(int x) implements Value {}\nrecord VBool(boolean b) implements Value {} \n
Run Code Online (Sandbox Code Playgroud)\n

回想起来,为了避免像您遇到的那样的混乱,我认为最好仅使用以下界面来呈现论文:

\n
interface Exp {\n    int eval();\n}\n
Run Code Online (Sandbox Code Playgroud)\n

并使用if结构 \xc3\xa0 la C,其中整数扮演布尔值的角色。这首先可以避免对价值表示的干扰。

\n

论文中价值观的表述

\n

无论如何,回到论文的表示,以及关于偏袒的观点,我接下来将展示代码的最小但完整的实现。为了说明偏倚不是一个根本问题,我还从使用未检查的异常切换到使用检查的异常。

\n

首先我们可以定义值如下:

\n
interface Value {\n    // You can choose checked exceptions or unchecked exceptions.\n    // In the paper we opted for unchecked exceptions.\n    // But here, I show that you can also use checked exceptions,\n    // if you want to ensure that you deal with exception cases.\n    int intValue() throws Exception;\n    boolean boolValue() throws Exception;\n}\n\nclass VInt implements Value {\n    int x;\n\n    public VInt(int x) {\n        this.x = x;\n    }\n\n    public int intValue() throws Exception {\n        return x;\n    }\n\n    public boolean boolValue() throws Exception {\n        throw new Exception();\n    }\n\n    public String toString() {\n        return Integer.valueOf(x).toString();\n    }\n}\n\nclass VBool implements Value {\n    boolean b;\n\n    public VBool(boolean b) {\n        this.b = b;\n    }\n\n    public int intValue() throws Exception {\n        throw new Exception();\n    }\n\n    public boolean boolValue() throws Exception {\n        return b;\n    }\n\n    public String toString() {\n        return Boolean.valueOf(b).toString();\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

然后您可以使用对象代数编写代码,如下所示:

\n
    // Integer Expressions\ninterface IntAlg<Exp> {\n    Exp lit(int x);\n    Exp add(Exp e1, Exp e2);\n}\n\ninterface Exp {\n    Value eval() throws Exception;\n}\n\nclass IntEval implements IntAlg<Exp> {\n    public Exp lit(int x) {\n        return () -> new VInt(x);\n    }\n\n    public Exp add(Exp e1, Exp e2) {\n        return () -> new VInt(e1.eval().intValue() + e2.eval().intValue());\n    }\n}\n\n// Boolean Expressions\ninterface BoolAlg<Exp> extends IntAlg<Exp> {\n    Exp bool(boolean b);\n    Exp iff(Exp e1, Exp e2, Exp e3);\n}\n\nclass BoolEval extends IntEval implements BoolAlg<Exp> {\n    public Exp bool(boolean b) {\n        return () -> new VBool(b);\n    }\n\n    public Exp iff(Exp e1, Exp e2, Exp e3) {\n        return () -> e1.eval().boolValue() ? e2.eval() : e3.eval();\n    }\n}\n\npublic class Main {\n    public static <Exp> Exp m(IntAlg<Exp> alg) {\n        return alg.add(alg.lit(4),alg.lit(3));\n    }\n\n    public static <Exp> Exp m2(BoolAlg<Exp> alg) {\n        return alg.iff(alg.bool(false),m(alg),alg.bool(true));\n    }\n\n    public static void main(String[] args) {\n        Exp e = m(new IntEval());\n        try {\n            System.out.println(e.eval());\n        } catch (Exception exp) {\n            System.out.println("Ops! There is an error...");\n        }\n\n        Exp e2 = m2(new BoolEval());\n        try {\n            System.out.println(e2.eval());\n        } catch (Exception exp) {\n            System.out.println("Ops! There is an error...");\n        }\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

正如您在 main 的定义中看到的,我们最终需要处理异常。事实上,如果您想更好地处理错误,您应该更改解释器中的代码以捕获其中的异常,然后给出错误消息,例如“您无法将布尔值添加到整数...”和很快。这确实涉及额外的代码(与该技术的要点不太相关),但它可以很容易地完成。

\n

另请注意,我使用 lambda 来避免使用匿名类的一些样板。在 Java 中,具有单个方法实现的匿名类可以编写为 lambda。

\n

如果您不喜欢使用异常的代码,并且您更喜欢更函数式的方法,您还可以使用 Java 的可选类来建模故障(就像您在 Haskell 等函数式语言中所做的那样,说)。在这种情况下,您可以有以下内容:

\n
// Values\ninterface Value {\n    Optional<Integer> intValue();\n    Optional<Boolean> boolValue();\n} \n\n// The expression interface\ninterface Exp {\n    Optional<Value> eval();\n}\n
Run Code Online (Sandbox Code Playgroud)\n

我们现在使用Optional而不是部分操作。使用此选项的 Java 代码(没有模式匹配)不会那么好,但也许使用新的模式匹配功能,它不会那么糟糕。

\n