Brand new to Rescript! I'm wondering how to declare types for generics

Hey guys, I’m brand spankers to ReScript and deciding to get familiarized by diving in head-first. I’m writing a simple weather API with ReScript and Express.js (using bloodyowl/rescript-express: Experimental (nearly zero-cost) bindings to express (github.com) as a helper library to get types for Express).

So I’m currently working on my get() method, it looks like this:

app->get("/:city", (req, res) => {
  let params: string = req->params["city"]
  Js.Console.log(params)
})

According to rescript-express, req->params should take a generic type. I’m wondering how I would go about assigning the type {city: string} to this?

I’ve tried doing it the TypeScript way with let params: {city: string} = req->params["city"] however the ReScript compiler says this isn’t valid.

Also, when I do req->params["city"], I get an error in VSCode saying that params["city"] has no method city even though in the compiled JS it correctly resolves to params.city. Is this a bug?

Thanks in advance for the help!

When you try to do req->params["city"], Rescript tries to evaluate params["city"] first. Since params is a function, it is expecting a call there. You can avoid this in following ways.

// 1
let city = params(req)["city"]

// 2
let city = (req->params)["city"]

You can read more about data first pipeline here.

2 Likes

You can also define a record type:

type getCityParams = {city: string}
...
app->get("/:city", (req, res) => Js.log(params(req).city))
2 Likes

Thanks for the responses @praveen @yawaramin!

I was able to get it working using this syntax:

app->get("/:city", (req, res) => {
  let city = params(req)
  Js.Console.log(city)
  let _ = res->send("Test")
})

@yawaramin you mentioned creating a type, I’m wondering if it’s possible that I could do it inline like in TypeScript so it’d be city: {city: string} = params(req)? I’ve tried doing it and it mentions that you can only do inline for variant types?

Right now, city is just a generic type.

EDIT: Removed some redundant wording (see OP for original issue).

ReScript is mostly nominally typed, so each type needs an explicit name. Defining types inside annotations like that is not possible, at least not for records.

The structural types, like objects and polymorphic variants, can be defined inside annotations.

The error message you’re seeing is kind of weird, since it isn’t actually relevant to what you’re trying to do. It’s referring to defining a variant type with a record inside a constructor (type foo = | Bar({baz: int})).

Basically what John said. You can for sure do destructuring like in JavaScript, but you need to tell the compiler about the type beforehand, e.g.

type getCityParams = {city: string}
...
app->get("/:city", (req, res) => {
  let {city} = req->params
  Js.log(city)
  res->send("Test")
})
2 Likes

Thanks! This worked!

Quick question though, why is it not valid to include the type definition inside the app->get function? Should types just be globally declared to begin with?

There’s probably a more elegant explanation as to why types cannot be defined within functions, but I think the short answer is that it just doesn’t make sense with the way the ReScript type system works, going back to the way it’s nominally typed. When you declare a type, then its name is scoped just like bindings are. If a type was declared inside a function, then it wouldn’t be very useful, since nothing defined outside of that function would have any way to reference it. In addition to that, then every time the function was executed, the type would be completely new, so not compatible with a type created the other times you execute a function.

There actually are ways you can indirectly declare new types inside functions using advanced features (first-class modules and GADTs), and those features often lead to these exact kinds of type-level problems. It’s easy to get cryptic type errors, or the compiler will complain the types are “escaping their scope,” which means they’re being used in a context that shouldn’t be able to access them.

Probably a better way to think about types is not that they’re declared “globally,” but within modules. (Modules aren’t necessarily globally available.) Modules are part of each type’s full path, so you can reference them like MyModule.Submodule.mytype.

3 Likes

Yeah, exactly. In ReScript nothing is actually ‘global’, everything is in a scope of either a module or a value.