How to bind this kind of function?

Hi there !

I’m struggling with this kind of binding for the ky HTTP lib. (The wanted case is commented)

I’ve tried to bind the type t directly but later in some code it produces a weak type error. Did I miss something :thinking: ?

Thanks for your help !

Would this work? ReScript Playground

This works only when we use this instance alone, in your example, the response will be the same for each other methods (here is an example).

Per the library docs, doesn’t calling ky return its own custom response type? Perhaps you’d need to model, that, too, e.g this playground.

Yes, I omit this part in my example to keep it basic.

I made pretty much all the bindings except this last one I need for a specific use case I’m working on.

let kyInstance = Ky.Instance.create({prefixUrl: "someBasePath"})

kyInstance("path",  {method: "GET"}).json()
kyInstance.get("path").json()

Both functions are valids, but I don’t known how to do the first one in ReScript :thinking:

This pattern is never going to look and feel very ergonomic in ReScript given the different semantics, but if you really need to support it and are fine with it being slightly awkward, you can add an identity cast to a separately defined function type. Notice asCallable and callable here: ReScript Playground

2 Likes

Yes I know, I wanted to match closely the JS API at first but I think I’ll move to a module way with a type t like in the previous comments.

Thanks for your answers everyone :slight_smile:

1 Like

If type t doesn’t need to reference itself (return itself in this case) then you could make it a function type directly and have both that and @send functions for the other methods. Then you’d get rid of the cast and it’ll look nicer.

2 Likes

Since this is something that comes up from time to time, you can also just add another binding to create that returns the instance typed as a function directly, that then return t when invoked:

Excerpt:

module Ky = {
  module Instance = {
    type options = {method?: string}
    type t

    type instance = (string, ~options: options=?) => t

    @send external get: (t, string) => t = "get"

    type createOptions = {prefixUrl: string}

    @module("Ky") @scope("Instance")
    external createCallable: createOptions => instance = "create"

    @module("Ky") @scope("Instance")
    external create: createOptions => t = "create"

    @send external json: t => JSON.t = "json"
  }
}

let kyInstance = Ky.Instance.create({prefixUrl: "someBasePath"})
let kyInstanceCallable = Ky.Instance.createCallable({prefixUrl: "someBasePath"})

let one = kyInstanceCallable("path", ~options={method: "GET"})->Ky.Instance.json
let two = kyInstance->Ky.Instance.get("path")->Ky.Instance.json

Take away: It’s fine (and encouraged) to create several bindings to the same underlying thing, but with different types as needed. Same JS output, and easy to control if you want the callable or not directly.

And finally, here’s an example when there’s just a single create function bound, that can also be called, or invoked via a get method. This works if you don’t need to return the same type t directly again, and is probably the best given the presented situation: ReScript Playground

Excerpt:


// kyInstance("path",  {method: "GET"}).json()
// kyInstance.get("path").json()

module Ky = {
  module Instance = {
    type options = {method?: string}
    type t

    type instance = (string, ~options: options=?) => t

    @send external get: (instance, string) => t = "get"

    type createOptions = {prefixUrl: string}

    @module("Ky") @scope("Instance")
    external create: createOptions => instance = "create"

    @send external json: t => JSON.t = "json"
  }
}

let kyInstance = Ky.Instance.create({prefixUrl: "someBasePath"})

let one = kyInstance("path", ~options={method: "GET"})->Ky.Instance.json
let two = kyInstance->Ky.Instance.get("path")->Ky.Instance.json

Produces the expected JS.

Take away: Even if you use the type t pattern with methods via @send, type t can be anything. It doesn’t need to be abstract.

2 Likes