Description
So here is an idea for a tooling project, I hope it is appropriate:
Problem
It a common design pattern in Elm that we make a type and then need to add "capabilities" to that type. Usually these are forms of integration with various packages. Some common examples include:
- JSON decoders and encoders
- Random generators
- Fuzzers
- Form builders
- HTML views
Most programming languages support this by either meta programming (or macros, deriving, template Haskell, etc) or introspection and dynamic dispatch (interfaces/protocols with default implementations).
Elm doesn't support those forms of abstraction instead preferring manual implementation and duplication of these tasks. This has the benefit of allowing more control and customisability of the process, but forces programmers to type out more "boilerplate" code.
A solution
Code generation. However, I think the code generation has to be rather smart for this to work nicely. First of all, it should be type driven. I envisage the user writing the type in a module and then getting a completion suggestion in their editor:
⚡ Generate JSON encoder...
⚡ Generate Fuzzer...
etc.
The generator needs to be able to traverse the type and for each subtype* encountered:
- If is a primitive type (primitive types are types that the package provides that need no further expansion) and expand to a call to the appropriate function from that package (generating the appropriate imports if necessary).
- If it a type that is local to the module, then recurse for its subtypes.
- If it is a type imported from another module, look for a function with an appropriate signature (i.e. when generating a decoder for
Color
, look for a value with typeJson.Decode.Decoder Color
:- If found just one, import it and generate a call as apropriate.
- If found multiple, generate a
Debug.todo
listing the candidates and instructing the user to pick. - If none found, generate a
Debug.todo
with a message instructing the user to implement that.
- Generate a call to an appropriate composition operator for all the encountered subtypes.
If anything goes wrong, generate a Debug.todo
with a message explaining the problem.
However, since the set of packages that could benefit from this is unbounded, I suggest that each package can contain a script in some well known location that details how this process works for it. Then the extension would search through the packages that are dependencies of the current project and load these as completion items.
Technical challenges
Since this whole process is based around types, there needs to be solid type resolution facilities. For example if the package considers Color
a primitive type, than it needs to be ensured that we are taking about the same Color
(i.e. for example that this is the color from avh4/elm-color rather than from some other package).
Second, loading scripts from packages into the editor environment has some security, stability and performance implications. Having correct handling around these is a bit tricky.
* By subtype I mean any type encountered in the type declaration:
So for
type Foo
= Bar Int
| Baz { hello = Qumox }
Int
and Qumox
are subtypes.