Open expipiplus1 opened 1 year ago
It seems like defining a
Showable
class and writing instances forExpr
,Type
andKind
might be the "proper" way to go?
Yes, that's how I usually do it.
Another theoretical benefit of this approach is that if there was another expression language typed with Type
, or another type language kinded with Kind
, then you would have reused for the show'
implementations rather than repeating in the cases.
That being said, I can definitely see the ergonomic usefulness of your GADT approach. If there was a TH generator for it then one would need to do less work in cases like these and it would make using the library easier. It's kind of a flattened version of HRecWitness
, and reminds me of join
too. It would definitely be useful to have :)
I haven't tested this yet, but something along these lines should work, if the hypertypes internals were exposed then I think this could duplicate less logic
{-# INLINE withClosureWitness #-}
withClosureWitness ::
forall s t p a.
(Recursively (Closure s) s) =>
(ClosureWitness s t -> t # p -> a) ->
HRecWitness s t ->
t # p ->
a
withClosureWitness f = Proxy @(Closure s) ##>> f inClosure
type Closure :: HyperType -> HyperType -> Constraint
class Closure s t where
inClosure :: ClosureWitness s t
type ClosureWitness :: HyperType -> HyperType -> Type
data family ClosureWitness s t
deriveClosureWitness :: Name -> DecsQ
deriveClosureWitness n = do
ds <- hyperDepsRec n
let ws =
ds <&> \d ->
(d, mkName ("CW_" <> nameBase n <> "_" <> nameBase d))
is <-
join
<$> for ws \(d, w) -> [d|instance Closure $(conT n) $(conT d) where inClosure = $(conE w)|]
g <- do
a <- newName "a"
let t = DConT ''ClosureWitness `DAppT` DConT n `DAppT` DVarT a
cs =
ws <&> \(d, w) ->
let ct = DConT ''ClosureWitness `DAppT` DConT n `DAppT` DConT d
in DCon [] [] w (DNormalC False []) ct
pure $ DDataInstD Data [] Nothing t Nothing cs []
pure (sweeten [g] <> is)
-- All the child hypertypes
hyperDepsRec :: Name -> Q [Name]
hyperDepsRec = closeM' hyperDeps
hyperDeps :: Name -> Q [Name]
hyperDeps n = do
d <-
dsReify n >>= \case
Just (DTyConI d _) -> pure d
Just i -> fail ("Expected a type, but " <> show n <> " is a " <> dInfoSummary i)
Nothing -> fail ("Couldn't reify " <> show n)
case d of
DDataD _ _ _ vs _ cons _ -> do
h <-
maybe
(fail (show n <> " doesn't have any type variables, is it a hypertype?"))
( \case
DPlainTV v _ -> pure v
DKindedTV v _ _ -> pure v
)
(lastMay vs)
let ts = conFieldTypes =<< cons
let hs = getHyperTypes h =<< ts
pure hs
_ -> fail (show n <> " isn't a data type")
-- The hypertypes contained in a type, for example (Either (h :# A) (h :# B))
-- contains A and B
getHyperTypes :: Name -> DType -> [Name]
getHyperTypes h = go
where
go =
\case
DConT hash `DAppT` DVarT h' `DAppT` DConT t
| hash == ''(:#), h' == h -> pure t
DForallT _ t -> go t
DConstrainedT _ t -> go t
DAppT t1 t2 -> go t1 <> go t2
DAppKindT t _ -> go t
DSigT t _ -> go t
DVarT _ -> []
DConT _ -> []
DArrowT -> []
DLitT _ -> []
DWildCardT -> []
conFieldTypes :: DCon -> [DType]
conFieldTypes (DCon _ _ _ f _) = case f of
DNormalC _ ts -> ts ^.. each . _2
DRecC vs -> vs ^.. each . _3
dInfoSummary :: DInfo -> String
dInfoSummary = \case
DTyConI{} -> "type"
DVarI{} -> "value"
DTyVarI{} -> "type variable"
DPrimTyConI{} -> "primitive type"
DPatSynI{} -> "pattern cinnamon"
--
-- Utils
--
-- Reflexive, transitive closure, on lists
closeM' :: (Ord a, Monad m) => (a -> m [a]) -> a -> m [a]
closeM' f = fmap Set.toList . closeM (fmap Set.fromList . f)
-- Reflexive, transitive closure
closeM :: (Ord a, Monad m) => (a -> m (Set a)) -> a -> m (Set a)
closeM f x = go (Set.singleton x) =<< f x
where
go seen next | Set.null next = pure seen
go seen next = do
novel <-
(`Set.difference` seen)
. Set.unions
<$> traverse f (toList next)
go (seen <> next) novel
data Foo h = Foo Int (Either (h :# Baz) (h :# Bar))
data Bar h = Bar Int (h :# Foo)
data Baz h = Baz Int (h :# Foo)
-- deriveClosureWitness ''Foo
data instance ClosureWitness Foo a_a8mpv
where
CW_Foo_Bar :: ClosureWitness Foo Bar
CW_Foo_Baz :: ClosureWitness Foo Baz
CW_Foo_Foo :: ClosureWitness Foo Foo
instance Closure Foo Bar where
inClosure = CW_Foo_Bar
instance Closure Foo Baz where
inClosure = CW_Foo_Baz
instance Closure Foo Foo where
inClosure = CW_Foo_Foo
Another theoretical benefit of this approach is that if there was another expression language typed with Type, or another type language kinded with Kind, then you would have reused for the show' implementations rather than repeating in the cases.
Yeah, this is a strong motivation for doing it that way indeed
Take for example, this language of literals, type-annotated expressions and kind annotated types.
If we want to perform a fold over an
Expr
we have to do a little dance withHRecWitness
, deconstructing it recursively.One can mechanically derive a GADT representing the closure of the
HWitness
relation, which makes defining these folds much more pleasant:Does this construct have a place in
hypertypes
, or is this actually quite an unidiomatic way of performing such a fold?It seems like defining a
Showable
class and writing instances forExpr
,Type
andKind
might be the "proper" way to go? Something like: