Skip to content

Commit 5aabb11

Browse files
Merge pull request #110 from chrisdone/cd/2025-11-10-primcase
Support case expressions for primitive types (Maybe, Either, etc)
2 parents fffa846 + 9924f63 commit 5aabb11

File tree

3 files changed

+242
-14
lines changed

3 files changed

+242
-14
lines changed

examples/25-sum-types.hell

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,12 @@ main = do
1414
Good -> "Good!"
1515
Bad -> "Bad!"
1616
Ugly -> "Ugly!"
17+
Text.putStrLn $ case Main.Bad of
18+
Good -> "Good!"
19+
Bad -> "Bad!"
20+
_ -> "Ugly!"
21+
-- Wildcard
22+
Text.putStrLn $ case Main.Ugly of
23+
Good -> "Good!"
24+
Bad -> "Bad!"
25+
_ -> "Ugly!"

examples/42-primcase.hell

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
main = do
2+
let maybe = \i -> case i of
3+
Maybe.Just x -> IO.print x
4+
Maybe.Nothing -> Text.putStrLn "nope"
5+
maybe Maybe.Nothing
6+
maybe $ Maybe.Just 1
7+
8+
let either = \i -> case i of
9+
Either.Left x -> IO.print x
10+
Either.Right y -> Text.putStrLn y
11+
either $ Either.Left 1
12+
either $ Either.Right "abc"
13+
14+
let exitCode = \i -> case i of
15+
Exit.ExitSuccess -> Text.putStrLn "Success!"
16+
Exit.ExitFailure y -> IO.print y
17+
exitCode $ Exit.ExitSuccess
18+
exitCode $ Exit.ExitFailure 1
19+
20+
let bool = \i -> case i of
21+
Bool.True -> Text.putStrLn "True!"
22+
Bool.False -> Text.putStrLn "False!"
23+
bool $ Bool.True
24+
bool $ Bool.False
25+
26+
let these = \i -> case i of
27+
These.This x -> IO.print x
28+
These.That y -> Text.putStrLn y
29+
These.These x y -> do IO.print x; Text.putStrLn y
30+
these $ These.This 1
31+
these $ These.That "abc"
32+
these $ These.These 1 "abc"
33+
34+
let value = Function.fix \value i -> case i of
35+
Json.Null -> Text.putStrLn "null!"
36+
Json.Bool y -> IO.print (y :: Bool)
37+
Json.String x -> IO.print (x :: Text)
38+
Json.Number n -> IO.print (n :: Double)
39+
Json.Array a -> IO.forM_ (Vector.toList a) value
40+
Json.Object m -> IO.forM_ (Map.toList m) \(k,v) -> do
41+
Text.putStrLn $ "key: " <> k
42+
value v
43+
value $ Json.Null
44+
value $ Json.Bool Bool.True
45+
value $ Json.String "abc"
46+
value $ Json.Number 123.0
47+
value $ Json.Array $ Vector.fromList [Json.String "vec string"]
48+
value $ Json.Object $ Map.fromList [("k",Json.String "v")]
49+
50+
let bool = Function.fix \bool i ->
51+
case i of
52+
Json.Bool y -> IO.print (y :: Bool)
53+
_ -> Text.putStrLn "Something else."
54+
bool $ Json.Null
55+
bool $ Json.Number 123.0
56+
bool $ Json.Bool Bool.True

src/Hell.hs

Lines changed: 177 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
{-# LANGUAGE DeriveFoldable #-}
77
{-# LANGUAGE DeriveFunctor #-}
88
{-# LANGUAGE DeriveTraversable #-}
9-
{-# LANGUAGE ExistentialQuantification #-}
9+
{-# LANGUAGE ExistentialQuantification, DuplicateRecordFields, NoFieldSelectors #-}
1010
{-# LANGUAGE ExtendedDefaultRules #-}
1111
{-# LANGUAGE FlexibleContexts #-}
1212
{-# LANGUAGE FlexibleInstances #-}
@@ -1105,21 +1105,61 @@ desugarExp userDefinedTypeAliases globals = go mempty
11051105
HSE.RecConstr _ qname fields -> go scope $ makeConstructRecord qname fields
11061106
e -> Left $ UnsupportedSyntax $ show e
11071107

1108+
-- | Handles both user-defined case and primitive type case (Maybe, Either, etc.)
1109+
desugarCase
1110+
:: HSE.SrcSpanInfo
1111+
-> HSE.Exp HSE.SrcSpanInfo
1112+
-> [HSE.Alt HSE.SrcSpanInfo]
1113+
-> Either DesugarError (HSE.Exp HSE.SrcSpanInfo)
1114+
desugarCase _ _ [] = Left $ UnsupportedSyntax "empty case"
1115+
-- Generates this:
1116+
--
1117+
-- Either.either (\a -> e1 a) (\b -> e2 b) scrutinee
1118+
-- Maybe.maybe e1 (\b -> e2 b) scrutinee
1119+
-- etc
1120+
desugarCase l scrutinee alts0 | any isPrimCons alts0 = do
1121+
let (wilds, alts) =
1122+
Either.partitionEithers $
1123+
map (\x -> maybe (Right x) Left $ desugarWildPat x) alts0
1124+
conses <- traverse desugarPrimCons alts
1125+
let names = map (.accessor) conses
1126+
let consNames = map (.constructor) conses
1127+
let mwildpat = Maybe.listToMaybe wilds
1128+
if
1129+
| length wilds > 1 ->
1130+
Left $ UnsupportedSyntax $
1131+
"at most one catch-all (var/wildcard) in a case is permitted"
1132+
| Set.toList (Set.fromList consNames) /= List.sort consNames ->
1133+
Left $ UnsupportedSyntax $ "duplicate constructors in case: " <>
1134+
show consNames
1135+
<> show consNames
1136+
-- | All constructors below to the same type.
1137+
| Set.size (Set.fromList names) == 1 ->
1138+
HSE.App l <$> desugarPrimAlts l (List.concat (take 1 names)) conses mwildpat
1139+
<*> pure scrutinee
1140+
| otherwise ->
1141+
Left $ UnsupportedSyntax $ "mismatching types for constructors in case: "
1142+
<> show consNames
11081143
-- Generates this:
11091144
--
11101145
-- Variant.run
11111146
-- x
11121147
-- $ Variant.cons @"Main.Number" (\i -> Show.show i) $
11131148
-- Variant.cons @"Main.Text" (\t -> t) $
1114-
-- Variant.nil
1115-
desugarCase :: HSE.SrcSpanInfo -> HSE.Exp HSE.SrcSpanInfo -> [HSE.Alt HSE.SrcSpanInfo] -> Either DesugarError (HSE.Exp HSE.SrcSpanInfo)
1116-
desugarCase _ _ [] = Left $ UnsupportedSyntax "empty case"
1149+
-- Variant.nil (or `WildP x' for `_ -> x')
11171150
desugarCase l scrutinee xs = do
1118-
alts <- fmap (List.sortBy (Ord.comparing fst)) $ traverse desugarAlt xs
1119-
pure $
1120-
HSE.App l (HSE.App l run scrutinee) $
1121-
foldr (HSE.App l) nil $
1122-
map snd alts
1151+
alts0 <- fmap (List.sortBy (Ord.comparing fst)) $ traverse desugarAlt xs
1152+
let (alts,wild0) = Either.partitionEithers $
1153+
map (\(x,y) -> bimap (const y) (const y) x) alts0
1154+
if length wild0 > 1
1155+
then
1156+
Left $ UnsupportedSyntax $
1157+
"at most one catch-all (var/wildcard) in a case is permitted"
1158+
else do
1159+
let wild = Maybe.listToMaybe wild0
1160+
pure $
1161+
HSE.App l (HSE.App l run scrutinee) $
1162+
foldr (HSE.App l) (Maybe.fromMaybe nil wild) alts
11231163
where
11241164
tySym s = HSE.TyPromoted l (HSE.PromotedString l s s)
11251165
nil =
@@ -1142,11 +1182,11 @@ desugarCase l scrutinee xs = do
11421182
[HSE.PVar _ (HSE.Ident _ x)]
11431183
)
11441184
(HSE.UnGuardedRhs _ e)
1145-
_
1185+
Nothing
11461186
) =
11471187
-- Variant.cons @name (\x -> e)
11481188
pure $
1149-
(name,) $
1189+
(Left name,) $
11501190
HSE.App
11511191
l'
11521192
( HSE.App
@@ -1168,11 +1208,11 @@ desugarCase l scrutinee xs = do
11681208
[]
11691209
)
11701210
(HSE.UnGuardedRhs _ e)
1171-
_
1211+
Nothing
11721212
) =
11731213
-- Variant.cons @name (\_ -> e)
11741214
pure $
1175-
(name,) $
1215+
(Left name,) $
11761216
HSE.App
11771217
l'
11781218
( HSE.App
@@ -1184,8 +1224,99 @@ desugarCase l scrutinee xs = do
11841224
(HSE.TypeApp l' (tySym name))
11851225
)
11861226
(HSE.Lambda l' [HSE.PVar l' (HSE.Ident l' "_")] e)
1227+
desugarAlt (HSE.Alt l' (HSE.PWildCard l1) (HSE.UnGuardedRhs _ e) Nothing) =
1228+
pure $ (Right (), HSE.App
1229+
l'
1230+
( HSE.Var
1231+
l1
1232+
(hellQName l' "WildA")
1233+
)
1234+
e)
11871235
desugarAlt _ = Left $ UnsupportedSyntax "case alternative syntax"
11881236

1237+
data PrimCons = PrimCons {
1238+
l :: HSE.SrcSpanInfo,
1239+
accessor :: String,
1240+
constructor :: String,
1241+
bindings :: [String],
1242+
rhs :: HSE.Exp HSE.SrcSpanInfo
1243+
} deriving (Show)
1244+
1245+
data WildPat = WildPat {
1246+
l :: HSE.SrcSpanInfo,
1247+
rhs :: HSE.Exp HSE.SrcSpanInfo
1248+
} deriving (Show)
1249+
1250+
desugarPrimCons
1251+
:: HSE.Alt HSE.SrcSpanInfo
1252+
-> Either DesugarError PrimCons
1253+
desugarPrimCons (HSE.Alt l (HSE.PApp _ qname slots) (HSE.UnGuardedRhs _ rhs) Nothing)
1254+
| HSE.Qual _ (HSE.ModuleName _ prefix) (HSE.Ident _ string) <- qname,
1255+
let constructor = (prefix ++ "." ++ string),
1256+
Just (accessor,arity) <- Map.lookup constructor primitiveConstructors =
1257+
if length slots /= arity
1258+
then Left $ UnsupportedSyntax $ "wrong number of arguments to constructor in case alt: " ++ string
1259+
else do bindings <- traverse desugarPVarIdent slots
1260+
pure PrimCons{l, accessor, constructor, bindings, rhs}
1261+
where
1262+
desugarPVarIdent (HSE.PVar _ (HSE.Ident _ i)) = pure i
1263+
desugarPVarIdent _ =
1264+
Left $
1265+
UnsupportedSyntax "only var patterns are allowed in a primitive case (for now)"
1266+
desugarPrimCons (HSE.Alt _ p _ _) =
1267+
Left $ UnsupportedSyntax $
1268+
"unknown primitive constructor in pat: " <> HSE.prettyPrint p
1269+
1270+
desugarWildPat
1271+
:: HSE.Alt HSE.SrcSpanInfo
1272+
-> Maybe WildPat
1273+
desugarWildPat (HSE.Alt _ (HSE.PWildCard l) (HSE.UnGuardedRhs _ rhs) Nothing) =
1274+
Just WildPat { l, rhs }
1275+
desugarWildPat _ = Nothing
1276+
1277+
isPrimCons :: HSE.Alt HSE.SrcSpanInfo -> Bool
1278+
isPrimCons (HSE.Alt _ (HSE.PApp _ qname _) _ _)
1279+
| HSE.Qual _ (HSE.ModuleName _ prefix) (HSE.Ident _ string) <- qname =
1280+
Map.member (prefix ++ "." ++ string) primitiveConstructors
1281+
isPrimCons _ = False
1282+
1283+
desugarPrimAlts
1284+
:: HSE.SrcSpanInfo
1285+
-> String -- ^ Accessor e.g. Maybe.maybe
1286+
-> [PrimCons] -- ^ (cons, bindings, rhs)
1287+
-> Maybe WildPat
1288+
-> Either DesugarError (HSE.Exp HSE.SrcSpanInfo)
1289+
desugarPrimAlts l accessor consesFound mwildpat =
1290+
case lookup accessor primitiveSumTypes of
1291+
Nothing -> Left $ UnsupportedSyntax $ "invalid primitive accessor " <> accessor
1292+
Just cases -> do
1293+
alts <- traverse makeAlt cases
1294+
pure $ foldl' (HSE.App l) accessorE alts
1295+
where
1296+
accessorE =
1297+
HSE.Var l (HSE.Qual l (HSE.ModuleName l prefix) (HSE.Ident l string))
1298+
(prefix,drop 1 -> string) = List.break (=='.') accessor
1299+
makeAlt (cons, arity) =
1300+
case find ((==cons) . (.constructor)) consesFound of
1301+
Nothing ->
1302+
case mwildpat of
1303+
Nothing ->
1304+
Left $ UnsupportedSyntax $ "missing constructor in case: " <> cons
1305+
Just wildpat ->
1306+
pure $ HSE.Lambda
1307+
wildpat.l
1308+
pats
1309+
wildpat.rhs
1310+
where pats = [ HSE.PWildCard wildpat.l
1311+
| _ <- [1.. arity] ]
1312+
Just primCons ->
1313+
pure $ HSE.Lambda
1314+
primCons.l
1315+
pats
1316+
primCons.rhs
1317+
where pats = [ HSE.PVar primCons.l (HSE.Ident primCons.l b)
1318+
| b <- primCons.bindings ]
1319+
11891320
bindingStrings :: Binding -> [String]
11901321
bindingStrings (Singleton string) = [string]
11911322
bindingStrings (Tuple tups) = tups
@@ -1233,6 +1364,10 @@ desugarArg _ (HSE.PTuple _ HSE.Boxed idents)
12331364
| Just idents' <- traverse desugarIdent idents =
12341365
pure (Tuple idents', Nothing)
12351366
desugarArg userDefinedTypeAliases (HSE.PParen _ p) = desugarArg userDefinedTypeAliases p
1367+
desugarArg _ (HSE.PWildCard l) =
1368+
pure $ (Singleton $
1369+
"$wildcard_" <> show (HSE.startLine l) <> "_" <> show (HSE.startColumn l),
1370+
Nothing)
12361371
desugarArg _ p = Left $ BadParameterSyntax $ HSE.prettyPrint p
12371372

12381373
desugarIdent :: HSE.Pat HSE.SrcSpanInfo -> Maybe String
@@ -1848,6 +1983,7 @@ polyLits =
18481983
"hell:Hell.LeftV" LeftV :: forall (k :: Symbol) a (xs :: List). SSymbol k -> a -> Variant (ConsL k a xs)
18491984
"hell:Hell.RightV" RightV :: forall (k :: Symbol) a (xs :: List) (k'' :: Symbol) a''. Variant (ConsL k'' a'' xs) -> Variant (ConsL k a (ConsL k'' a'' xs))
18501985
"hell:Hell.NilA" NilA :: forall r. Accessor 'NilL r
1986+
"hell:Hell.WildA" WildA :: forall r (xs :: List). r -> Accessor xs r
18511987
"hell:Hell.ConsA" ConsA :: forall (k :: Symbol) a r (xs :: List). (a -> r) -> Accessor xs r -> Accessor (ConsL k a xs) r
18521988
"hell:Hell.runAccessor" runAccessor :: forall (t :: Symbol) r (xs :: List). Tagged t (Variant xs) -> Accessor xs r -> r
18531989

@@ -2090,6 +2226,31 @@ polyLits =
20902226
in toplevel
20912227
)
20922228

2229+
--------------------------------------------------------------------------------
2230+
-- Primitive sum types (for case support)
2231+
2232+
-- Easy access lookup for case alt desugaring.
2233+
primitiveConstructors :: Map String (String, Int)
2234+
-- ^ cons ^ type ^ arity
2235+
primitiveConstructors = Map.fromList [
2236+
(cons, (typ, arity))
2237+
| (typ,conses) <- primitiveSumTypes
2238+
, (cons,arity) <- conses
2239+
]
2240+
2241+
-- | Easier-to-maintain list for me, the author.
2242+
primitiveSumTypes :: [ (String, [(String, Int)]) ]
2243+
-- ^ type ^ cons ^ arity
2244+
primitiveSumTypes =
2245+
[ ("Maybe.maybe",[("Maybe.Nothing",0),("Maybe.Just",1)]),
2246+
("Either.either", [("Either.Left", 1),("Either.Right", 1)]),
2247+
("Exit.exitCode", [("Exit.ExitSuccess", 0),("Exit.ExitFailure", 1)]),
2248+
("Bool.bool", [("Bool.False", 0),("Bool.True", 0)]),
2249+
("These.these", [("These.This", 1),("These.That", 1),("These.These",2)]),
2250+
("Json.value", [("Json.Null",0),("Json.Bool",1),("Json.String",1),("Json.Number",1),("Json.Array", 1),("Json.Object", 1)])
2251+
]
2252+
2253+
20932254
--------------------------------------------------------------------------------
20942255
-- Internal-use only, used by the desugarer
20952256

@@ -2438,7 +2599,7 @@ bindingVars l tupleVar (Tuple names) = do
24382599
_ -> lift $ Left $ UnsupportedTupleSize
24392600

24402601
equal :: (MonadState Elaborate m) => HSE.SrcSpanInfo -> IRep IMetaVar -> IRep IMetaVar -> m ()
2441-
equal l x y = modify \elaborate' -> elaborate' {equalities = equalities elaborate' <> Set.singleton (Equality l x y)}
2602+
equal l x y = modify \elaborate' -> elaborate' {equalities = elaborate'.equalities <> Set.singleton (Equality l x y)}
24422603

24432604
freshIMetaVar :: (MonadState Elaborate m) => HSE.SrcSpanInfo -> m IMetaVar
24442605
freshIMetaVar srcSpanInfo = do
@@ -2670,12 +2831,14 @@ data Variant (xs :: List) where
26702831
data Accessor (xs :: List) r where
26712832
NilA :: Accessor 'NilL r
26722833
ConsA :: forall k a r xs. (a -> r) -> Accessor xs r -> Accessor (ConsL k a xs) r
2834+
WildA :: forall r xs. r -> Accessor xs r
26732835

26742836
-- | Run a total case-analysis against a variant, given an accessor
26752837
-- record.
26762838
runAccessor :: Tagged s (Variant xs) -> Accessor xs r -> r
26772839
runAccessor (Tagged _ (LeftV _k a)) (ConsA f _) = f a
26782840
runAccessor (Tagged t (RightV xs)) (ConsA _ ys) = runAccessor (Tagged t xs) ys
2841+
runAccessor _ (WildA r) = r
26792842

26802843
--------------------------------------------------------------------------------
26812844
-- Pretty printing

0 commit comments

Comments
 (0)