A few small questions

Perhaps this will help OCaml interface files–hero or menace? - DEV Community

2 Likes

Hey @yawaramin you answered this before, sort of. I asked whether to use Js_dict or Js.Dict.t, but there is also a Js.dict it seems.

Why is Js.Dict.t the correct one and how can I tell?

It’s the correct one by convention, in ReScript modules may be created from files or from the module X = ... syntax. There is no guarantee that a module that is a file today, will stay a file tomorrow, or be refactored into a syntactic module tomorrow. But there is a stronger guarantee that the nested module that is available today, will be available tomorrow (again, by convention).

So if you have a choice between using a module that’s from a file (like Js_dict) or one that’s a submodule (Js.Dict), you should choose the latter one.

Thank you that makes sense to me.

How do I change the max line width for the autoformatter?

It’s creating way too long lines on my display.

Can I tweak the autoformatter to always insert types?

Use less libraries :crazy_face:

2 Likes

Haha, thank you for that. :wink:

What is the convention for types?

I sometimes see type options = { … }, and sometimes I see module Options = { type t = { … } }

When do I use which? Is it just preference or convention? Which should I prefer?

It’s preference and depends on your domain. I try to revolve my modules around one “type” but that module can always have subtypes. But once other modules start interacting with a specific type too often I will pull it out into it’s own module.

For example, I’m working on a golf rpg, and I had a GolfCourse module, which had an internal type fairway = {}. Turns out all my other modules need to query the fairway a bunch of different ways, so I pulled it out of golfcourse to it’s own Fairway.t module so now it can be fairway->Fairway.hazardsAt(~yards=200)

1 Like

If I expect to receive an object that can contain all sorts of random stuff, what type should I be using?

Let’s say there’s an API that gives me something I request, e.g. a user. The user looks like this:

{ name: string, email: string, metadata: ??? }

What type do I give metadata? Js.Json.t?

Oh, that makes sense to me!

So wording it another way: If a type has a lot of associated functions, then the functions should be put together with the type in its own module (module Options = { type t = { ... } }). If it’s a type that is used for just carrying data and has no “applicable functions” then it can be type options = { ... }.

Is that right?

1 Like

Is it a subset of JSON? Because JSON doesn’t include “all sorts of random stuff”. It doesn’t have a representation for functions, Maps or Sets, for example.

If it doesn’t map that well to JSON, you could still use basically the same approach with a custom untagged union type (This uses OCaml syntax, but hopefully it should be close enough that you’re able to translate).

Edit: Perhaps better to just look at how JSON decoding is implemented directly.

Yes, you are right. It’s a subset of JSON!

Am I doing it right?

Alright, so this is a slightly intricate setup maybe. I’m probably abusing the type system or something.

Imagine that I have a backend and a frontend.

Backend has module MyApi.res, which looks like the following:

type response = { text: string }

let url = "/api/my-api"

let handler = () => {
  // ...
  sendJson({text: "hello"})
}

Frontend has the following:

...
module(MyApi)->apiFetch->Promise.then(r =>
  switch r {
  | Some(data) => Js.log(data.text) // <--- important part, type safety
  | None => ()
  }
)

I imagine apiFetch would look something like this:

module type RestApi = {
  type response
  let url: string
  let handler: () => response
}

let apiFetch = (module(M: RestApi)) => {
  let url = M.url
  // Let's imagine return type for fetch is Promise.t<option<'a>>
  fetch(url)->Promise.thenResolve(r => (r :> option<M.response>))
}

Is what I’m trying to accomplish:

  1. Possible
  2. Stupid

Can you describe verbally what you are trying to accomplish? Is it type-safe backend/frontend communication? If so that should be very doable, e.g. look at Trpc, it should be possible to port that or something similar to ReScript.

Yes, typesafe backend/frontend. Thanks, I’ll take a look.

tRPC seems to be some runtime-checking. I’m merely trying to extract the compile-time type here. Maybe I can make this more clear somehow, hmm…

I updated the example. Can you have a look again?

Look at the RestApi module type. Notice how it has a response type requirement and a handler function requirement (that must return a response)

What I want is that, during compile time, have access to the .text field of the response. In theory, this should be doable since both parts can know about the type at compile-time. The problem I’m facing:

The type constructor M.response would escape its scope

This is not a problem in this scenario but the compiler will still not allow it.

If you change the type coercion to :> MyApi.response, everything works as expected. But then I have to do one fetchApi function for each API endpoint.

Actually, it all boils down to the fact that this is not compiling:

let fetchApi = (module(M: RestApi)): M.response => {
  Js.Obj.empty()->Obj.magic
}

Any ideas around it?

EDIT: Ok apparently I can’t even do this which I think is weird:

module type FooMod = {
  type t
  let foo: unit => t
}

module MyModule = {
  type t = {foo: bool}
  let foo = () => {foo: false}
}

let fetchApi = (module(M: FooMod)) => {
  M.foo()
}

EDIT2: Ok, I think I sort of get it now. The type is bound to the M module variable and what the compiler is basically saying is that the type (bound to the variable) outlives the variable itself. Which I guess makes sense.

You have to make M.response polymorphic:

let fetchApi = (module(M: RestApi with type response = 'response)): 'response => {
  ...
}

Interesting. Can you give an example? The following does not work:

module type FooMod = {
  type t
  let foo: unit => t
}

module MyModule = {
  type t = {foo: bool}
  let foo = () => {foo: false}
}

let fetchApi = (module(M: FooMod with type t = 't)): 't => {
  M.foo()
}

  The type of this packed module contains variables:
module(FooMod with type t = 't)

(The compiler is actually not giving an error on this, it’s just saying that and then saying it can’t continue because of errors)

Right, for unpacking it you’ll need to use a locally abstract type, so it should be something like:

let fetchApi = (type ret, module(M: FooMod with type t = ret)): ret => {
  M.foo()
}