I wanted to emulate OOP-style decorators for open recursion and found it surprisingly difficult.
The core idea is this: There is some vtable which contains a bunch of functions, and each function receives this vtable as argument. Decorators are vtable transformers and add or alter some behavior. They can dispatch to their wrapped vtable or the fixpoint vtable, which is written as super
and this
in most OOP languages.
So far this is very similar to mtl style typeclasses and transformers. Each typeclass is a vtable, each transformer is a decorator, lift calls the parent implementation.
But monad transformers do not have the vtable argument. If we lift through a decorator and the parent class contains a recursive call we do not consider the decorator in nested calls.
One approach I have seen before is to reify the vtable, but this tends to optimize a lot worse and composes badly unless we also use some generic record library.
data ListPrinter a = LP {
plist :: ListPrinter a -> [a] -> String,
pelem :: ListPrinter a -> a -> String
}
pDec :: ListPrinter Int -> ListPrinter Int
pDec super = LP {
plist = \r xs -> plist super r xs,
pelem = \r d -> if d == 0 then "ZERO" else pelem super r d
}
pList :: (a -> String) -> ListPrinter a
pList f = LP {
plist = \r xs -> case xs of
[] -> "[]"
(x:xs) -> pelem r r x <> " : " <> plist r r xs,
pelem = \_ -> f
}
Typeclass decorators essentially would need two super-classes, one for the parent and one for the root/this
. But I haven’t found a way to dispatch in a low-weight way, essentially simulating the distinction of super vs this in OOP languages. The approaches I considered are pretty awkward, either boilerplate heavy or using complex type familiies/overlapping instances/implicit params for instance selection.
Is there a known way to do this nicely using type classes rather than structs of functions?