interoprescript

How to use List, Map, etc. from TypeScript


I have a combo ReScript and TypeScript project. I want to use gentype to expose TypeScript friendly objects. Gentype works for string, bool, records, and other basic objects without any hassles. How can I work with list and char and other ReScript-specific types from TypeScript?

There is a shims feature but I don't know how to do that. I would assume there are built-in shims for the standard Belt library but can't find them.

I plan to do most of the calculations and heavy-lifting inside Rescript. So when the info is sent to TypeScript, I'll probably just be consuming the data, not modifying it. This might be iterating through a list to display the contents in a non-Rescript React project. I don't plan to manipulate the data.

One option is to convert lists to arrays inside ReScript before exporting the results. So any time I want to consume information from TypeScript, I'll create a function and/or type in ReScript that only has bool, number, string, array, and object. I could create an "interop" module with all the stuff I want to consume or use from TypeScript. This provides a clean separation between the ReScript and TypeScript world and is easy to get my head around but seems a little inefficient (like converting all lists to arrays) and extra work.

On the other hand, if using the Belt objects like list and map is cumbersome from TypeScript, even with the shims, then I'm probably better off creating my "interop" module anyway.

What is the recommended/simplest/best way to use list, map, char, and other ReScript specific objects from within TypeScript? Are there friendly Typescript definitions for the Belt standard library that I could use even if I wasn't using ReScript?

===

One additional note. I have experience trying to use F# (functional) from C#. It was painful. The best solution for me in that environment was to create an interface on the F# side that was easily consumable on the C# side that did not use the F# native objects.


Solution

  • As rescript compiles to JavaScript, and the output is very readable and (usually) straight-forward, you could just emulate what it generates.

    For example, this rescript code:

    let xs = list{1, 2, 3}
    let _ = List.map(x => x + 1, xs)
    

    is compiled into this (slightly simplified) JavaScript:

    var List = require("./stdlib/list.js");
    
    var xs = {
      hd: 1,
      tl: {
        hd: 2,
        tl: {
          hd: 3,
          tl: /* [] */0
        }
      }
    };
    
    List.map((x) => x + 1, xs);
    

    There is a slight problem with the literal syntax of lists specifically, but that could be simplified a bit by using List.cons instead:

    let xs = List.cons(1, List.cons(2, List.cons(3, list{})))
    

    which becomes:

    var xs = List.cons(1, List.cons(2, List.cons(3, /* [] */0)));
    

    Pattern matching also isn't as convenient, obviously, but still pretty straight-forward for the simple things at least. For example:

    let rec sum = xs => switch xs {
      | list{} => 0
      | list{x, ...rest} => x + sum(rest)
    }
    

    becomes:

    function sum(xs) {
      if (xs) {
        return xs.hd + sum(xs.tl);
      } else {
        return 0;
      }
    }
    

    Most other types don't have any special compiler support, and so becomes just plain function calls. Using Belt.Map for example, despite using some advanced language features, compiles to very straight-forward JavaScript:

    module IntCmp = Belt.Id.MakeComparable({
      type t = int
      let cmp = (a, b) => a - b
    })
    
    let m = Belt.Map.make(~id=module(IntCmp))
    let _ = Belt.Map.set(m, 0, "a")
    

    becomes (more or less):

    var Belt_Id = require("./stdlib/belt_Id.js");
    var Belt_Map = require("./stdlib/belt_Map.js");
    
    var IntCmp = Belt_Id.MakeComparable({
          cmp: (a, b) => a - b
        });
    
    var m = Belt_Map.make(IntCmp);
    Belt_Map.set(m, 0, "a");