The first step is to separate the parts that are specific to each view ( Var , Lam , Let ) from the rest of the definitions.
data AbstractTerm β· Type β β
where App β· AbstractTerm (a :-> b) β AbstractTerm a β AbstractTerm b Tup β· AbstractTerm a β AbstractTerm b β AbstractTerm (a :*: b) Lft β· AbstractTerm a β AbstractTerm (a :+: b) Rgt β· AbstractTerm b β AbstractTerm (a :+: b) Tru, Fls β· AbstractTerm Boolean Uni β· AbstractTerm Unit data Term β· Type β β
where Var β· Id β Term a Lam β· Id β Type β Term b β Term (a :-> b) Let β· Id β Term a β Term b β Term b data TermIn β· Type β β
where VarI β· Idx β Info β TermIn a LamI β· Type β TermIn b β Info β TermIn (a :-> b) LetI β· TermIn a β TermIn b β Info β TermIn b
Then you need to combine the βcommon part with the specific representation you want. Thereβs a well-known trick for creating inductive definitions into smaller pieces: you will reorganize the inductive call from the data type, making the sub-element types a parameter for the type (in this case, a function over the type of the type , since you need to track the type of object-object).
data AbstractTerm (t β· Type β β
) β· Type β β
where App β· t (a :-> b) β ta β AbstractTerm tb Tup β· ta β tb β AbstractTerm t (a :*: b) Lft β· ta β AbstractTerm t (a :+: b) Rgt β· tb β AbstractTerm t (a :+: b) Tru, Fls β· AbstractTerm t Boolean Uni β· AbstractTerm t Unit data Term (t β· Type β β
) β· Type β β
where Var β· Id β Term ta Lam β· Id β Type β tb β Term t (a :-> b) Let β· Id β ta β tb β Term tb data TermIn (t β· Type β β
) β· Type β β
where VarI β· Idx β Info β TermIn ta LamI β· Type β tb β Info β TermIn t (a :-> b) LetI β· ta β tb β Info β TermIn tb
To create an instance of this type, you can use an inductive type definition that sums with an abstract type to get a parameter that gives an abstract type.
newtype ConcreteTermNamed ty = ConcreteTermNamed (Either (Term ConcreteTermNamed ty) (AbstractTerm ConcreteTermNamed ty)) newtype ConcreteTermNameless ty = ConcreteTermNameless (Either (TermIn ConcreteTermNameless ty) (AbstractTerm ConcreteTermNameless ty))
This adds a little extra buzz to choose whether you want to be an agnostic or -specific term at each level, as well as the newtype shell assigned by Haskell, but otherwise leaves your terminology as it is.
var β· ConcreteTermNamed (Boolean :*: Unit) var = ConcreteTermNamed (Right (Tup (ConcreteTermNamed (Left (Var "x"))) (ConcreteTermNamed (Left (Var "y"))))) fun β· ConcreteTermNamed (Boolean :-> (Unit :*: Boolean)) fun = ConcreteTermNamed (Left (Lam "b" Boolean (ConcreteTermNamed (Right (Tup (ConcreteTermNamed (Right Uni)) (ConcreteTermNamed (Left (Var "b"))))))))
This trick can be used to summarize any number of different inductive types and is often used to break up a larger language into smaller, more modular sublanguages; for example, it might be a good style to further divide your AbstractTerm into application types, products, amounts, and units, and then combine them all together by adding them to the amount type.