I'm implementing a small DSL for a research project I'm involved with. Since this is the example for almost literally every explanation of GADTs out there, I decided that this would be a good time to really get started on using them. The obvious expressions (arithmetic, etc) worked out fine, but my DSL needs to support user-defined functions, which is where I ran into an issue.
With no GADTs, the structure of my expression type looked somewhat like this (condensed to a minimal example):
data Expr = IntConst Int
| BoolConst Bool
| Plus Expr Expr
| Times Expr Expr -- etc
| AndAlso Expr Expr -- etc
| Call String [Expr] -- function call!
Converted to GADTs, how can I express the Call
rule?
data Expr a where
IntConst :: Int -> Expr Int
BoolConst :: Bool -> Expr Bool
Plus :: Expr Int -> Expr Int -> Expr Int
Times :: Expr Int -> Expr Int -> Expr Int
AndAlso :: Expr Bool -> Expr Bool -> Expr Bool
Call :: ???
My first thought was that this is impossible without some super fancy dependent types, because there's no way to know what types a given user function will accept (also the return type, but I can fix that by making Call
return Expr a
and specializing at the construction site). I can force it to typecheck by "erasing" the type, by adding rules
EraseInt :: Expr Int -> Expr a
EraseBool :: Expr Bool -> Expr a
but then it seems that I lose the benefit of having GADTs in the first place. I was also thinking there might be some other rankN
polymorphism I could use in Call
(something something existential types?), but nothing I came up with seemed to work.
Help?