On Thursday, 18 February 2021 at 13:53:19 UTC, Paul Backus wrote:
On Thursday, 18 February 2021 at 11:14:05 UTC, Mina wrote:
I'm following along with the crafting interpreters book
(https://craftinginterpreters.com) and it goes into
implementing a visitor pattern that returns generic types, so
implementing it in D came down to the accept method causing
undefined symbol error that goes away when changing it to
returning a concrete type, so here's what I've got working
(https://github.com/MKamelll/dlox/blob/main/source/loxast.d)
and here's the book's implementation
(https://github.com/munificent/craftinginterpreters/blob/master/java/com/craftinginterpreters/lox/Expr.java).
Thanks.
In D, because generics are implemented using templates
("monomorphization"), generic methods can't be virtual and
can't be overridden in child classes. As you've discovered,
that means `accept` has to work entirely with concrete types
rather than generic ones.
One way to solve this (which is used in the D compiler's source
code) is to have both `accept` and `visit` return `void` and
put the result inside the visitor object as a member variable.
For example:
interface Visitor
{
void visit(Expr.Literal expr);
// etc.
}
class AstPrinter : Visitor
{
string result;
override void visit(Expr.Literal expr)
{
if (!expr.literal.hasValue) result = "nil";
else result = lexLiteralStr(expr.literal);
}
// etc.
string print(Expr expr)
{
expr.accept(this);
return result;
}
}
Another possibility is to use discriminated unions and
tag-based dispatch (i.e., switch statements) instead of classes
and virtual method dispatch. This would make it a bit harder to
follow the book, but might be a better learning experience if
you're up for a challenge.
Or combination of discriminate uninons and classes:
/+dub.sdl:
dependency "sumtype" version="~>0.10.0"
+/
import std.stdio;
import sumtype;
alias Expression = SumType!(
ExprValue,
ExprBinary,
ExprUnary
);
class Expr{
abstract Expression expression()pure nothrow @safe @nogc;
}
class ExprValue : Expr{
string val;
override Expression expression()pure nothrow @safe @nogc{
return Expression(this);
}
this(string val)pure{
this.val = val;
}
}
class ExprBinary : Expr{
string op;
Expr left;
Expr right;
override Expression expression()pure nothrow @safe @nogc{
return Expression(this);
}
this(string op, Expr left, Expr right)pure{
this.op = op;
this.left = left;
this.right = right;
}
}
class ExprUnary : Expr{
string op;
Expr expr;
override Expression expression()pure nothrow @safe @nogc{
return Expression(this);
}
this(string op, Expr expr)pure{
this.op = op;
this.expr = expr;
}
}
string printExpr(Expr expr){
assert(expr !is null);
static auto impl(E)(E e){
static if(is(E == ExprValue)){
return e.val;
}
else static if(is(E == ExprUnary)){
return e.op ~ printExpr(e.expr);
}
else static if(is(E == ExprBinary)){
return printExpr(e.left) ~ e.op ~ printExpr(e.right);
}
else static assert(0, "no impl");
}
return expr.expression.match!impl;
}
void main(){
// (1 + (- 2 ))
Expr expr = new ExprBinary(
"+",
new ExprValue("1"),
new ExprUnary(
"-",
new ExprValue("2")
)
);
writeln(expr.printExpr());
}