Is there an easy way to mimic generics with an accept method of a visitor pattern?

vitamin vit at vit.vit
Thu Feb 18 14:26:37 UTC 2021


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());


}


More information about the Digitalmars-d-learn mailing list