Expanding the library
Closed this issue ยท 17 comments
While we do have purescript-simple-dom now I'm not a huge fan of it's design and it's not as rapidly updated/released as stuff in contrib, so I'd like to avoid depending on it in Halogen and the like (I'm pretty sure @jdegoes agrees with this too).
Recently I mentioned doing something like this:
class Node a
instance nodeDocument :: Node Document
instance nodeElement :: Node Element
appendChild :: forall a b e. (Node a, Node b) => a -> b -> Eff (dom :: DOM | e) b
appendChild = unsafeAppendChild
foreign import unsafeAppendChild :: forall a b e. a -> b -> Eff (dom :: DOM | e) bHow do we feel about expanding purescript-dom to be more comprehensive and following this model for its implementations?
/cc @paf31 @joneshf @jdegoes @dylex @ethul @fluffynukeit @wildlyinaccurate or anyone with an interest in this ๐
I'm in favour of building a standard low-level DOM library, but I'm not sure why that has to be in purescript-dom. I think that if we don't want to do the polymorphic effects thing, then there is still a role for libraries like this one which just identify types, without any implementation.
If we do decide to implement anything here, I think it should be as close to the DOM API as possible: ideally, no type classes, no layers of abstraction, just FFI. I see why a Node type class is appealing, but even that is too opinionated for purescript-dom in my opinion. Why pay the cost of an extra dictionary if you know for other reasons that your method call is valid (maybe your higher level library uses smart constructors or something).
To be clear, I'd rather do something like
foreign import appendChild :: forall e. Node -> Node -> Eff (dom :: DOM | e) bIf you want to append something to a Document, you can coerce it to a Node. Maybe this library should provide a collection of safe coercions which are valid according to the API.
The class is there to represent subtyping relationships - functions and types aren't enough, as the API is made up of interfaces rather than types. The example above there is illustrating that appendChild works for both elements and documents (although incorrectly, as the rhs can't be a document).
Maybe that's the way to go then. I had intended that safe coercions would be part of the library but I guess you're right that something else could be built on top of that with classes, etc.
as the API is made up of interfaces rather than types
I understand the motivation, but that's not why we have type classes. It's the same workaround that's in simple-dom, just disguised slightly differently.
Nothing is to stop someone defining Node Int or Node String in another module.
We can represent interfaces using FFI types without type classes.
Edit: I think we're interleaving our responses, so some of what I said had already been said :)
Nothing except those would be orphan instances, but point taken ๐
Ok, sure. What I was getting at was that someone could make a new type of node, and think it was compatible with all the APIs, but not verify it with all browsers or whatever. If they declare an instance of Node for their type, the API has essentially been broken.
Would you object to using classes for the safe casting even? Like:
class IsEvent e where
toEvent :: e -> EventThat seems fine, but maybe on top of a lower-level API? Things like IsEvent could make for a saner "classy API" with things like appendChild :: (IsNode n1, IsNode n2) => n1 -> n2 -> Eff (dom :: DOM | e) b. It looks similar to the original approach, but the point is, you can't write an instance of IsNode without being able to write the coercion, and if the lower-level API doesn't provide that coercion somewhere, it wouldn't be possible without FFI hacks.
I know it's a bit of an extreme stance not to use type classes at all, but I think if we want to attract beginners, having very simple APIs with which to do something with familiar things like the DOM will be very important.
I would definitely separate the two though, maybe even creating a purescript-classy-dom package. Basically, I'd like to defer all possible API design decisions to other packages which can be opinionated about things like using type classes.
We can definitely do that, but I very much doubt anyone will use the non-classy version, I think having to explicitly cast everywhere will be far less beginner friendly than the alternative ๐
For example, there will be 49 different e -> Event cast functions for starters, to support the standard event types (according to MDN).
You might be right. The case I'm thinking of is where you want to build a UI library (maybe something like virtual-dom in PureScript), and squeeze out extra performance by specifying coercions directly, avoiding dictionary passing. You can take this to an extreme of course, and say that we should use FnN everywhere. I'm not sure where the sweet spot is.
Maybe we could generate the low-level API from the DOM API IDL file?
We can represent interfaces using FFI types without type classes.
How would this work?
@joneshf We can just create a foreign data type for each interface, foreign imports for accessors, and coercions for subclass relationships. In some cases (maybe Event?) we could probably tidy things up using records if we're careful about it.
I do think this library should be as bare metal and un-opinionated as possible, even to the extent of using Nullable, for example, to avoid unnecessary wrapping / unwrapping.
The goal is to eliminate the need for anyone to do any DOM FFI ever again. The only way that succeeds is if opinionated libraries are built on top of it. Which means this library needs to be totally un-opinionated.
Even the desire to use type classes to model sub-typing is an opinion. A popular one, but one which is definitely not universal (I personally am not a big fan of the pattern, though I do understand why people like it).
Generating something from IDL seems ideal. It will be comprehensive, low-level, and if the translation is done right, un-opinionated. A sane generator will also guarantee consistency which will help people use the library.
Looks great! Shall we put this on Pursuit?