In section 2.3 of these really cool notes on tagless final interpreters for DSLs, Oleg Kiselyov shows how to solve the problem of parsing a serialized DSL expression once, and interpreting it multiple times.
Briefly, he shows that "fake first-class polymorphism" with the types
newtype Wrapped = Wrapped (∀ repr. ExpSYM repr ⇒ repr)
fromTree :: String → Either ErrMsg Wrapped
is not satisfactory because it is not extensible: we have to have a different Wrapper
/fromTree
for every set of constraints on repr
. Thus I'm inclined to use his solution of a duplicator interpreter. This question is about how to use that interpreter with HOAS.
Specifically, consider the following language for target language bindings:
class Lam repr where
lam :: (repr a -> repr b) -> repr (a -> b)
app :: repr (a -> b) -> repr a -> repr b
I'm having trouble giving a sound instance of the Lam
class for my duplicator interpreter. Here's what I have:
data Dup repr1 repr2 a = Dup {unDupA :: repr1 a, unDupB :: repr2 a}
instance (Lam repr1, Lam repr2) => Lam (Dup repr1 repr2) where
lam f = Dup (lam $ unDupA . f . flip Dup undefined) (lam $ unDupB . f . Dup undefined)
app (Dup fa fb) (Dup a b) = Dup (app fa a) (app fb b)
Is there some way to give a recursive instance of Lambda
for something like my Dup
type that doesn't involve undefined
?
I've also tried using the more powerful version of lam
from this paper, which permits monadic interpreters with HOAS, though I didn't see how it would help me with my instance for Dup
. A solution using either version of lam
with HOAS would be great!
*: Oleg showed how to define a sound instance using de Bruijn indices, but I'm really interested in a solution for HOAS.
class Lam repr where
lam :: repr (a,g) b -> repr g (a -> b)
app :: repr g (a->b) -> repr g a -> repr g b
data Dup repr1 repr2 g a = Dup{d1:: repr1 g a, d2:: repr2 g a}
instance (Lam repr1, Lam repr2) => Lam (Dup repr1 repr2) where
lam (Dup e1 e2) = Dup (lam e1) (lam e2)
app (Dup f1 f2) (Dup x1 x2) = Dup (app f1 x1) (app f2 x2)