Skip to content

Instantly share code, notes, and snippets.

@tel
Forked from gallais/MutuallDefined.hs
Last active April 20, 2023 06:34
Show Gist options
  • Save tel/99e666308d270a3d1d8c to your computer and use it in GitHub Desktop.
Save tel/99e666308d270a3d1d8c to your computer and use it in GitHub Desktop.
Mutually defined data types with recursion principle
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeOperators #-}
module MutuallyDefined where
import Data.Maybe (fromJust)
-- Really we want to only quantify over whatever the kind of the
-- argument to f and g is, but I don't think there's a sane way to do
-- that in Haskell.
newtype f ~> g = Nat { app :: forall i . f i -> g i }
class Functor1 k where
fmap1 :: (f ~> g) -> (k f ~> k g)
newtype Tie (f :: (k -> *) -> (k -> *)) (i :: k) =
Tie { unTie :: f (Tie f) i }
tie :: Functor1 k => (k r ~> r) -> (Tie k ~> r)
tie phi = go where go = Nat $ app phi . app (fmap1 go) . unTie
--------------------------------------------------------------------------------
newtype Zero f x (i :: ()) = Zero { unZero :: f (x i) }
newtype Tie0 (f :: * -> *) = Tie0 { unTie0 :: Tie (Zero f) '() }
tie0 :: Functor f => (f r -> r) -> (Tie0 f -> r)
tie0 phi = phi . fmap (tie0 phi . Tie0) . unZero . unTie . unTie0
--------------------------------------------------------------------------------
data Count = One | Many
data ForestF l n (x :: Count -> *) (idx :: Count) where
Leaf :: l -> ForestF l n x One
Node :: n -> x Many -> ForestF l n x One
Nil :: ForestF l n x Many
Cons :: x One -> x Many -> ForestF l n x Many
instance Functor1 (ForestF l n) where
fmap1 f = Nat $ \case
Leaf l -> Leaf l
Node n a -> Node n (app f a)
Nil -> Nil
Cons a1 a2 -> Cons (app f a1) (app f a2)
type Tree l n = Tie (ForestF l n) One
type Forest l n = Tie (ForestF l n) Many
leaf :: l -> Tree l n
leaf = Tie . Leaf
node :: n -> Forest l n -> Tree l n
node n = Tie . Node n
nil :: Forest l n
nil = Tie Nil
cons :: Tree l n -> Forest l n -> Forest l n
cons a x = Tie (Cons a x)
newtype K a b = K { getK :: a } deriving (Show, Functor)
count :: ForestF l n (K Int) ~> K Int
count = Nat $ \case
Leaf _ -> K 1
Node _ (K n) -> K (n + 1)
Nil -> K 0
Cons (K n) (K m) -> K (n + m)
--------------------------------------------------------------------------------
data Ty = Bool | Arr Ty Ty deriving (Eq, Show)
data Hypothesis = Infer | Check
data ExprF (x :: Hypothesis -> *) (i :: Hypothesis) where
Var :: Int -> ExprF x Infer
App :: x Infer -> x Check -> ExprF x Infer
Annot :: x Check -> Ty -> ExprF x Infer
If :: x Check -> x Infer -> x Infer -> ExprF x Infer
ETrue :: ExprF x Infer
EFalse :: ExprF x Infer
Lam :: x Check -> ExprF x Check
CI :: x Infer -> ExprF x Check
instance Functor1 ExprF where
fmap1 phi = go where
go = Nat $ \case
Var i -> Var i
App f a -> App (app phi f) (app phi a)
Annot e t -> Annot (app phi e) t
If b t e -> If (app phi b) (app phi t) (app phi e)
ETrue -> ETrue
EFalse -> EFalse
Lam e -> Lam (app phi e)
CI e -> CI (app phi e)
type Expr = Tie ExprF
data Val = VTrue
| VFalse
| VLam (Val -> Val)
vapp :: Val -> Val -> Val
vapp (VLam f) = f
seek :: Int -> [a] -> Maybe a
seek n [] = Nothing
seek 0 (x : _) = Just x
seek n (_ : xs) = seek (n-1) xs
eval1 :: ExprF (K ([Val] -> Val)) ~> K ([Val] -> Val)
eval1 = Nat $ \x -> K $ \env -> case x of
Var i -> fromJust (seek i env)
App f a -> getK f env `vapp` getK a env
Annot e _ -> getK e env
If b t e -> case getK b env of
VTrue -> getK t env
VFalse -> getK e env
ETrue -> VTrue
EFalse -> VFalse
Lam e -> VLam $ \v -> getK e (v:env)
CI e -> getK e env
evalOpen :: [Val] -> Expr i -> Val
evalOpen env e = getK (tie eval1 `app` e) env
eval :: Expr i -> Val
eval = evalOpen []
@tel
Copy link
Author

tel commented Nov 27, 2014

Now if I could only figure out a way to push the bidirectional type checker through. It would probably require putting a typeclass constraint on the index quantified over with (~>).

@gallais
Copy link

gallais commented Nov 27, 2014

You could go Nat-indexed for a better-structured definition of Tie (see updated gist) but you are giving up on the coverage checking as a side effect. :/ Might be worth investigating type-level lists à la @jonsterling's Vinyl too.

Your comment about typechecking reminded me of Brian McKenna's blog post about type inference using the cofree-comonad.

@gallais
Copy link

gallais commented Nov 27, 2014

I could not resist doing the example with type-level lists: https://gist.github.com/gallais/d125853cea257f051578

@tel
Copy link
Author

tel commented Nov 29, 2014

Haha, once you go higher-typed you don't go back.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment