Also, there is a technique to alter ADTs, called tagless final.
ADT approach is called initial approach in some context, and tagless final is called the final approach in this scope.
For your code, we can use tagless final, to achieve stably typed Julia program:
struct SYM{F1, F2}
constant :: F1
add :: F2
end
function constant(v)
function (sym::SYM)
sym.constant(v)
end
end
function add(term1, term2)
function (sym::SYM)
sym.add(term1(sym), term2(sym))
end
end
# self algebra
self = SYM(constant, add)
evaluate =
let constant(v::Int) = v,
add(l::Int, r::Int) = l + r
SYM(constant, add)
end
println(add(constant(2), constant(3))(evaluate))
@code_warntype add(constant(2), constant(3))(evaluate)
There’re no red points, try above codes in your Julia shell
5
Variables
#self#::var"#17#18"{var"#15#16"{Int64},var"#15#16"{Int64}}
sym::Core.Compiler.Const(SYM{var"#constant#19",var"#add#20"}(var"#constant#19"(), var"#add#20"()), false)
Body::Int64
1 ─ %1 = Base.getproperty(sym, :add)::Core.Compiler.Const(var"#add#20"(), false)
│ %2 = Core.getfield(#self#, :term1)::var"#15#16"{Int64}
│ %3 = (%2)(sym)::Int64
│ %4 = Core.getfield(#self#, :term2)::var"#15#16"{Int64}
│ %5 = (%4)(sym)::Int64
│ %6 = (%1)(%3, %5)::Int64
└── return %6