-
Notifications
You must be signed in to change notification settings - Fork 26
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
Refactoring to support recursive capabilities #210
Conversation
In
|
Now |
My thought was that you should be able to use the function regardless of the kind of ref type you have (
I would say that a trait implements itself trivially, and to some extent that a capability implements all its constituent traits ( |
Didn't think about it. Which ones do you think should be hidden? |
I thought a trait can only be implemented by a class. The capability case is that a capability contains a collection of traits. I fear that unifying the above cases would create some confusion. As for the first guard, getting the implemented traits from a trait, I can't think of any valid use cases. As for the third guard, getting the implemented traits from a capibility, Went through all the usage of
True; the only two things, I can think of, we can do on a ref type variable, is assignment and method call.
I was thinking about the monadic common functions, but maybe they would be used somewhere else. |
I pushed a version with explicit exports now. |
Wouldn't it be more confusing to have a function
I don't have any concrete usecases either, but I don't think that asking a trait type for its implemented traits should raise an error. It's not an absurd request. As for the third case, see below.
Currently yes, but I was thinking of a hypothetical future use case that could have the following shape
rather than one case for each kind of ref type.
Again, for the current version of the code I agree with you, but I don't see the harm in leaving that functionality. If I was tasked with writing a function that returns a list of all the traits whose interface a certain type provides, I would write it like this. Is |
Since it's a private function now, it's less of a problem. I do feel having two functions, one for class type, the other for capability type, more clear. (I guess, it's more convincing if Still, I would prefer the One drawback I can think of unifying these cases is that we lose the type info we obtained before calling the function. For sure, this is rather subjective. Anyway, my feeling is that we don't need to write functions as general as possible; instead, we could extend it when we actually need the extra feature, where we know more about what exactly is required. |
Before this commit, each occurrence of a `ClassType` stored its own capability (implemented traits), but this turns out to be problematic. Since a trait can be parameterized over a class type we can declare a class as `passive class C : T<C>`, meaning that all occurrences of `C` would indirectly store itself, thus leading to an infinite recursion. The reason we didn't see any infinite loops is due to bugs when resolving types. Fixing these, I managed to hang the compiler instead. Here are some example programs exposing bugs in the current master, which is fixed in this commit: ``` trait T<v> passive class C<v> : T<v> f : C<int> def foo() : void this.f = new C<int> ``` Gives the error `Type 'C<int>' does not match expected type 'C<int>'` on the last line. This is the bug that lead to this refactoring. ``` trait T<v> passive class C : T<int, bool, string> ``` Compiles without complaining about the different number of type parameters. ``` passive class C : C ``` Crashes with the error `encorec: Maybe.fromJust: Nothing` ``` trait T<v> passive class C<v> : T<C<v>> class Main def main() : void let x = new C<int> : T<C<int>> in () ``` Gives the error `Type 'C<int>' does not match expected type 'T<C<int>>'` for the typecast in `main` (even though its a subtype). Will also hang compilation if types are resolved properly. Since we do not store capability information in the class types anymore, checking for subtypes requires a lookup, which in turn means that `isSubtypeOf` needs to be monadic. It has thus been moved to `Typechecker.Util`, and has been extended to cover more cases. The subtype checks in `CodeGen.Expr` has been simplified to `ty /= expected`, optimistically trusting the typechecker that if two types doesn't exactly match, it's safe to cast one into the other. This commit also adds a verbatim mode to `encorec` (`-v`) that prints the compiler phases before running them (useful for bug hunting, and should probably be extended to become even more verbatimy in the future). It also captures the type constraints `(MonadError TCError m, MonadReader Environment m)` in a type `TypecheckM a`, meaning the type of all typechecking functions is now of the form `Foo -> TypecheckM Bar` (where `Bar` is what the function returns if there are no exceptions). This trick requires the ghc extension `Rank2Types`, which has been added to the cabal file.
Okay, you made some fair points. The latest version only uses |
I think it can be merged. No objections? |
no objections |
Refactoring to support recursive capabilities
Before this commit, each occurrence of a
ClassType
stored its owncapability (implemented traits), but this turns out to be problematic.
Since a trait can be parameterized over a class type we can declare a
class as
passive class C : T<C>
, meaning that all occurrences ofC
would indirectly store itself, thus leading to an infinite recursion.
The reason we didn't see any infinite loops is due to bugs when
resolving types. Fixing these, I managed to hang the compiler instead.
Here are some example programs exposing bugs in the current master,
which is fixed in this commit:
Gives the error
Type 'C<int>' does not match expected type 'C<int>'
onthe last line. This is the bug that lead to this refactoring.
Compiles without complaining about the different number of type parameters.
Crashes with the error
encorec: Maybe.fromJust: Nothing
Gives the error
Type 'C<int>' does not match expected type 'T<C<int>>'
for the typecast in
main
(even though its a subtype). Will also hangcompilation if types are resolved properly.
Since we do not store capability information in the class types anymore,
checking for subtypes requires a lookup, which in turn means that
isSubtypeOf
needs to be monadic. It has thus been moved toTypechecker.Util
, and has been extended to cover more cases. Thesubtype checks in
CodeGen.Expr
has been simplified toty /= expected
, optimistically trusting the typechecker that if twotypes doesn't exactly match, it's safe to cast one into the other.
This commit also adds a verbatim mode to
encorec
(-v
) that printsthe compiler phases before running them (useful for bug hunting, and
should probably be extended to become even more verbatimy in the
future).
It also captures the type constraints
(MonadError TCError m, MonadReader Environment m)
in a typeTypecheckM a
, meaning the type of all typechecking functions is now ofthe form
Foo -> TypecheckM Bar
(whereBar
is what the functionreturns if there are no exceptions). This trick requires the ghc
extension
Rank2Types
, which has been added to the cabal file.