Why is gentype .gen.tsx complicating my functions with curry helper?

I noticed something weird about my .gen.tsx files. Why is it wrapping all of my (arity >= 2) functions with this Curry helper?

Client.gen.tsx

// @ts-ignore: Implicit any on import
import * as Curry__Es6Import from 'rescript/lib/es6/curry.js';
const Curry: any = Curry__Es6Import;

// @ts-ignore: Implicit any on import
import * as ClientBS__Es6Import from './Client.bs';
const ClientBS: any = ClientBS__Es6Import;

import type {Headers_t as Fetch_Headers_t} from '../src/shims/Fetch.shim';

import type {Map_String_t as Belt_Map_String_t} from '../src/shims/Belt.shim';

export type t = { readonly baseUrl: string; readonly headers: Belt_Map_String_t<string> };

export const make: () => t = ClientBS.make; // ✅ good

export const headers: (client:t) => Fetch_Headers_t = ClientBS.headers; // ✅ good

export const setBaseUrl: (client:t, baseUrl:string) => t = function (Arg1: any, Arg2: any) {
  const result = Curry._2(ClientBS.setBaseUrl, Arg1, Arg2); // ❓why
  return result
};

export const setHeader: (client:t, param:[string, string]) => t = function (Arg1: any, Arg2: any) {
  const result = Curry._2(ClientBS.setHeader, Arg1, Arg2); // ❓why
  return result
};

I think it should be as simple as linking the uncurried versions from the .bs.js file -

// …
export type t = { readonly baseUrl: string; readonly headers: Belt_Map_String_t<string> };
export const make: () => t = ClientBS.make;
export const headers: (client:t) => Fetch_Headers_t = ClientBS.headers;
export const setBaseUrl: (client:t, baseUrl:string) => t = ClientBS.setBaseUrl // ✅
export const setHeader: (client:t, param:[string, string]) => t = ClientBS.setHeader // ✅

Client.bs.js already includes uncurried versions -

function make(param) {
  return {
          baseUrl: "",
          headers: Belt_MapString.fromArray([[
                  "Content-type",
                  "application/json"
                ]])
        };
}

function headers(client) {
  return new Headers(Belt_MapString.toArray(client.headers));
}

function setBaseUrl(client, baseUrl) {
  return {
          baseUrl: baseUrl,
          headers: client.headers
        };
}

function setHeader(client, param) {
  return {
          baseUrl: client.baseUrl,
          headers: Belt_MapString.set(client.headers, param[0], param[1])
        };
}

Here’s the original Client.res file -

@gentype
type t = {
  baseUrl: string,
  headers: Belt.Map.String.t<string>,
}

@gentype
let make = (): t => {
  {
    baseUrl: "",
    headers: Belt.Map.String.fromArray([("Content-type", "application/json")])
  }
}

@gentype
let headers = (client: t) => {
  client.headers->Belt.Map.String.toArray->Fetch.Headers.fromArray
}

@gentype
let setBaseUrl = (client: t, baseUrl: string): t => {
  {
    ...client,
    baseUrl: baseUrl
  }
}

@gentype
let setHeader = (client: t, (header: string, value: string)): t => {
  {
    ...client,
    headers: client.headers->Belt.Map.String.set(header, value)
  }
}

It’s a fair question.
The reason is that given that type alone you have no way of knowing whether or not the implementation inside the .bs.js uses curry (change the code a bit, and it will). And genType does not have access to the actual implementation.
With uncurried mode in the v11 early test releases of the compiler, all of that is gone. Just because curried functions are gone.

2 Likes

Can you explain what you mean by this?

Any guidance on testing this with v11?

  1. a function with 2 args can be written as x => { Js.log("something"); y => x+y } See https://rescript-lang.org/try?version=v10.1.2&code=DYUwLgBAZg9jEC4IAoCWA7MAaCGwEoIBeAPl02IgA9iyBvCAKQGcA6YGAc2QCJmYAtuAAWGTj3wBuCAE9a1ANRyAvkA

  2. try the same example in the playground with v11 settings

If you call it from JS, you need to use the curry module, or else you’d need to know whether it takes first 1 then then the 2nd argument or both at once.

In uncurried mode there’s no such ambiguity: the arity is explicit.

Thanks for the continued support. It’s very motivating to know I’m not alone in attempting to undertake learning ReScript.

Without considering gentype, the exported functions in Client.bs.js are already uncurried. The consumer of this module should be able to call them as ordinary n-ary functions. … right?

A workaround I’m exploring is using TS to generate a .d.ts file from the Client.gen.tsx file -

tsconfig.json

{
  "compilerOptions": {
    "target": "esnext",
    "lib": ["esnext"],
    "allowJs": false,
    "strict": true,
    "module": "esnext",
    "isolatedModules": true,
    "emitDeclarationOnly": true,
    "declaration": true,
  },
  "include": ["src"],
}

Which produces a desirable Client.gen.d.ts -

import type { Headers_t as Fetch_Headers_t } from '../src/shims/Fetch.shim';
import type { Map_String_t as Belt_Map_String_t } from '../src/shims/Belt.shim';
export type t = {
    readonly baseUrl: string;
    readonly headers: Belt_Map_String_t<string>;
};
export declare const make: () => t;
export declare const headers: (client: t) => Fetch_Headers_t;
export declare const setBaseUrl: (client: t, baseUrl: string) => t;
export declare const setHeader: (client: t, param: [string, string]) => t;

Do you see any drawbacks to this approach?

1 Like

That should work just fine.

I should have been more precise: when calling a function of that type from JS one might have to use the curry module in special corner cases. Not in the example discussed here.

1 Like