JavaScript constructor behaviour in ReasonML using BuckleScript


#1

I want to generate javascript function called Publisher from ReasonML so that i can use it in other files as for example:

const publisher = new Publisher("Prasad", "email@email.com", "team@email.com", "rill")
const req = Publisher.toAPI(publisher) //  returns {name: "Prasad", email: "email@email.com", team: "team@email.com", service: "rill"}

To achieve above functionality, I wrote ReasonML code in file named Util.re which is:

type publisher = {
  name: string,
  emailID: string,
  teamEmailID: string,
  serviceName: string,
};

type publisherReqBody = {
  name: string,
  email: string,
  team: string,
  publisher: string,
};

module Publisher = {
  let toAPI = (p: publisher) => {
    name: p.name,
    email: p.emailID,
    team: p.teamEmailID,
    publisher: p.serviceName,
  };
  [@bs.new] external create: unit => publisher = "Publisher";
};

After compilation from ReasonML to JavaScript using BuckleScript, what i got

// Generated by BUCKLESCRIPT, PLEASE EDIT WITH CARE


function toAPI(p) {
  return {
          name: p.name,
          email: p.emailID,
          team: p.teamEmailID,
          publisher: p.serviceName
        };
}

var Publisher = {
  toAPI: toAPI
};

export {
  Publisher ,

}

I am not sure why [@bs.new] external create: unit => publisher = "Publisher"; line not working. I have tried for an hour but no use.

My question:

how to achieve functionality i mentioned in first snippet in JavaScript which is compiled from ReasonML

Thanks a lot!


#2

The @bs.new attribute is used to bind to an existing constructor that has been implemented in JavaScript. It’s not used to create a new constructor function. Reason does not have a way to create a JavaScript constructor function. However, you can write a normal function that will output a plain old JavaScript object (POJO):

let make = (~name, ~emailID, ~teamEmailID, ~serviceName) => {
  "name": name,
  "email": emailID,
  "team": teamEmailID,
  "publisher": serviceName,
};

/* Usage: */

let req = make(
  ~name="Prasad",
  ~emailID="email@email.com",
  ~teamEmailID="team@email.com",
  ~serviceName="rill",
);

If you check the output JS this exports a make function that can be called from other JS files and outputs the POJO with the required fields. See https://bucklescript.github.io/docs/en/object-2#creation for more details on how this works.


#3

I think this speaks also to the different way in which you will see behavior encapsulated in Reason as opposed to JS. The main way to do this is JS is obviously how you described by using a constructor. In Reason the main conduit, from what I have commonly seen, is using modules. From what I know there is a well formed object/class system in OCaml/Reason but BuckleScript uses different syntax for this because of the need to compile to JS.

So there are a couple things I see commonly done in these situations. (1) Create a type t that represents the data shape and any additional functions that act on that shape. (2) Have a make function that instantiates the data shape. From there you can expand upon this more.

Here is a small example:

module Publisher = {
  type t = {
    name: string,
    emailID: string,
    teamEmailID: string,
    serviceName: string,
  }
  
  let make = (~name, ~emailID, ~teamEmailID, ~serviceName) => {
    name,
    emailID,
    teamEmailID,
    serviceName,
  };
  
  let toUrl = t => {
     let { name, serviceName } = t;
     "/" ++ serviceName ++ "/" ++ String.lowercase_ascii(name);
  }
};


let publisher = Publisher.make(~name="Adam", ~emailID="adam@example.com", ~teamEmailID="adam@team.com", ~serviceName="serve");

let pubUrl = Publisher.toUrl(publisher);

playground to fork if you want: https://sketch.sh/s/SQD7hvMhSFDoUDGijwhPOh/

From this point onward you have both an instantiator for this type as well as some behaviors/functions that act on this type that you can pass around your application.

I only mention this to build upon what @yawaramin wrote while also speaking to some idioms I have found in Reason which are quite different from JavaScript and takes some time to get used to.

Not sure if this helps but it took me some time to figure out the module pattern so I figured I would discuss it a bit here.

Further reading: https://github.com/ostera/reason-design-patterns