【问题标题】:How to implement Exp in Bool or Iff from the paper Extensibility for the MassesHow to implement Exp in Bool or IFF from the paper Extensibility for the Masses
【发布时间】:2021-06-03 08:45:24
【问题描述】:

我目前正在阅读论文 Extensibility for the Masses。 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 {...}

Exp 的实现似乎留给读者作为练习。但是,我不清楚本文的这一部分是如何定义 Exp 的。我的问题是:

BoolIff应该如何实现?

这是我尝试过的:

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&lt;A&gt;是另一个接口:

interface IntAlg<A> {
    A lit(int x);
    A add(A e1, A e2);
}

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

Bool 的可能实现

我们应该如何基于Exp的这个定义来实现提议的Bool类?

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 参数只定义了litadd 方法。

lit 方法需要 int,而 Bool 中没有。

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

Exp的第三个定义?

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

int x = exp(base).eval();

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

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

interface Exp {
    int eval();
}

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

Bool 的可能实现

我们可以用Exp的这个定义来实现BoolIff吗?

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

同样,不清楚如何实现接口。当然,可以返回0 为假,1 为真,但这只是一个任意决定。这似乎不合适。

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

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

【问题讨论】:

  • 这个抽象根本就不是抽象。当我声明一个必须支持从int 到该类型并返回到int 的“抽象类型”时,我只是声明了这个类型是一个int 容器。这条线public A add(A x, A y) { return valFact.lit(x.getInt() + y.getInt()); } 总是执行int 加法,无论A 是如何实现的,并且以不同方式实现int 容器的可能性在实践中并没有真正的帮助。您已经在为VBool 苦苦挣扎,但是如何添加,例如VString?我在本文中没有看到解决方案。
  • @Holger 你可能是对的。我正在看这篇论文,因为每次我在互联网上提到表达问题时,总是有人向我扔这篇论文。我试图弄清楚它是否有任何实质内容。我开始以开放的心态阅读,但现在,由于我在这里概述的问题,我在围栏上。

标签: oop computer-science extensibility


【解决方案1】:

@马克。让我试着澄清一下你的困惑点。

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 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 的定义中看到的那样,我们最终需要处理异常。事实上,如果您想更好地处理错误,您应该更改解释器中的代码以捕获那里的异常,然后给出错误消息,例如“您不能将布尔值添加到整数......”和很快。这确实涉及额外的代码(这与技术的要点无关),但很容易完成。

另外请注意,我使用 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 代码(没有模式匹配)不会那么好,但也许有了新的模式匹配功能,它不会那么糟糕。

【讨论】:

    猜你喜欢
    • 2022-11-20
    • 2022-12-01
    • 2022-12-01
    • 2022-12-02
    • 2022-12-27
    • 2022-07-07
    • 2022-12-01
    • 2022-12-02
    • 2022-12-02
    相关资源
    最近更新 更多