The big migration thread for ReScript v11 (and uncurried mode)

Here’s a thread we can use where we can post our questions and experiences around migrating to ReScript v11 and uncurried mode. Feel free to cover all aspects of migrating, from actual code changes you had to make, to dependencies that needed updating.

I can go first: I migrated an app consisting of ~700 res files to v11, turning on uncurried mode. An overwhelming majority of the work was just changing (implicit) currying to the new explicit partial application in uncurried mode. Typically looking like this screenshot:

I also had to add a few explicit units to API calls that take trailing units:

Looking forward to hearing your stories! Let’s try and help each other.


I think we are going to try to do some mob coding to port today during our company “Rescript Users Group”

No more dangling unit after optional argument in last position of function definition:

let f = (~a, ~b=?) =>
  switch b {
  | Some(b) => a + b
  | None => a

We talked about it in our engineering forum. We depend on two ppx’s that are probably the most painful to transition from. graphql-ppx and decco. We plan to move from decco to ppx-spice and I started the work to support uncurried mode in graphql-ppx.


Map Defaults: Will the Belt utility functions like etc be ported to prefer uncurried functions, or should we switch to mapU for example? It seems like everything needs to change?

Function Merging It seems like the tooltips in vscode are still eager to combine arguments of functions returning functions, which makes sense for currying but wont any more:

onClearStore: (. t,  ~cb: (. unit) => Js.Promise.t<unit>) => (. unit) => unit


(. ~cb: (. unit) => Js.Promise.t<unit>, unit) => unit

The compiler handles uncurried vs curried for you in uncurried mode, without you having to do anything explicit, with a few smaller exceptions. It essentially compiles all of your dependencies (including Belt) as uncurried if you’re in uncurried mode. So, you don’t need to explicitly use uncurried Belt APIs in uncurried mode for example, you can continue pointing to the regular (curried) ones and the compiler will treat them as uncurried without you having to do anything.

There’s a few exceptions to this where you need to wrap functions for things to work, but they’re quite rare so we’ll see when/if they surface here.

@cristianoc has more details if needed.


The editor tooling should also handle when you’re in uncurried mode and not display dots etc. If that’s not the case and you have a reproduction, please open an issue on the repo.

Any issue with ppx_spice during migration, please let me know. It would be nice to open the issue.

1 Like

I have couple questions on the default uncurried mode:

  1. With ReScript v11 rc4, my top-level project compiles fine with uncurried:true but there are a few external ReScript libraries that I use won’t. Is there a config that I can use to tell the compiler to use uncurried:false for external libraries?

  2. I understand the curried by default is discouraged going forward, but what is the unofficial escape hatch to compile a file in curried mode? I have been using:


at the top of each .res that I want to do currying. Also, even with that, I need to explicitly put . in front of function parameter to make it curried.

Is this the right way to do it?

  1. Is function type definition affected by uncurried mode? ie:
type f = 'a => 'b

would f be type matched with both x => y and (. x) => y ?


This hasnt been my experience so far. looking at rescript-apollo-client i had to switch to Belt.Array.mapU to compile.

I am on a prerelase vscode plugin, not sure if thats new enough, but those tooltips were copy-paste from the editor.

Is there any difference between:

  let fn = (x, y, z) => x + y + z

  let fnA = fn(x, ...)
  let fnB = (y, z) => fn(x, y, z)


Any chance it’s from a namespaced / pinned dependency?
I recall there was something odd when not every dependency in a workspace repo had "uncurried" set to true explicitly, but not sure if that issue still exists.

Alternatively you could wrap it into a function: (item) => item->

If it could be possible to set dependencies to the uncurried mode that would help immensely with backwards compatibility.

The other place I struggle with dependencies is file extensions. It would also help to be able to override the file extension of specific dependencies.

1 Like

What are you talking about? If the compiled file extension, then as I know they inherit the file extension from the root bsconfig.

basically, it would be nice to use the file extension required in the dependency’s bsconfig instead of the root

Hi Yall
A following question from other threads:
Will explicit currying apply to Functors as well?
I cant say I hit it often, but when you dont fully saturate a functor, the warning is really something.


1 Like

Hi !

I started the migration and the watch seems to doesn’t work when I have code error. Any idea ?
Just in case, here is one of my rescript.json file config :

  "$schema": "",
  "name": "@colisweb/rescript-toolkit",
  "jsx": {
    "version": 4
  "sources": [
      "dir": "src",
      "subdirs": true
      "dir": "playground",
      "subdirs": true,
      "type": "dev"
  "package-specs": [
      "module": "es6-global",
      "in-source": false
  "suffix": ".bs.js",
  "namespace": false,
  "uncurried": true,
  "bs-dependencies": [
  "ppx-flags": [
    ["@greenlabs/ppx-spice/ppx", "-uncurried"],
  "warnings": {
    "number": "-44-30-32",
    "error": "+5"
  "bsc-flags": [
    "-open Belt",
    "-open Cx"

It’s a regression of rc-4. I’ll fix it in the next version Watch mode is not working in `11.0.0-rc.4` · Issue #6435 · rescript-lang/rescript-compiler · GitHub.
As a solution you can try using rewatch.


Thanks for your fast reply !

I have some issues with Js.Dict.t and the uncurried mode. I’m trying to upgrade the code based on this coupled with @spice.

For reference, here is the implementation :

module MakeString = () => {
  module Id: {
    type t
    module Dict: {
      type key = t
      type t<'a>
      let get: (t<'a>, key) => option<'a>
      external set: (t<'a>, key, 'a) => unit = ""
      external keys: t<'a> => array<string> = "Object.keys"
      @obj /** Returns an empty dictionary. */
      external empty: unit => t<'a> = ""
      let unsafeDeleteKey: (t<string>, string) => unit
      let entries: t<'a> => array<(key, 'a)>
      let values: t<'a> => array<'a>
      let fromList: list<(key, 'a)> => t<'a>
      let fromArray: array<(key, 'a)> => t<'a>
      let map: ('a => 'b, t<'a>) => t<'b>
      let deleteKey: (t<'a>, key) => t<'a>
  } = {
    type t = string
    module Dict = {
      include Js.Dict

      let deleteKey: (t<'a>, string) => t<'a> = %raw("function (dict, key) {
  const newDict = Object.assign({},dict);
  delete newDict[key];
  return newDict;
      let t_encode = (encoder, dict): Js.Json.t => Js.Json.Object( => encoder(a), dict),

      let t_decode = (decoder, json) => {
        open Spice
        switch (json: Js.Json.t) {
        | Js.Json.Object(dict) =>
          ->Belt.Array.reduce(Ok(Js.Dict.empty()), (acc, (key, value)) =>
            switch (acc, decoder(value)) {
            | (Error(_), _) => acc

            | (_, Error({path} as error)) => Error({...error, path: "." ++ (key ++ path)})

            | (Ok(prev), Ok(newVal)) =>
              let () = prev->Js.Dict.set(key, newVal)
        | _ => Error({path: "", message: "Not a dict", value: json})

  type t = Id.t

  module Dict = Id.Dict

  external make: string => t = "%identity"
  external toString: t => string = "%identity"

The compiler complains about signature missmatch for the t_encode function

  Signature mismatch:
  In module Dict:
  Values do not match:
    let t_encode: ('a => Js.Json.t, Js.Dict.t<'a>) => Js.Json.t
  is not included in
    let t_encode: ('a => Js.Json.t) => t<'a> => Js.Json.t

How could I handle this case ? I tried the dot notation but without success :thinking:

Thanks again !