-
Notifications
You must be signed in to change notification settings - Fork 73
Subtyping Experiment #23
Conversation
the esy branch is the sandboxed one
This reverts commit 38de449.
We're tracking master and the output (and thus tests) get thrashed every time the version changes. See #17
* Removed Style module, added CssStyleDeclaration * Fixed signature of a few CssStyleDeclaration functions * Added a few ofNode and ofElement functions * Removed Style module, added CssStyleDeclaration * Fixed signature of a few CssStyleDeclaration functions * Added a few ofNode and ofElement functions * Fixed Promise.make * Added reject argument to Promise.make * Fleshed out Promise * Removed a few semi-colons that were troubling webpack * Added CSS2Properties + parentRule to CssStyleDeclaration
* Removed Style module, added CssStyleDeclaration * Fixed signature of a few CssStyleDeclaration functions * Added a few ofNode and ofElement functions * Removed Style module, added CssStyleDeclaration * Fixed signature of a few CssStyleDeclaration functions * Added a few ofNode and ofElement functions * Fixed Promise.make * Added reject argument to Promise.make * Fleshed out Promise * Removed a few semi-colons that were troubling webpack * Fleshed out an experimental fetch implementation
inheritance between them.
* Removed Style module, added CssStyleDeclaration * Fixed signature of a few CssStyleDeclaration functions * Added a few ofNode and ofElement functions * Removed Style module, added CssStyleDeclaration * Fixed signature of a few CssStyleDeclaration functions * Added a few ofNode and ofElement functions * Fixed Promise.make * Added reject argument to Promise.make * Fleshed out Promise * Removed a few semi-colons that were troubling webpack * Fleshed out an experimental fetch implementation
inheritance between them.
…into maybe-functors
|
I also have an example mini-implementation here: https://github.com/glennsl/subtyping-experiment/blob/master/src/main.re |
|
|
||
| let unwrapUnsafely = fun | ||
| | Some v => v | ||
| | None => assert false; |
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 no exception?
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.
@tekknolagi good point. @glennsl can you provide a message here please? assert false should only ever be used in branches that really can't be reached. Invalid_argument is much better
Also, something something GADT lol
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.
<3 GADTs
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've been thinking of having functions like HtmlDivElement.create to avoid the casting and option, but maybe a GADT would be better.... Could then perhaps be used with getElementsByTagName and such too... I'll have to do some reading.
This depends on #15, which isn't strictly necessary, but still very nice to have due to lack of "multiple inheritance"
The relveant commit is this: 025c25f
And the real magic is here: https://github.com/BuckleTypes/reason-js/pull/23/files#diff-f21fba065742f68ab32c1fe347b823b9R14
So what does this do?
In short, it makes e.g.
elementa subtype ofnode. Well, technically a subtype ofnode_like, of whichnodeis also a subtype. But this makes it possible for a function to accept all kinds ofnode_likes,element,node,documentwhatever, as long as its type is constructed accordingly, which meansElement.asNodeand such are now obsolete.So something like this:
Becomes simply:
Which might not look like much, but situations like this comes up surprisingly often.
But how?
By the power of chained phantom types!
So a phantom type is a type (or maybe type parameter, instructions have been unclear) which isn't used for anything other than to differentiate between different kinds of a type. E.g.:
To implement subtyping, we need a "tag" for each kind of object we have, and for both technical (type generalization) and convenience reasons, we will separate the concept of inheritability from the concrete types. So if we want to specify
elementas a subtype ofnode, we would tneed anode_tagand anelement_tag, as well as anode_like 'a. We can then definenodeandelementin terms of these:That's one step, and to also make
elementinheritable, we just add a type parameter toelement_tagand anelement_like 'atype and chain them:In this example I've also used
unitas a kind of short-hand terminator to avoid having to define yet more types just for that.Awesome, but there are downsides, right?
No! Well, maybe, depending on how much you love "informative type errors". There's also some limitations:
"Multiple inheritance" (for lack of a better word) is not supported (Although I think it's technically possible, the ordered nature of type parameters would make it very messy). Lots of dom objects are implement multiple interfaces, e.g.
ElementandDocumentimplement bothNodeandEventTarget. So one has to be picked as the "super-type", in both these casesNodeis a natural pick, andEventTargetwill have to be converted to. With implementation inheritance this is fortunately less of an issue.Type errors are more verbose. E.g.:
They're very informative, containing all the information you need to figure it out, but might seem a bit scary due to its verbosity and formatting (which is also partly because types are printed with ML-formatting).
Other notes
Node.*functions onnode_likes, you'll have to use the module that's appropriate for the type you have. E.g. forelementinstead ofNode.innerTextuseElement.innerText. There shouldn't be any issues with having the implementation inheritance respect subtyping, I'm just not sure it should. I'm not even sure there should be any implementation inheritance at all with subtyping.