A few small questions

Usually Obj.magic is enough for this. Also you can easily find all the places that should be fixed to make your code typesafe. That would be difficult with // @typeless pragma, just imagine how much pain would be to remove it after prototyping :slight_smile:.

3 Likes

I think it’s mostly domain logic vs hacking technical problems (e.g., the UI). Domain code should be as pure as possible and it lends itself well to type-driven programming. The I/O code, not so much.

But of course it’s not as clean-cut when you need the UI and hands-on experience to see if that’s what you need at all. But given that you can mix ReScript and JS in a project, maybe you can do some exploration in JS.

Experience and proficiency with the language is a major part of it I think. With a strong, sound and relatively simple type system you can do a lot of exploration just in your head. Which is a lot more efficient than typing, testing, fixing the inevitable typos and bugs and so on. I don’t think it’s entirely coincidental that ReScript’s precursor was called “Reason” either :wink:

It does take a while to get to that proficiency though, and it’s most likely not for everyone anyway, so there is absolutely a need for something to bridge that gap.

I’ve had the exact same idea. Just a compiler flag to skip the type checking phase, for example. I don’t think that would be possible for native compilation because the type information is needed for code generation. But for targeting JavaScript t might actually be possible.

This is something that could be explored, if someone wants to try.

I’ve had the same idea but for disabling warnings about missing implementations when starting with the interface file. This flag might even already exist and I don’t know about it! ^^

Reviving this thread! :slight_smile:

How can I do something like Partial<> in ReScript?

Let’s say I have let user:User.t = {username:“phil”, phoneNo:“12345”}

I then have a function f, which is going to update the user in the database. It requires an arbitrary number of fields to be specified:

I want to be able to do e.g., f({username:"phil2}) or f({phoneNo:“123”}) or f({usersname:“x”, phoneNo:“y”})

Right now, I’m bypassing typing and just doing {“username”:“phil2”}.

Is it possible to type constrain f such that it takes an argument of Partial<'a>?

I had the same question in my mind when updating a database.
Afaik there is no way without ppx or defining the partial type manually, so I came to this hacky solution:

type x = {
  id: int,
  name: string,
}

let skip = None->Obj.magic

let newX = {
  id: skip,
  name: "test",
}

// insert into ... while skipping "undefined" / None values

That’s a really bad idea. The non-ppx way of doing a Partial in ReScript is to either do the manual work (the boilerplate) with different approaches or use a ppx.

Using a ppx is complicated since ReScript discourages them and most of those ppx come from OCaml, deriving and Jane Street so most of them aren’t optimized for JS output neither. I won’t recommend anything precisely here just because I think is good to know there’s an option and only follow this approach if you really really need it.

The manual approach might be a little time consuming, but nevertheless can bring more safety to your code:

https://rescript-lang.org/try?version=v10.1.2&code=PTAEBUAsFNQZwC4CcCGBLA5pBoBmB7JAdxSQBNQSBPALgCg6BbfMgVwBtYBlZdLBAGKES5UAF5QAbzqhQCKgAdYrONCTipKtQDsUjaDXjI02jABpQCyPm3QAcq0YAjNYfwKEaGwB5ESExgAfAC+MqCcOFrqEpJRuvqGAEQAIigAbmhkiRZWNvaOLkiGdnmhoXQgEDByitAAtDDpVJQoVBVgABTJ+Izo2qDJ-mnQ-WTQcJj9vWOgAqzaAMaeNijsoCgKCkj4KAuQZu2gvQDWsGicGKtGKAjjoGhISNBb4yMIKE6cFtAIC9+-AEoGMw2JxQABJC6rHg3cbgx7PJ6qbTvT6wGJheRKUBxPTozSqJDxAxGfymUKyLHKQkAfSIaAQkBpuVsGlihOJhj8ARy1lsDmcrlJAQpNWxUQ0AFVCR1cfoAaAAD6gaVqADqDMgAAU+dBZbT6YzmbqgaEgA

I recommend learning a bit about the different techniques of modeling your types based on your domain

2 Likes

Actually I don’t like my approach either.
Btw. the ppx-ts (made by @moondaddi) has this feature.

Yes, this is the only ppx that might allow this but again I trend to prefer to suffer the pain of doing it manually before jumping into a ppx.

Can use functors [to] sub in different containers?

https://rescript-lang.org/try?version=v10.1.2&code=LYewJgrgNgpgBAFwJ4Ad4E04F44G8BQciq8CAPAOQCGAfPgL776iSxwAa2cAFOgFxx0ASmw08hYmjgAPLgSJFpA9ADpyASwB2CGgBoJRJMrVkqAJzNUkZAGZQQVHXSKNGzcNHgBJMF3bd5SVJKWi5qBiEmFk84AHkUBHUQTT8AiWQpcmoxHBAEpM0QunpI-FgEOABHAR8VWRxApTgAZn1DAQBtAEYVAAZdOAAmPoBdBiZyuAgBeMTkurkJJoBlEGAYbmahNrgjOAA5ZJgGIA

1 Like

I would model this as a function with optional arguments. E.g.

Users.update(~username="phil2", users)
Users.update(~phoneNo="123", users)

The signature would look like (e.g.):

module Users: {
  type t

  let connect: ... => t
  let update: (~username: string=?, ~phoneNo: string=?, t) => unit
}

Another benefit of this is that it doesn’t need to allocate an object for every update. ReScript compiler turns it into a simple function call and manages passing the right arguments in the right places.

If you are thinking–this is not generated from the User.t type–that’s true. It’s not automated, you would need to write this manually. You could also probably write a script that generates most or all of this boilerplate.

3 Likes

What can I do in ReScript to “simulate” early returns? Long-term, I cannot see myself using the language without early returns.

There are cases where early returns make sense and makes the code way easier to understand, e.g., long functions where, instead of having to think about various cases, can sort them out immediately with one or two early returns.

It is enormously frustrating to me right now to have to handle a railway pipe of 10 stops when it would make way much more sense to just have an if Option.isNone(…) {return whatever} at the top of the function than propagating it 50 lines down through a railway.

Help me overcome this - what am I doing wrong?

One note here is that early returns wouldn’t be as helpful as one might expect, since the language relies on explicit unwrapping of things like options, and that the most powerful types like variants are nominal. So, even if you did if x->Option.isNone return, the type checker wouldn’t consider x as the unwrapped value inside of the option after your return, because you need to unwrap it to get it. Same goes for variants, if you do if someVariant == Hello return, that would still mean someVariant is the same type after the return, not the variant minus Hello.

The closets is probably the if let syntax that has been proposed before, which would let you do some of that unwrapping in a more early return looking way.

With that said, it would be great to gather up some tips and tricks around how people deal with the lack of early return, and maybe even add it to the docs. It’s something most people from JS/TS will encounter.

2 Likes

Thanks. Can you provide a couple of useful patterns that I would want to use instead of early returns?

I really need help with chaining/pipelining.

Let’s save I have n functions. It’s a process pipe. Right now, I’m doing it with Future, but I end up with tons of FuturePromise.fromPromise and flatMapOk and mapOk and map and it just ends up being a confusing mess that happens to work because I iterated until it did.

What’s a good way to set up a process pipe where each stop takes the input from the previous stop and gives output to the next stop? Every stop is async. I don’t want to have error checks linline in the chaining.

Optimally I could do:

…Promise.then(firstStep)->Promise.then(secondStep)->Promise.then(lastStep) but I miss mapOk and mapError from Future doing this so instead I would have to handle error cases in every single stop. The alternative is to use Future, but then I have to do all sorts of magic with FuturePromise etc.

A few years ago, while watching videos of Scott Wlashin, I fell in love with the pipeline oriented workflow. But only in theory. After adopting it to a typescript project it became a bit annoying.

Now I’m using rescript for a lot of private projects and trying to get comfortable with these pipelines again.
And I would say, I’m still very happy with it.

A lot of code looks like this:

UpdatePasswordPayload.validate(args.passwords)
->AsyncResult.flatMap(_ => getUser({userId: args.userId}))
->AsyncResult.flatMap(user => {
  if Bcrypt.compareSync(args.passwords.currentPassword, user.password) {
    updateUser({
      user: {
        ...user,
        password: Bcrypt.hashSync(args.passwords.newPassword),
      },
    })
  } else {
    AsyncResult.error(#Unauthorized)
  }
})

Validate the payload, get the old entity, save the updated one.
It returns errors like ValidationError, NotFound, DBError or the updated user entity.

Before calling this code, I use the same technique to parse cookies, get the current user from a session store etc.

Every step receives some data and returns the parsed / validated / updated data or whatever what makes sense.

While using small, simple functions (anyway a best practice), the code can keep very readable.

1 Like

Can you provide an example where you’d like to return early? I’ve never missed this feature, but I do sometimes use if let in Rust, which as Gabriel mentioned has at least been up for discussion previously.

Re: early return. I find powerful pattern matching to a large extent lessens the need for early return. E.g. in Go I might do:

func f(x, y *int) int {
  if x == nil {
    return 0
  }

  if y == nil {
    return 0
  }

  return x + y
}

In ReScript I would do:

let f = (x, y) => switch (x, y) {
  | (Some(x), Some(y)) => x + y
  | None => 0
}

This is of course a simplified example, but you get the point–pattern matching can destructure pretty complex data and scenarios and enable me to quickly focus on the exact case I need for my happy path, and make everything else the ‘early return’.

2 Likes

Thanks, though this one I can figure out because it’s a rather trivial example. What if I have a, b, c, d, e where a, c and d are async and b, c depends on a, d depends on c and e depends on d? I’m not being facetious here - i feel as if figuring this out is a necessary next step to be able to use ReScript comfortable.