# Bindings for libraries returning random objects

**URL:** <https://discuss.ocaml.org/t/bindings-for-libraries-returning-random-objects/2614>\
**Category:** Learning\
**Tags:** bucklescript\
**Created:** [September 24, 2018, 12:24pm UTC](https://discuss.ocaml.org/t/bindings-for-libraries-returning-random-objects/2614 "2018-09-24T12:24:05Z")\
**Posts on this page:** 7\
**Page:** 1

<div class="post-metadata">

**Author:** ![Danielo\_Rodriguez](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.ocaml.org/danielo_rodriguez/32/1202_2.png) [@Danielo\_Rodriguez](https://discuss.ocaml.org/u/Danielo_Rodriguez)\
**Post date:** [September 24, 2018, 12:24pm UTC](https://discuss.ocaml.org/t/bindings-for-libraries-returning-random-objects/2614/1 "2018-09-24T12:24:05Z")

</div>

Hello,

I want to create bindings for this simple library:

[https://www.npmjs.com/package/docopt](https://www.npmjs.com/package/docopt)

The easy part was declaring the functions to execute and declare that they return a plain JS object (AKA POJO), so far so god. This is what I has:

```ocaml
    type options = Js.t({.}); 
    [@bs.module "docopt"][@bs.val]
    external docopt : string => args = "docopt";

    let parse = str => docopt(str) |> argsFromJs;

```

However, when I want to turn those into an ocaml/reason record is where I start having problems. For example, this is what I tried:

```ocaml
[@bs.deriving jsConverter]
type args = {
    upgrade: bool,
    get: bool
};

let options = Docopt.parse(help) |> argsFromJs;

```

That gives me a type error obviously:

```auto
Error: This expression has type
         Js.t(({.. get: bool, upgrade: bool} as 'a)) => args
       but an expression was expected of type
         ReasonRancherCli.Docopt.args => 'b
       Type Js.t('a) is not compatible with type
         ReasonRancherCli.Docopt.args = ReasonRancherCli.Docopt.args

```

Im not sure how should I do this…  
Should I try to make the bindings as a Functor and provide a type record to it, or what is the correct approach here ?

---

<div class="post-metadata">

**Author:** ![Danielo\_Rodriguez](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.ocaml.org/danielo_rodriguez/32/1202_2.png) [@Danielo\_Rodriguez](https://discuss.ocaml.org/u/Danielo_Rodriguez)\
**Post date:** [September 24, 2018, 3:21pm UTC](https://discuss.ocaml.org/t/bindings-for-libraries-returning-random-objects/2614/2 "2018-09-24T15:21:44Z")

</div>

Well, I’m making some advance…

This works, I get a random JS object from my JS function and I just convert it using the derived converter

```auto
[@bs.deriving jsConverter]
type args = {
    upgrade: bool,
    upgradeFinish: bool,
    get: bool,
};

type a = args;

[@bs.module "docopt"] [@bs.val]
external docopt: string => Js.t({..}) = "docopt";

let parse = str => docopt(str) |> argsFromJs;

```

However, I need to specify a `@bs.as` annotation for upgradeFinish, it should be read from `upgrade-finish` (which is sadly a valid JS property). How can I accomplish this ?

---

<div class="post-metadata">

**Author:** ![Danielo\_Rodriguez](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.ocaml.org/danielo_rodriguez/32/1202_2.png) [@Danielo\_Rodriguez](https://discuss.ocaml.org/u/Danielo_Rodriguez)\
**Post date:** [September 24, 2018, 4:14pm UTC](https://discuss.ocaml.org/t/bindings-for-libraries-returning-random-objects/2614/3 "2018-09-24T16:14:03Z")

</div>

Seems there is no intermediate option.  
If I want to rename things I have to use `@bs.deriving abstract`, but then I can’t convert the entire record, I need to access each property by using `fieldNameGet(record)`. Also I have to modify the docopt function to make it report the args type, otherwhise I can not access the fields. Also, this last approach forces me to declare the type and the function on the same module.  
This is what I have now:

```ocaml
[@bs.deriving abstract]
type args = {
    upgrade: bool,
    [@bs.as "upgrade-finish"]
    upgradeFinish: bool,
    config: bool,
    saveEnv: bool,
    [@bs.as "--environment"]
    enviroment: string,
    print: bool,
    get: bool,
};

[@bs.module "docopt"] [@bs.val]
external docopt: string => args = "docopt";

let parse = str => {
    let arguments = docopt(str);
    if (upgradeFinishGet(arguments))
      "Upgrade finish"
    else
      "Other stuff"
}

```

The last part is the bad one, I want to convert the Js record to a sum type of possible actions. Seems that the only option I have is to use several nested if else. Is there any better way ? Can I convert a record to a sum type on any other way ?

Regards

---

<div class="post-metadata">

**Author:** ![Danielo\_Rodriguez](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.ocaml.org/danielo_rodriguez/32/1202_2.png) [@Danielo\_Rodriguez](https://discuss.ocaml.org/u/Danielo_Rodriguez)\
**Post date:** [September 25, 2018, 8:51am UTC](https://discuss.ocaml.org/t/bindings-for-libraries-returning-random-objects/2614/4 "2018-09-25T08:51:41Z")

</div>

Well, well, I’m very proud of myself.  
Finally I achieved a decent level of abstraction through functors. I have to say that the help I got at [this thread](https://discuss.ocaml.org/t/help-me-to-understand-functors) to understand function was key.

I don’t know why this was not working before but it works now. Obviously I was doing wrong with functors, but I don’t understand or remember what.  
Here is my current bindings implementation

```ocaml
module type T = {
    type t;
};
    
module Parser = (T:T) => {

    
    [@bs.deriving abstract]
    type options = {
        help: bool,
        /* version: Js.nullable(string), */
        options_first: bool,
        exit: bool,
    };
    
    [@bs.module "docopt"] [@bs.val]
    external docopt: (string, options) => T.t = "docopt";
    
    let parse = (~help=true, ~options_first=false, ~exit=true, doc) =>
    docopt(doc, options(~help, ~options_first, ~exit));
}

```

Some things I’m not very hapy with are:

- Don’t know how to accept a nullable string from Ocaml/Reason so I can not accept version field
- I don’t make any type checking on the provided type and I just blindly trust the user
- Users are forced to create a module from my submodule, I don’t know if this can be made simpler

Here is an example usage:

```ocaml
[@bs.deriving abstract]
type args = {
  [@bs.as "--version"]
  version: bool,
  /* Upgrade and upgrade-finish options */
  upgrade: bool,
  [@bs.as "upgrade-finish"]
  upgradeFinish: bool,
  [@bs.as "<stackName>"]
  stackName: string,
  [@bs.as "<imageName>"]
  imageName: Js.nullable(string),
  [@bs.as "<serviceName>"]
  serviceName: string,
  [@bs.as "--environment"]
  enviroment: string,
  /* Config options */
  config: bool,
  saveEnv: bool,
  print: bool,
  /* Read compose files */
  get: bool,
  dockerCompose: bool,
  rancherCompose: bool,
  [@bs.as "--output"]
  output: bool,
  [@bs.as "<fileName>"]
  fileName: string,
};

let parse = str => {
  module D = Docopt.Parser({type t = args})
  let args = D.parse(str)
  Js.log(args);
/* several extra lines to do something useful */
}

```

Regards

---

<div class="post-metadata">

**Author:** ![mseri](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.ocaml.org/mseri/32/91_2.png) [@mseri](https://discuss.ocaml.org/u/mseri)\
**Post date:** [September 25, 2018, 9:16am UTC](https://discuss.ocaml.org/t/bindings-for-libraries-returning-random-objects/2614/5 "2018-09-25T09:16:27Z")

</div>

For nullable strings you could try to use `string option`

---

<div class="post-metadata">

**Author:** ![Danielo\_Rodriguez](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.ocaml.org/danielo_rodriguez/32/1202_2.png) [@Danielo\_Rodriguez](https://discuss.ocaml.org/u/Danielo_Rodriguez)\
**Post date:** [September 25, 2018, 9:23am UTC](https://discuss.ocaml.org/t/bindings-for-libraries-returning-random-objects/2614/6 "2018-09-25T09:23:13Z")

</div>

@mseri that is what I want, but I don’t find a streamline way of converting it to Js.nullable …  
The output object must be a javascript object, so on the type I have to use `Js.nullable(string)` . If I accept an optional version string, how can I convert that to nullable string ?

```ocaml
    let parse = (~help=true, ~options_first=false, ~exit=true, ~version=? , doc) =>
        docopt(doc, options(~help, ~options_first, ~version, ~exit));

```

This is the error I got

```auto
  This has type:
    option('a)
  But somewhere wanted:
    Js.nullable(string)

```

BuckleScript has several ways of converting nullable to option, but doesn’t the opposite does not seems to exist…

---

<div class="post-metadata">

**Author:** ![Danielo\_Rodriguez](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.ocaml.org/danielo_rodriguez/32/1202_2.png) [@Danielo\_Rodriguez](https://discuss.ocaml.org/u/Danielo_Rodriguez)\
**Post date:** [September 25, 2018, 9:35am UTC](https://discuss.ocaml.org/t/bindings-for-libraries-returning-random-objects/2614/7 "2018-09-25T09:35:42Z")

</div>

Ok,

Seems that I have two options now  
I can use `option(string)` on the type declaration or use nullable stuff.  
The advantage of just using option is that it does not require any conversion and no runtime overhead. The problem could be that it is translated to undefined instead of null. In this case, it seems to work fine because the target library seems to don’t care about it being null or undefined, but there are cases where this may not suffice.

So just doing:

```ocaml
    [@bs.deriving abstract]
    type options = {
        help: bool,
        version: option(string),
        options_first: bool,
        exit: bool,
    };
    
    [@bs.module "docopt"] [@bs.val]
    external docopt: (string, options) => T.t = "docopt";
    
    let parse = (~help=true, ~options_first=false, ~exit=true, ~version=? , doc) =>
    docopt(doc, options(~help, ~options_first, ~version, ~exit));

```

Seems to fit the bill for this particular case, but I think it will be more correct to do

```ocaml
    [@bs.deriving abstract]
    type options = {
        help: bool,
        version: Js.nullable(string),
        options_first: bool,
        exit: bool,
    };
    
    [@bs.module "docopt"] [@bs.val]
    external docopt: (string, options) => T.t = "docopt";
    
    let parse = (~help=true, ~options_first=false, ~exit=true, ~version=? , doc) =>
    docopt(doc, options(~help, ~options_first, ~version=Js.Nullable.fromOption(version), ~exit));

```

But the generated JS requires external libraries, and I don’t think I need those…
