From Object Algebras to Finally Tagless Interpreters
oleksandrmanzyuk.wordpress.com
oleksandrmanzyuk.wordpress.com
Finally tagless style is an important architectural style for Haskell programming since it allows you to pass algebra joining up into the Prolog solver. This is more than adequate for simple combinations of algebras and workable (with a little elbow grease and commentary) for more complex ones.
Edit: Also, that boilerplate reduction on slide 30-32!
abstract Exp
immutable Lit <: Exp
value::Int
end
immutable Add <: Exp
left::Exp
right::Exp
end
evaluate(lit::Lit) = lit.value
evaluate(add::Add) = evaluate(add.left) + evaluate(add.right)
If you want to add a new operation, it's straightforward: view(lit::Lit) = string(lit.value)
view(add::Add) = string("(", view(add.left), " + ", view(add.right), ")")
If you want to add a new type of expression, it's also straightforward: immutable Mul <: Exp
left::Exp
right::Exp
end
evaluate(mul::Mul) = evaluate(mul.left) * evaluate(mul.right)
view(mul::Mul) = string("(", view(mul.left), " * ", view(mul.right), ")")
That's all there is to it. Of course, it's actually the external dispatch that's crucial, not the multiple dispatch. (But multiple part is very handy for other things, especially in numerical work).It seems like part of the benefits of the approaches described in the blog post is that the type system will catch incomplete extensions to the existing data or methods.
Scala has an "external" dispatch mechanism as well, basically a way to create new typed methods, namely pimping:
implicit class FooWrapper(foo: Foo) extends AnyVal {
def newMethod: Int = ...
}
val x: Foo = new Foo()
x.newMethod
These are all checked at compile time.Note that the example given above is actually not multiple dispatch, but merely adding methods to a type. It differs from function overloading (a Java/C++) feature only syntactically.
Multiple dispatch would be dispatch on multiple type arguments, not simply one (which Julia has, but Scala does not).
class Foo<X> { ... }
static class Foos {
static void DoFooInt(this Foo<int> self) { .... }
}
new Foo<int>().DoFooInt();
I built a whole pimped library called "Bling" for C#/WPF to lift values into signal form around this technique.Unfortunately, operators can't be externally extended in C#, so there is still a lot of wrapper creation, but extension methods can be used for that; e.g.
Signal<int> a = ..., b = ...;
var c = a.Bl() + b.Bl();
Bl is an extension method for signal ints that returns a wrapper around the argument that allows for access to the + method. Bl is overloaded for a variety of types to provide access to those wrappers.[1] http://en.wikipedia.org/wiki/Argument-dependent_name_lookup
– Fermat
In fact, if we sacrifice type-safety then the problem trivializes in a typed language too.
I think that over time, as you add new node types, you'd end up with lots of interface types, so you'd have to go back and clean them up after extending the language. But it will make the transition easier.
Also, real languages have multiple node types (expressions, statements, types, etc) that aren't interchangeable,so that might result in lots of type variables.