Refactoring help

This code compiles. I think it works, but it feels disgusting. Refactoring advice ?

let http_post = (hostname: string, port: string, path: string, body: string) => {
    let options = { 
        "hostname": hostname,
        "port": port,
        "path": path,
        "method": "POST",
        "headers": {
            "Content-Length": Js.String.length(body) } };

    let p1 = Js.Promise2.make((~resolve, ~reject) => {
        let ans = [];
        let req = Http_Api.Http.request(options, (response) => {
            response->Http_Server_Response.on("data", (x) => {
                Js.Array2.push(ans, x); });
            response->Http_Server_Response.on("end", (_) => {
                resolve(.  Js.String.concatMany(ans))
            })
            });
        req->Http_Api.Http_Client_Request.write(body);
        req->Http_Api.Http_Client_Request.end();
    });

    p1
};

let http_get = (hostname: string, port: string, path: string) => {
    let options = { 
        "hostname": hostname,
        "port": port,
        "path": path,
        "method": "GET",
        // "headers": { }
             };

    let p1 = Js.Promise2.make((~resolve, ~reject) => {
        let ans = [];
        let req = Http_Api.Http.request(options, (response) => {
            response->Http_Server_Response.on("data", (x) => {
                Js.Array2.push(ans, x); });
            response->Http_Server_Response.on("end", (_) => {
                let ans: string = Js.String.concatMany(ans, "");
                resolve(.  ans)
            })
            });
        req->Http_Api.Http_Client_Request.end();
    });

    p1
};
module Http_Client_Request = {
     type t;
     @send external write: (t, string) => unit = "write"
     @send external end: (t, ()) => unit = "end"
     @get external url: (t) => string = "url"
}

module Http_Server_Response = {
    type t;
     @send external write: (t, string) => unit = "write"
     @send external writeHead: (t, int, 'http_headers) => unit = "writeHead"
     @send external end: (t, ()) => unit = "end"
     @send external on: (t, string, 'a) => unit = "on"
}

module Http_Server = {
    type t;
    @send external listen: (t, 'a) => unit = "listen"
}

module Http = {
     @module("http") external createServer: ((Http_Client_Request.t, Http_Server_Response.t) => unit) => Http_Server.t = "createServer"
     @module("http") external request: ({..}, 'callback) => Http_Client_Request.t = "request"
}

module Fs = {
    @module("fs") external readFile: (~name: string, ('a, 'b) => unit ) => unit = "readFile"
}

It seems you’re binding to the node api?
There is rescript-nodejs.

Here is a first approach based on your given example:

module Http_Client_Request = {
  type t
  @send external write: (t, string) => unit = "write"
  @send external end: (t, unit) => unit = "end"
  @get external url: t => string = "url"
}

module Http_Server_Response = {
  type t
  @send external write: (t, string) => unit = "write"
  @send external writeHead: (t, int, 'http_headers) => unit = "writeHead"
  @send external end: (t, unit) => unit = "end"
  @send external onData: (t, @as("data") _, string => unit) => unit = "on"
  @send external onEnd: (t, @as("end") _, unit => unit) => unit = "on"
}

module Http_Server = {
  type t
  // TODO: use concrete type instead of type parameter
  @send external listen: (t, 'a) => unit = "listen"
}

module Http = {
  @module("http")
  external createServer: (
    (Http_Client_Request.t, Http_Server_Response.t) => unit
  ) => Http_Server.t = "createServer"
  type requestOptions = {
    hostname: string,
    port: int,
    path: string,
    method: [#GET | #POST],
    headers?: Js.Dict.t<string>,
  }

  @module("http")
  external request: (
    requestOptions,
    Http_Server_Response.t => unit,
  ) => Http_Client_Request.t = "request"
}

module Fs = {
  @module("fs")
  external readFile: (~name: string, (Js.Exn.t, string) => unit) => unit =
    "readFile"
}

module Example = {
  let send = (~body=?, options) => {
    Js.Promise2.make((~resolve, ~reject as _) => {
      let ans = []
      let req = Http.request(options, response => {
        response->Http_Server_Response.onData(
          x => {
            Belt.Array.push(ans, x)
          },
        )
        response->Http_Server_Response.onEnd(
          _ => {
            resolve(. Js.String.concatMany(ans))
          },
        )
      })
      body->Belt.Option.mapWithDefault((), body =>
        req->Http_Client_Request.write(body)
      )
      req->Http_Client_Request.end()
    })
  }

  let http_post = (hostname: string, port: int, path: string, body: string) => {
    let options = {
      Http.hostname,
      port,
      path,
      method: #POST,
      headers: [
        ("Content-Length", body->Js.String.length->Belt.Int.toString),
      ]->Js.Dict.fromArray,
    }

    send(options, ~body)
  }

  let http_get = (hostname: string, port: int, path: string) => {
    let options = {
      Http.hostname,
      port,
      path,
      method: #GET,
    }
    send(options)
  }
}

Try on the playground

  1. Thank you for taking your time to write helpful and instructive responses.

Yes, I’m rewriting an ocaml http server in rescript and doing so via the (JS) nodejs bindings.

I came across that rescript-nodejs binding, but felt I need to suffer 1-2 cycles to make sure I understand the mechanics of how this “ffi” works.

Not complaining, but definitely feels clunky compared to TypeScript “ffi” where everything is invisible and IntelliJ just auto completes everything. Hopefully everything goes smoothly after this.

While TS and rescript both introduce types into the “js world”. They are fundamentally different in the regard, that ts just adds types “later on” on top of js structures. It’s more like a means to ensure the correct usage of interfaces.

Rescript (in this comparison) is type first. Types are first-class-citizen and it is common (like in other ML languages) to model data by using according types. Many paradigms of functional programming exist in rescript as idioms.

Obviously it’s not all black or white and this is just a very rough comparison. But maybe it sparks an idea.