Eliminate built type class dicts when necessarily empty#3416
Eliminate built type class dicts when necessarily empty#3416hdgarrood merged 1 commit intopurescript:masterfrom LiamGoodacre:feature/elim-empty-dicts
Conversation
|
(Also bumped bower version in the test support code - it was failing to install dependencies) |
Input:data P t = P
class IsUnit unit | -> unit
instance isUnitInst :: IsUnit Unit
class IsUnit2 unit | -> unit
instance isUnitInst2 :: IsUnit unit => IsUnit2 unit
foo :: P _
foo = P :: forall unit . IsUnit unit => P unit
bar :: P _
bar = P :: forall unit . IsUnit2 unit => P unitPrevious output:var foo = (function (dictIsUnit) { return P.value; })(isUnitInst);
var bar = (function (dictIsUnit2) { return P.value; })(isUnitInst2(isUnitInst));Current progress:var foo = (function (dictIsUnit) { return P.value; })({});
var bar = (function (dictIsUnit2) { return P.value; })({});Goal:var foo = P.value;
var bar = P.value; |
|
Switched to var foo = P.value;
var bar = P.value; |
|
I'm happy with this. Wasn't too keen on having to change externs for tracking type class emptiness, but I'm okay with it. 😄 |
| mkNewClass :: Environment -> TypeClassData | ||
| mkNewClass env = makeTypeClassData args classMembers implies dependencies ctIsEmpty | ||
| where | ||
| ctIsEmpty = null classMembers && all (typeClassIsEmpty . findSuperClass) implies |
There was a problem hiding this comment.
If there is a superclass, is it possible that we try to solve a constraint for the superclass when only an instance for the subclass is in scope, and then by looking up the superclass as a property on the subclass dictionary, we run into a problem at runtime?
There was a problem hiding this comment.
I guess you might need a chain of two or more superclasses for it to become an issue though, otherwise you'll just get undefined anyway.
There was a problem hiding this comment.
If there is a non-empty super class, then this class isn't considered empty. So there shouldn't be any issues with looking up super classes, as I don't mark those ones as unused.
There was a problem hiding this comment.
I think it would be worth adding a test for this though (and maybe a few other cases) just to make sure we don't break that assumption in future. To be clear, the reasoning is that even if the subclass and superclass are empty, we'll never look up the superclass dictionary at runtime because it would be empty if we did. Right?
There was a problem hiding this comment.
You're right, I need to think more about super class lookup. I think the fix might just be moving the Unused hint slightly further up the dictionary expression. Thanks!
|
Pushed a fix for the super class lookup, but haven't properly tested yet. |
|
With the latest code.... Example:class WithArgEmpty t
instance withArgEmptyCheck :: WithArgEmpty Check
class WithArgEmpty t <= WithArgHasEmptySuper t where
withArgHasEmptySuper :: t
instance withArgHasEmptySuperCheck :: WithArgHasEmptySuper Check where
withArgHasEmptySuper = Check
whenAccessingSuperDict :: Check
whenAccessingSuperDict = foo Check where
bar :: forall t . WithArgEmpty t => t -> t
bar x = x
foo :: forall t . WithArgHasEmptySuper t => t -> t
foo x = bar xBefore:var withArgEmptyCheck = WithArgEmpty;
var withArgHasEmptySuperCheck = new WithArgHasEmptySuper(function () {
return withArgEmptyCheck;
}, Check.value);
var whenAccessingSuperDict = (function () {
var bar = function (dictWithArgEmpty) {
return function (x) {
return x;
};
};
var foo = function (dictWithArgHasEmptySuper) {
return function (x) {
return bar(dictWithArgHasEmptySuper.WithArgEmpty0())(x);
};
};
return foo(withArgHasEmptySuperCheck)(Check.value);
})();After:var withArgEmptyCheck = WithArgEmpty;
var withArgHasEmptySuperCheck = new WithArgHasEmptySuper(function () {
return undefined;
}, Check.value);
var whenAccessingSuperDict = (function () {
var bar = function (dictWithArgEmpty) {
return function (x) {
return x;
};
};
var foo = function (dictWithArgHasEmptySuper) {
return function (x) {
return bar()(x);
};
};
return foo(withArgHasEmptySuperCheck)(Check.value);
})();That is, the call to |
|
I think this is ready to look at again. I've also added a test. |
|
This is pretty exciting! My silence on it is not due to lack of enthusiasm, just lack of time 😄 |
|
Really looking forward to this being released! 👍 |
| { typeClassDependencies | ||
| , typeClassIsEmpty | ||
| } <- case M.lookup className' classesInScope of | ||
| Nothing -> throwError . errorMessage $ UnknownClass className' |
There was a problem hiding this comment.
Is this a sign for a programming error in the compiler, or an actual user facing error?
In the first case I'd prefer an internalError, and in the second case this really ought to have a source span (unless this is wrapped somewhere and gets a source span)
There was a problem hiding this comment.
It's the first case, I believe. Will change to internalError, thanks!
There was a problem hiding this comment.
Actually, turns out, it's not the first case. 😅
There was a problem hiding this comment.
The errors appear to be propagated through typesOf and into the type checker; where source spans get added and the errors re-thrown.
|
Apart from the conflicts (which are very minor), is there anything getting in the way of merging this? |
|
Not as far as I'm concerned, I forgot it wasn't merged already actually! |
|
@hdgarrood, I don't think so. I'll try to get this updated tonight. |
|
After rebasing, it seems to get stuck on the |
|
Here is a comparison of the output of master vs this branch: Specifically lines 20-22. It appears to be writing to the wrong variable. |
|
All tests on our work projects run just fine. |
|
I've just tested this on our code at work and it seems to work fine for me too. Shall we try to get this merged? @LiamGoodacre has the approach changed much since this was last reviewed, or is there anything in particular you think wants looking at? Since this passed review already I'm tempted to just merge it. |
|
@hdgarrood I'm happy to have this merged now 👍 |
|
Nice, thanks! :) |
Had a quick go at this. I want to switch to
undefinedinstead of{}, but so far it seems to work. I want to test it on a lot more dictionary heavy code, though.Fixes #2768