Guidance for using List and other ReScript types from TypeScript

I’m experimenting with ReScript inside a TypeScript React project. What is the recommended/best way to expose ReScript-specific types like Char and List for consumption in TypeScript?

I assume the TypeScript side would do data consumption and not much editing of the data it gets. For example if there was a ReScript “getAddressBook” function I might want to loop through it to display the contacts in a non-ReScript React project, and possibly check if the size is 0.

One option is to create an interop module and corresponding types where every ReScript-specific type is replaced with a native javascript type like Array. This is what I did when trying to consume F# from C# in .net land.

There also is the gentype stuff and shims. Are there shims for those Belt types? Is that a proper use of shims? Can I use the Belt types directly from TypeScript and have a reasonably good experience?

The recommendation is to not expose lists and chars and other internal types to TypeScript. Expose arrays and strings instead. In fact, keep the internal representations in terms of arrays and strings as well, even though from an FP point of view it wouldn’t be considered ‘idiomatic’. ReScript has a more pragmatic approach more concerned with shipping quality software while staying close to the JS platform, and encourages rethinking some FP idioms in order to achieve that.

Interesting. Are you saying it is ok, and maybe even encouraged, not to use List and other ReScript types even if I’m mostly consuming from within ReScript? Hard to imagine doing that since Array mutates things and using immutable structures is kind of essential for good functional programming.

Well, like all things it depends :slight_smile: In some cases it’s appropriate to use lists and chars. But if you need to load large pieces of data, or if you need to expose the data to JS/TS consumers, or if you need to interact with React, then it’s more appropriate to use the more standard JS platform data types, like arrays and strings.

Concretely, here’s an example of when I’d say it’s appropriate to use a list:

// Api.resi

type t
type elem

let get: t => int => Js.Promise.t<elem>

// Api.res

type t = ...
type elem = ...

let timeout = (~duration, promise) => ...

let rec retry = (timeouts, f) => ...recursive function that pattern matches on list...

let get = (t, id) => retry(list{2_000, 3_000, 5_000}, () => ...)

Why is a list appropriate for the retry function’s timeouts argument? Because it’s used internally only, not exposed to users, and it’s used to implement an algorithm (a limited series of retries) that quite naturally expresses as a recursive algorithm with pattern matching on the list.

There are obviously other cases of this kind of thing, you have to use some judgment to decide…

1 Like

Hi, @jmagaram
For user defined types, it is recommended to treat it as abstract type as @yawaramin suggested.
However, for basic types, in particular the types you mentioned: char, and list, you can treat it as TS types directly.
Char → the unicode point, it is number type
List → number 0 | { hd : 'a, tl : List <'a>} here number 0 means empty list.
Array → Array
This should be reliable.

Are you saying it is ok, and maybe even encouraged, not to use List

I can tell that in our app we’ve eventually converted almost all of the lists to arrays. Practically more often than not we ended up wanting to loop over them in a React component or whatever, and then you need to convert anyway. And arrays being directly represented in JS, it often just doesn’t seem to be worth the tradeoff to use the list.

Also, maybe surprisingly, the mutability of arrays is not that big of a concern, as most of Belt.Array functions do not mutate. When you just use those, it almost feels like the arrays were immutable.

4 Likes