Skip to content

Smart generators #1

Open
Open
@gampleman

Description

@gampleman

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:

  1. 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).
  2. If it a type that is local to the module, then recurse for its subtypes.
  3. 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 type Json.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.
  4. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions