Skip to content
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

Merged
merged 7 commits into from
Jan 5, 2021
Merged

Improve type class syntax #420

merged 7 commits into from
Jan 5, 2021

Conversation

dougalm
Copy link
Collaborator

@dougalm dougalm commented Jan 4, 2021

  • Lightweight syntax for class constraints in functions and instances:

     def (/=) [Eq a] (x:a) (y:a) : Bool = not $ x == y`
    
    
     instance [Eq a, Eq b] Eq (a & b)
       (==) = \(x1,x2) (y1,y2). x1 == y1 && x2 == y2
    
  • Handle superclasses in interface decls:

    interface [Eq a] Ord a
      (>) : a -> a -> Bool
      (<) : a -> a -> Bool
    
  • 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 -> ...)

  * 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.
@google-cla google-cla bot added the cla: yes label Jan 4, 2021
@oxinabox
Copy link
Contributor

oxinabox commented Jan 4, 2021

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.

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)
idea, but not longer feels anywhere as needful.
This is so nice

Remove the need to name instances explicitly

Does this outright remove the ability to name them? (I think it should, those names are not useful for anything?)
Cool side effect this is that it masssively cleans up the output of #407 which wass showing completions for several hunderen instance names.

Copy link
Collaborator

@apaszke apaszke left a 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.

@@ -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
Copy link
Collaborator

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

Copy link
Collaborator Author

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.

Copy link
Collaborator

@apaszke apaszke Jan 5, 2021

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:

  1. This should be explained in the comment
  2. 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.

Copy link
Collaborator Author

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.

_ -> error "Bad projection"

nosrc = WithSrc Nothing
uignore = nosrc $ UPatBinder $ Ignore ()
Copy link
Collaborator

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.

Copy link
Collaborator Author

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.

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
Copy link
Collaborator

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

@@ -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
Copy link
Collaborator

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

Copy link
Collaborator Author

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.

pattern ClassDictDef conName superclasses methods =
[DataConDef conName
(Nest (Ignore (RecordTy (NoExt superclasses)))
(Nest (Ignore (RecordTy (NoExt methods))) Empty))]
Copy link
Collaborator

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!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed to BinderAnn

addImplicitArg :: Name -> (UType, UExpr) -> (UType, UExpr)
addImplicitArg v (ty, e) =
( ns $ UPi (Just uPat, uTyKind) ImplicitArrow ty
, ns $ ULam (uPat, Just uTyKind) ImplicitArrow e)
Copy link
Collaborator

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?

Copy link
Collaborator Author

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.

@@ -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)]
Copy link
Collaborator

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?

Copy link
Collaborator Author

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
Copy link
Collaborator

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.

Copy link
Collaborator Author

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
Copy link
Collaborator

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

Copy link
Collaborator Author

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.

@dougalm
Copy link
Collaborator Author

dougalm commented Jan 5, 2021

Does this outright remove the ability to name them? (I think it should, those names are not useful for anything?)

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 +. Adam has some cool stuff cooking on this front!

@dougalm dougalm merged commit 8283de6 into main Jan 5, 2021
ULet LetAnn UPatAnn UExpr
| UData UConDef [UConDef]
| UInterface [UType] UConDef [UAnnBinder] -- superclasses, constructor, methods
| UInstance (Nest UPatAnnArrow) UType [UMethodDef] -- args, type, methods
Copy link
Collaborator

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?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants