Thanks for the good feedback, Dillon! Here are my thoughts.
If I understand it correctly, the approach that elm-typescript-interop takes reads the Elm port declarations and generates typescript definitions for use on the TS side. That’s great for when you only want to send supported types through the port!
The approach that I’ve liked taking for interop uses few ports, maybe just an in-out port pair for a specific “theme” of tasks, and uses an ADT to represent the things that can be done through that port.
So, for example, let’s say I’m making an Elm module with ports that will allow interaction with the browser’s client-side storage. I’d have a port for messages going out, and a port for messages coming in. Then I’d have two ADTs, one that describes the messages I can send out, and one that describes the messages that can come back in. (I know you already know about this because of my Elm conf talk, and we’ve talked about it in-person, I’m just writing it here for clarity).
So the ADT going out might look like:
type MsgToBrowserStorage
= StoreItem {key: String, item: Json.Encode.Value}
| LoadItem {key: String}
| ClearItem {key: String}
And I’d have a similar one for messages coming in:
type MsgFromBrowserStorage
= LoadedItem {key: String, item: Json.Encode.Value}
I feel like these ADTs make for a nice API on the Elm side. The painful part is that, since ADTs aren’t supported in ports, we’ve got to write encoders and decoders for these messages. And we’ve got to write those encoders & decoders on both the Elm side and the host language side (Reason, TS, Flow, whatever).
So for this specific workflow, generating the types from the port definitions doesn’t quite fit the bill.
There are a couple of reasons why I reached for something like YAML:
- The parser is already written and usable in whatever language the CLI will be written in.
(VS trying to re-use the Elm parser somehow, or re-write the parser by hand)
- It’s clear to the user where they should write the code for the generator to read
(negative example: if the format used to describe the types we wanted generated were Elm types, we’d have to figure out the UX for the user to specify where those types live, and which types in the file should be generating code VS which types should be left alone)
- It’s clear to the user what values they can use with the generator.
(VS just writing an Elm type and then discovering after you’ve written a bunch of stuff that you didn’t understand the lib very well, and the types you wrote aren’t supported)
- When the code that describes what will be generated is edited in the YAML file (or whatever format), it’s very clear to the user that they are making changes to both Elm code and host language code. It forces the user to think about the border between the two worlds at that point, considering what would work well for both languages, instead of just thinking in Elm-world the whole time.
Now that I’ve written those reasons out, I find them less compelling than they were when they were just sitting in my head, except for the first. It’s still very compelling to me that we wouldn’t have to write and maintain a copycat parser.
Also, now that you’ve brought it up, I think it’d be a great idea to make this pluggable, so that a plug for any host language could be written, not just Reason and TS.
What are your thoughts on all of that?
Also, @brian, JSON Schema is a great idea. I didn’t think of that.
Also you just blew my mind with Dhall. Stack overflow. Brain melting.