-
Notifications
You must be signed in to change notification settings - Fork 52
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
module system #1020
base: dev
Are you sure you want to change the base?
module system #1020
Conversation
@gensofubi as you make progress on this, can you update the PR description documenting what is now possible |
The types being reported for member variables of modules seem to be inconsistent. In the following, the type is
In the following, where the module is nested deeper, however, the type is
|
@cyrus- These issues have been addressed |
I finally understand some amount of casting, so I found a few casting bugs - they're pretty similar to some bugs I found recently in dev so they won't be too hard to fix but casting's always annoying... Problem # 1: This one is because of the definition of a ground type. Specifically ground types should have the property that if two ground types are consistent then they are equal. e.g. Int & Int are consistent and equal (fine) If you have a basic type like int, string, then it is ground, but if you have a more complex type like list, functions, sums or modules, then only the version that only has holes should be ground i.e. [?], ? -> ?, +X(?) + Y, {T1: ?, T2:?} should be ground you can think of the ground type as representing just the top-level type - i.e. we know it's a module but we don't know what kind of module it is. Problem # 2: once you fix problem # 1, I think you'll find that this example doesn't show an error, but it does get stuck later. This is because of the "canonical forms" thingy in the appendix of the 2019 paper. I'll probably try and write it out in the codebase because a lot of us are tripping up over this, and it's kinda hidden at the bottom of that paper. It would be nice if we could say "if it has type module, and it is a value, then it is a Module(...)"; unfortunately it's not quite that simple with casts... "If it has type Int, and it is a value, then it is an Int(...)" is true. "If it has type [...], and it is a value, then it is a List(...)" is unfortunately not true. This is because the list might have some casts from [X] to [Y] outside it. When you try and open up a list you have to apply a cast from X to Y for every element before you can look inside. This is what I tried to do with the "If it has type {...}, and it is a value, then it is a Module(...)" is also not going to be true. Once you fix problem # 1, the casts around module values will stop disappearing, and so now we'll have to deal with them. In particular if you have a module {x=4}:<{x:Int} => {x:?} > then when you access I know this stuff is complicated, and I barely understand it myself, so if you want to meet at some point to discuss I'd be happy to. code for reproducing example:
should evaluate to FailedCast(5, Int, String) ++ "hello" on an unrelated note: idk whether the desired behaviour is to evaluate this or to get stuck with some sort of error message, but this evaluation is just hanging atm
|
marking as draft until above casting issues are resolved |
@gensofubi can you merge with dev when you get a chance |
Back with another type bug sorry!!! An important property of type consistency is that it's a symmetric relationship. (A ~ B ⇔ B ~ A) and unfortunately that doesn't hold for modules at the moment. In particular At first it seems fine because whenever we need a
This type checks fine because Here's an example where I've tricked the type checker into letting us use {y=4} as an {x:Int} using this g function:
|
Looks good now! I wasn't able to find any more casting bugs - not letting users define rho types feels like a good practical workaround for things like {x:?, …}->{x:?, y:?}->{y:?, …} for now. |
@gensofubi reviewing now, but there is a merge conflict when you get a chance |
@gensofubi I'm a bit confused about what is the difference between a let binding with a module signature in the type position and a module binding. The error message in this screenshot is quite confusing: |
@cyrus- syntax notes / questions:
For concrete illustration here's a version of the contents of a scratch pad which:
in the above module blocks and non-module blocks are both semi-colon separated lists of convex forms, including prefix-shaped definition forms (ie the semicolon is not part of let/type) taking a right unidelimited child. i've opted against having an optional trailing semicolon, but this seems possible if-desired. the above is almost achievable using the current syntax model... the only thing not trivially doable is the Anyway I know the above is a bit different than we talked about, but I'm not totally clear on what your exact preferred model is, so this is intended as starting point for discussion. My goals are to (within reason) stem keyword proliferation and avoid having incidentally-different ways of doing the same thing. But I'm also ~ fine with keeping let...in and using var/val/whatever for the block-level let. I'm curious if there's a reasonable way of finessing block versus module forms in some way which obviates the need for a separate module definition keyword, assuming some other solution were found for the constructor issue above. like perhaps curly braces is a module IFF there trailing item is a definition. Perhaps not a good idea though, would have to guard against weird error message when you accidentally do this when defining a function etc. |
Haz3l module system.
Current progress:
Type Module defined (with detailed information as name-type pair), keyword module and operator . implemented,
Type annotation for modules implemented. Use a pair of curly parentheses around a tuple of patterns. Typically the tuple should contain only TypeAnn patterns, but Var/Tag will also be accepted and treated as hole-typed members.
Type Inconsistency warnings includes both the pattern (which define the member) and the definition expression.
For member name inconsistency, accessing members shown in module's typeann but not defined will be treated as a emptyhole (supposing users are to implement later), accessing members defined but not shown in module's typeann will be treated as free variables.
Basic Type member implemented. Type member in module type annotation implemented.
For constructors defined by Sum types in module, either access directly by dot access, or access under analytic mode (dot access not necessary).
Provided warnings for using type members that are not defined, and inconsistency for type members. Created documentations for module-related keywords.
Some current rules of typing and casting:
Module type consistency: module types are consistent only if they share the same member name list, and the types of every member are consistent. Therefore, the ground type of a module is one where all members have hole types.
For module creation, the definition part will always be packaged as the exact same module type of the annotation as we mentioned amove. So nothing to worry about casting.
Casts will be added on module aliases. Cast calculation will happen on dot operators.