-
Notifications
You must be signed in to change notification settings - Fork 109
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Improve type class syntax #420
Conversation
* Handle superclasses * Remove the need to name instances explicitly * Push down types from interface definitions into instance methods * Improve error messages for missing/duplicated methods As par of this change, I moved the lowering (turning interface/instance decls into data defs and method/super class getters) from the parser to type inference where we have much more context about existing definitions. Fixes #370.
I guess that makes them implicitly typed implicit implicit arguments.
I love this so much. This is great. I had been playing around in my head with the opposite of the last point. What if we were to push specially marked types of methods upwards from methods to allow methods to define interfaces. Which I still think is a cute (albeit under-developed)
Does this outright remove the ability to name them? (I think it should, those names are not useful for anything?) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks nice! Some things that would be good to fix before merging.
Also, I'd appreciate a walk through the inference changes related to dictionary synthesis. It's been hard enough to understand without comments that I gave up for the sake of saving time.
src/lib/Parser.hs
Outdated
@@ -258,7 +258,7 @@ findImplicitImplicitArgNames typ = filter isLowerCaseName $ envNames $ | |||
UFor _ _ _ -> error "Unexpected for in type annotation" | |||
UHole -> mempty | |||
UTypeAnn v ty -> findVarsInAppLHS v <> findVarsInAppLHS ty | |||
UTabCon _ -> error "Unexpected table in type annotation" | |||
UTabCon _ -> mempty |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why did that have to change? It looks wrong to me
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is reachable with a syntactically valid but ill-typed user program so it shouldn't cause a compiler crash. We could throw a parser error (and change the type of this function to allow that) but I think it's also fine to forget about the implicit args and catch the error during type inference.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right. So the whole explanation sounds reasonable to me, but:
- This should be explained in the comment
- There are more error cases that we should fix
Also, do we really have a guarantee that there will be a type error if there's a UTabCon
? At some point we might add another syntactic form that turns a UTabCon
into a valid type, and then this function will silently compute the wrong thing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure. I'll fix the rest of the function too.
src/lib/PPrint.hs
Outdated
_ -> error "Bad projection" | ||
|
||
nosrc = WithSrc Nothing | ||
uignore = nosrc $ UPatBinder $ Ignore () |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this gone? This was pretty useful, since otherwise projections are super difficult to read.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I got rid of it because it was crashing the pretty-printer when I was trying to debug some ill-formed IR I was generating. As a debugging aid, we want the pretty printer to remain faithful to the underlying IR rather than trying to rewrite it. But I hadn't appreciated that this is user-facing too. I'll replace it with something more readable.
src/lib/Syntax.hs
Outdated
withLabels :: LabeledItems a -> LabeledItems (Label, Int, a) | ||
withLabels (LabeledItems items) = LabeledItems $ | ||
flip M.mapWithKey items $ \k xs -> fmap (\(i,a) -> (k,i,a)) (enumerate xs) | ||
|
||
lookupLabel :: LabeledItems a -> Label -> Maybe a | ||
lookupLabel (LabeledItems items) l = case M.lookup l items of |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rename to lookupLabelHead
? It doesn't return all values for a given label
src/lib/Syntax.hs
Outdated
@@ -784,11 +798,15 @@ instance BindsUVars UPat' where | |||
instance HasUVars UDecl where | |||
freeUVars (ULet _ p expr) = freeUVars p <> freeUVars expr | |||
freeUVars (UData (UConDef _ bs) dataCons) = freeUVars $ Abs bs dataCons | |||
freeUVars (UInterface _ _ _) = mempty -- TODO | |||
freeUVars (UInstance _ _) = mempty -- TODO |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like something worth fixing before we merge this
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was worth doing! Implementing it made me realize I've been sloppy about the scope of the binders in the instance type. They're meant to be in scope in the method implementations, so they shouldn't be represented as alpha-renameable pi binders within the type. I'll push a separate fix.
src/lib/Syntax.hs
Outdated
pattern ClassDictDef conName superclasses methods = | ||
[DataConDef conName | ||
(Nest (Ignore (RecordTy (NoExt superclasses))) | ||
(Nest (Ignore (RecordTy (NoExt methods))) Empty))] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't we use BinderAnn
here, so that it becomes insensitive to whether the names are used or not? Or should we just maintain an invariant that we don't use any names in class definitions? But if we go with the latter case, why do we ever use BinderAnn
-- let's just keep our conventions!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed to BinderAnn
src/lib/Parser.hs
Outdated
addImplicitArg :: Name -> (UType, UExpr) -> (UType, UExpr) | ||
addImplicitArg v (ty, e) = | ||
( ns $ UPi (Just uPat, uTyKind) ImplicitArrow ty | ||
, ns $ ULam (uPat, Just uTyKind) ImplicitArrow e) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We did have source info for those expressions before. Maybe consider keeping it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The source info wasn't real though, so I think it's just as good to let the source context fall back to the next level up in the AST.
src/lib/Syntax.hs
Outdated
@@ -237,6 +249,8 @@ data UExpr' = UVar UVar | |||
data UConDef = UConDef Name (Nest UAnnBinder) deriving (Show, Generic) | |||
data UDecl = ULet LetAnn UPatAnn UExpr | |||
| UData UConDef [UConDef] | |||
| UInterface [UType] UConDef [UAnnBinder] | |||
| UInstance UType [(UVar, UExpr)] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you please add some comments that explain what the different fields mean?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, good idea
|
||
recGet :: Label -> Atom -> Atom | ||
recGet l x = do | ||
let (RecordTy (Ext r _)) = getType x |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't you assert that this has NoExt
? Otherwise there is no way to know if there won't be more instance of your label coming later? Also, this does retrieve only the first element under a given label, without any assertion that there are no more of those. Is that expected? If so, please reflect that in the name.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll rename to recGetHead
. I think it's fine if there are more copies of the same label coming later.
buildImplicitNaryLam Empty body = body [] | ||
buildImplicitNaryLam (Nest b bs) body = | ||
buildLam b ImplicitArrow $ \x -> do | ||
bs' <- substEmbed (b@>x) bs |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: this is O(n^2). Would be better to carry the substitution in a recursive call, but no big deal I guess
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch. I think there are many places like this in the compiler. One day we'll need to do some profiling and fix the ones that matter the most. I'll leave this one for now.
Yes, it does for now. We're conflicted about it though, because names could be useful when you want to specify a type class instance explicitly. For example, you might want to do a reduction with an alternative monoid instance for floats like max or min instead of the usual |
And fix type inference to handle them properly.
ULet LetAnn UPatAnn UExpr | ||
| UData UConDef [UConDef] | ||
| UInterface [UType] UConDef [UAnnBinder] -- superclasses, constructor, methods | ||
| UInstance (Nest UPatAnnArrow) UType [UMethodDef] -- args, type, methods |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What are "args"? Is this a list of binders to prepend to each method definition?
Lightweight syntax for class constraints in functions and instances:
Handle superclasses in interface decls:
Remove the need to name instances explicitly
Push down types from interface definitions into instance methods, reducing the annotation burden for instance methods.
Improve error messages for missing/duplicated methods.
Infer types of implicit implicit args. (No more
(n:Int) ?-> n=>a -> ...
)