RFC: Variant maps

I had an idea for a new language feature. Before I sat down and tried to hack it out myself, I wanted feedback and thoughts.

Suggestion

Allow a special type of object/record, which I will henceforth call a “variant map.” It would behave essentially like a Javascript object, but would have variants for keys. This essentially allows you to define something like a Map with keys of variant type t but which could be accessed using object notation and represented as a regular Javascript object at runtime. This allows you to create a variant and a record which are closely linked, so to speak. Since variants are strings at runtime, I don’t think this would require too much magic on the Javascript side.

// Variant used in this must not carry data, i.e must be a simple enum type.
type myVariant = 
    | Key1 
    | Key2

let myObject = { Key1: "bar", Key2: "bar"}

Motivating example

I’m working on a ReactFlow app built in Rescript. The datastructure for nodes includes a “type” tag which in javascript is a string. In Rescript of course, I represent this as a variant. I have vaccilated a bit between using polymorphic variants or regular variants, but so far have settled on regular variants.

To map node types to react component functions, one provides an object which maps these node types to the functions. If this were a function, I could easily make use of Rescript’s exhaustive pattern matching to ensure all my node types are properly mapped.

To do this today, I have to create either a record or object with fields that have the same runtime representation of my variants. This requires some amount of manual checking/testing and feels inelegant. I think that ReactFlow is not the only library that has this situation where you have an enum that you also want to use as a dictionary.

Looking forward to people’s thoughts!

this looks like a good use case for meta programming, I’m pretty sure you could generate the variant from the record type using @rescript/tools for example!

I could certainly try that – It looks like rescript tools is vs-code thing primarily? I use Emacs mostly.
You definitely could do something with a ppx-type thing.

However, doesn’t it seem like it would be a really cool feature to be able to have something that is essentially a record field name, but at the same time a value? To me, it feels like a natural progression – you have union types, you have sum types, now you have something that has a foot in both worlds so to speak.

There are a lot of situations where you might want a pattern-matching function for one thing, and a record/dictionary for another, but have the same keys. At the end of the day a pattern matching clause looks a lot like a hashmap where the keys are the domain of the function and the range are the values. If I’m not mistaken a lot of languages implement pattern matching using just such a datastructure. Why not double down on that relationship? :grin:

(Rescript tool is now part of v12, so installing v12 beta gives you access to npx rescript-tools)

You could get (limited) typed tree information as JSON via

# requires a build first
npx rescript-tools doc src/Foo.res

gives

{
  "name": "Foo",
  "docstrings": [],
  "source": {
    "filepath": "src/Foo.res",
    "line": 1,
    "col": 1
  },
  "items": [
  {
    "id": "Foo.myVariant",
    "kind": "type",
    "name": "myVariant",
    "signature": "type myVariant = Key1 | Key2",
    "docstrings": [],
    "source": {
      "filepath": "src/Foo.res",
      "line": 1,
      "col": 1
    },
    "detail": 
    {
      "kind": "variant",
      "items": [
      {
        "name": "Key1",
        "docstrings": [],
        "signature": "Key1"
      }, 
      {
        "name": "Key2",
        "docstrings": [],
        "signature": "Key2"
      }]
    }
  }]
}
1 Like