Skip to content

Commit 69862de

Browse files
Merge pull request #114 from chrisdone/cd/2026-04-15-entailment
Support restricted form of entailment: C a => C (t a) (e.g. Eq a => Eq [a])
2 parents 2ec76ec + e27f26e commit 69862de

2 files changed

Lines changed: 134 additions & 31 deletions

File tree

examples/15-type-classes.hell

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
main = do
22
Text.putStrLn (Show.show 123)
33
Text.putStrLn (Show.show Bool.True)
4+
Text.putStrLn $ Show.show $ Eq.eq 1 1
5+
Text.putStrLn $ Show.show $ Eq.eq [Maybe.Just 1] [Maybe.Just 2]
6+
IO.print [Maybe.Just 1, Maybe.Nothing]
7+
IO.print $ Maybe.Just [1] <> Maybe.Nothing
48

59
env <- Environment.getEnvironment
610
Maybe.maybe

src/Hell.hs

Lines changed: 130 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -726,7 +726,6 @@ tc (UForall _ forallLoc _ _ fall _ _ reps0) _env = go reps0 fall
726726
go [] (Term typed') = pure typed'
727727
go (SomeTypeRep rep : reps) (Forall sym f)
728728
| Just Type.HRefl <- Type.eqTypeRep (typeRepKind rep) sym = go reps (f rep)
729-
-- Cases that look like: Monad (Either (a :: Type) :: Type -> Type)
730729
go reps (ClassConstraint rep crep f) =
731730
withClassConstraint forallLoc reps rep crep f go
732731
go reps fa@(GetOf k0 a0 t0 r0 f) =
@@ -768,30 +767,9 @@ withClassConstraint ::
768767
([SomeTypeRep] -> Forall -> Either TypeCheckError (Typed (Term g))) ->
769768
Either TypeCheckError (Typed (Term g))
770769
withClassConstraint forallLoc reps rep crep f go =
771-
if
772-
-- Cases that look like: Semigroup (Vector (e :: *))
773-
-- Note: the kinds are limited to this exact specification in the signature above.
774-
| Type.App t _ <- rep,
775-
Just Type.HRefl <- Type.eqTypeRep (typeRepKind t) (TypeRep @(Type -> Type)),
776-
Just dict <- resolve1 (Type.App crep rep) crep t instances ->
777-
go reps (withDict dict f)
778-
-- Cases that look like: Monad (Either (e :: *) (a :: *))
779-
-- Note: the kinds are limited to this exact specification in the signature above.
780-
| Type.App t _ <- rep,
781-
Just Type.HRefl <- Type.eqTypeRep (typeRepKind t) (TypeRep @(Type -> Type -> Type)),
782-
Just dict <- resolve1 (Type.App crep rep) crep t instances ->
783-
go reps (withDict dict f)
784-
-- Cases that look like: Semigroup (Mod (f :: * -> *) (a :: *))
785-
-- Note: the kinds are limited to this exact specification in the signature above.
786-
| Type.App (Type.App t _a) _b <- rep,
787-
Just Type.HRefl <- Type.eqTypeRep (typeRepKind t) (TypeRep @((Type -> Type) -> Type -> Type)),
788-
Just dict <- resolve2 (Type.App crep rep) crep t instances ->
789-
go reps (withDict dict f)
790-
-- Simple cases: Eq (a :: k)
791-
| Just dict <- resolve crep rep instances ->
792-
go reps (withDict dict f)
793-
| otherwise ->
794-
problem $
770+
case lookupDict rep crep of
771+
Just dict -> go reps (withDict dict f)
772+
Nothing -> problem $
795773
"type "
796774
++ show rep
797775
++ " doesn't appear to be an instance of "
@@ -800,20 +778,60 @@ withClassConstraint forallLoc reps rep crep f go =
800778
problem :: forall x. String -> Either TypeCheckError x
801779
problem = Left . ConstraintResolutionProblem forallLoc (ClassConstraint rep crep f)
802780

781+
-- The workhorse behind withClassConstraint. See documentation there.
782+
lookupDict ::
783+
forall g k (c :: k -> Constraint) (a :: k).
784+
TypeRep a ->
785+
TypeRep c ->
786+
Maybe (Dict (c a))
787+
lookupDict rep crep =
788+
if
789+
-- Cases that look like: Semigroup (Vector (e :: *))
790+
-- Note: the kinds are limited to this exact specification in the signature above.
791+
| Type.App t _ <- rep,
792+
Just Type.HRefl <- Type.eqTypeRep (typeRepKind t) (TypeRep @(Type -> Type)),
793+
Just dict <- resolve1 (Type.App crep rep) crep t instances ->
794+
pure dict
795+
-- Cases that look like: Monad (Either (e :: *) (a :: *))
796+
-- Note: the kinds are limited to this exact specification in the signature above.
797+
| Type.App t _ <- rep,
798+
Just Type.HRefl <- Type.eqTypeRep (typeRepKind t) (TypeRep @(Type -> Type -> Type)),
799+
Just dict <- resolve1 (Type.App crep rep) crep t instances ->
800+
pure dict
801+
-- Cases that look like: Semigroup (Mod (f :: * -> *) (a :: *))
802+
-- Note: the kinds are limited to this exact specification in the signature above.
803+
| Type.App (Type.App t _a) _b <- rep,
804+
Just Type.HRefl <- Type.eqTypeRep (typeRepKind t) (TypeRep @((Type -> Type) -> Type -> Type)),
805+
Just dict <- resolve2 (Type.App crep rep) crep t instances ->
806+
pure dict
807+
-- Simple cases: Eq (a :: k)
808+
| otherwise ->
809+
resolve crep rep instances
810+
803811
--------------------------------------------------------------------------------
804812
-- Instances
805813

814+
-- Dict but for (t :: * -> *), like Monad []
806815
newtype D1 c t = D1 (forall e. Dict (c (t e)))
807816

817+
-- Dict but for (t :: * -> * -> *), like Monad (Either e)
808818
newtype D2 c t = D2 (forall f a. Dict (c (t f a)))
809819

810-
newtype Instances = Instances (Map (SomeTypeRep, SomeTypeRep) Dynamic)
820+
-- Entailment, c a => c (t a), E.g. Eq a :- Eq [a]
821+
newtype ED1 c t = ED1 (forall e. c e :- c (t e))
822+
823+
newtype Instances = Instances {getInstances ::Map (SomeTypeRep, SomeTypeRep) Dynamic}
811824

812825
instances :: Instances
813826
instances =
814827
Instances $
815828
Map.fromList
816-
[ instance0 @Show @Int,
829+
[ entail1 @Show @[],
830+
entail1 @Show @Set,
831+
entail1 @Show @Tree,
832+
entail1 @Show @Maybe,
833+
entail1 @Show @Vector,
834+
instance0 @Show @Int,
817835
instance0 @Show @Integer,
818836
instance0 @Show @Day,
819837
instance0 @Show @UTCTime,
@@ -824,6 +842,12 @@ instances =
824842
instance0 @Show @Text,
825843
instance0 @Show @ByteString,
826844
instance0 @Show @ExitCode,
845+
instance0 @Show @Value,
846+
entail1 @Eq @[],
847+
entail1 @Eq @Set,
848+
entail1 @Eq @Maybe,
849+
entail1 @Eq @Tree,
850+
entail1 @Eq @Vector,
827851
instance0 @Eq @Int,
828852
instance0 @Eq @Integer,
829853
instance0 @Eq @Day,
@@ -835,6 +859,11 @@ instances =
835859
instance0 @Eq @Text,
836860
instance0 @Eq @ByteString,
837861
instance0 @Eq @ExitCode,
862+
entail1 @Ord @[],
863+
entail1 @Ord @Set,
864+
entail1 @Ord @Maybe,
865+
entail1 @Ord @Tree,
866+
entail1 @Ord @Vector,
838867
instance0 @Ord @Int,
839868
instance0 @Ord @Integer,
840869
instance0 @Ord @Day,
@@ -865,6 +894,7 @@ instances =
865894
instance1 @Applicative @Either,
866895
instance0 @Alternative @Options.Parser,
867896
instance0 @Alternative @Maybe,
897+
entail1 @Semigroup @Maybe,
868898
instance0 @Monoid @Text,
869899
instance1 @Monoid @Vector,
870900
instance2 @Monoid @Options.Mod,
@@ -896,6 +926,30 @@ instance1 =
896926
toDyn $ D1 @c @t Dict
897927
)
898928

929+
-- A very restricted kind of entailment: C a => C (t a)
930+
-- This serves:
931+
-- Eq a => Eq [a], Ord a => Ord (Maybe [a]), etc.
932+
--
933+
-- Lookup process:
934+
-- class = Ord
935+
-- type = Maybe [Int]
936+
-- find (Ord,Maybe)
937+
-- recurse
938+
-- find (Ord,[])
939+
-- recurse
940+
-- find (Ord,Int)
941+
-- ==> Ord Int
942+
-- ==> Ord [Int]
943+
-- ==> Ord (Maybe [Int])
944+
entail1 ::
945+
forall {k1} (c :: k1 -> Constraint) (t :: k1 -> k1).
946+
((forall a. c a => c (t a)), Typeable c, Typeable t, Typeable k1) =>
947+
((SomeTypeRep, SomeTypeRep), Dynamic)
948+
entail1 =
949+
( (SomeTypeRep $ typeRep @c, SomeTypeRep $ typeRep @t),
950+
toDyn $ ED1 @c @t (Sub Dict)
951+
)
952+
899953
instance2 ::
900954
forall {k0} {k1} {k2} (c :: k2 -> Constraint) (t :: k0 -> k1 -> k2).
901955
((forall a b. c (t a b)), Typeable c, Typeable t, Typeable k0, Typeable k1, Typeable k2) =>
@@ -924,11 +978,21 @@ resolve1 ::
924978
TypeRep t ->
925979
Instances ->
926980
Maybe (Dict (c (t a)))
927-
resolve1 _ c t (Instances m) = do
981+
resolve1 cta c t (Instances m) = do
928982
Dynamic rep dict <- Map.lookup (SomeTypeRep c, SomeTypeRep t) m
929-
Type.HRefl <- Type.eqTypeRep rep $ Type.App (Type.App (typeRep @D1) c) t
930-
let D1 d = dict
931-
pure d
983+
(do Type.HRefl <- Type.eqTypeRep rep $ Type.App (Type.App (typeRep @D1) c) t
984+
let D1 d = dict
985+
pure d) <|>
986+
-- When we see e.g. C (T A), where T A and A have the same kind,
987+
-- we can lookup C A, for the entailment C A :- C (T A).
988+
(do case cta of
989+
Type.App _c a@(Type.App f a') -> do
990+
Type.HRefl <- Type.eqTypeRep (typeRepKind a') (typeRepKind a)
991+
Type.HRefl <- Type.eqTypeRep rep $ Type.App (Type.App (typeRep @ED1) c) f
992+
let ED1 entailment = dict
993+
dictA <- lookupDict a' c
994+
pure $ mapDict entailment dictA
995+
_ -> Nothing)
932996

933997
-- Resolve an instance of the form: Monoid (Mod f a)
934998
resolve2 ::
@@ -2996,6 +3060,9 @@ _generateApiDocs = do
29963060
let excludeHidden = filter (not . List.isPrefixOf "hell:Hell." . fst)
29973061
ul_ do
29983062
for_ (excludeHidden $ Map.toList supportedTypeConstructors) typeConsToHtml
3063+
h2_ "Instances"
3064+
ul_ do
3065+
for_ (Map.toList instances.getInstances) instToHtml
29993066
h2_ "Terms"
30003067
let groups =
30013068
excludeHidden $
@@ -3052,6 +3119,38 @@ makeSearchIndex = Json.Array $ typeConstructorsIndex <> litsIndex <> polysIndex
30523119
nameToElementId :: String -> Text
30533120
nameToElementId = Text.pack
30543121

3122+
instToHtml :: ((SomeTypeRep, SomeTypeRep), Dynamic) -> Html ()
3123+
instToHtml ((cls', ty), dyn') =
3124+
li_ [id_ (nameToElementId name), class_ "searchable"] $ do
3125+
code_ do
3126+
em_ "instance "
3127+
when entailed do
3128+
strong_ do
3129+
"("
3130+
toHtml $ show cls'
3131+
" a) => "
3132+
strong_ $ toHtml $ show cls'
3133+
" "
3134+
if entailed || foralld
3135+
then strong_ do
3136+
"("
3137+
toHtml $ show ty
3138+
" a)"
3139+
else
3140+
if foralld2
3141+
then strong_ do
3142+
"("
3143+
toHtml $ show ty
3144+
" a b)"
3145+
else
3146+
strong_ $ toHtml $ show ty
3147+
3148+
where name = show cls' ++ " " ++ show ty
3149+
-- TODO: Use the types rather than this hack.
3150+
entailed = Text.isPrefixOf "<<ED1" (Text.pack (show dyn'))
3151+
foralld = Text.isPrefixOf "<<D1" (Text.pack (show dyn'))
3152+
foralld2 = Text.isPrefixOf "<<D2" (Text.pack (show dyn'))
3153+
30553154
typeConsToHtml :: (String, SomeTypeRep) -> Html ()
30563155
typeConsToHtml (name, SomeTypeRep rep) =
30573156
li_ [id_ (nameToElementId name), class_ "searchable"] $ do

0 commit comments

Comments
 (0)