ReasonML access JS record field

575 Views Asked by At

Say I have a global object called Example, which has a constructor such that new Example() returns an object with a key called "Messaging"

Say I have the following Reason/Bucklescript Code:

[@bs.deriving abstract] type example = {
  [@bs.as "Messaging"] messaging: string
};

type wrappedExample = Js.t(example);

[@bs.new] external exampleConstructor: unit => wrappedExample = "ExampleThing";

exampleConstructor()#messaging;

This results in:

This expression has type wrappedExample
It has no method messaging

Changing the last line to:

exampleConstructor()##messaging or exampleConstructor().messaging similiarly fails. What is the issue here? How can I access my javascript value?

https://reasonml.github.io/en/try?rrjsx=true&reason=NoAQRgzgdAJgpgJwJYDckDsDmACAhpAFwVwGMCBdbAgTwAc5s4APXAW1oBsGBebAbwBQ2bKEhRcEbACIAsnAgRcmDJimVW8xcqwAubBCIqBAXwDcAgTXrYA7sVr0YAURbsu2XgCloBABTM2TjgASnMBUWh0OBtKZgJEdFwORlcggGEAe3QDBABXMgyEPVz0JAIPAD5be0cXQPdeKTq3OAAVAAsVKTCAlszsonyCQt9ggGINBSUVUyA

1

There are 1 best solutions below

0
On BEST ANSWER

You seem to be conflating a bunch of things here that are similar, but don't really work together.

So first of all, # is used for accessing the field of an OCaml object. ## is used to access the field of a Js.t object, which is not what you have here (I'll explain why in a minute).

[@bs.deriving abstract] is a different concept from Js.t and will not create an object type of any kind, but produces an abstract type (this is what abstract in the bs.deriving annotation hints at). An abstract type has no "structure" in itself, so you can't do anything with it directly. And wrapping it in Js.t does not make it a JS object type, just an abstract type wrapped in Js.t (a Js.t object type is specifically an OCaml object type wrapped in Js.t).

The key to this is that [@bs.deriving abstract] doesn't just create an abstract type, but also a number of functions that operate on it. For every field it creates a getter suffixed with Get, messagingGet in your case, and if mutable a setter suffixed with Set. There's also a creation function named after the type and with the fields as labeled arguments, so in your case you can create an object with example(~messaging="whatever"). See the BuckleScript documentation for details.

Here's a fixed version of your example:

[@bs.deriving abstract] type example = {
  [@bs.as "Messaging"] messaging: string
};

[@bs.new] external exampleConstructor: unit => example = "ExampleThing";

exampleConstructor() |> messagingGet;

If you think this is all very confusing, that's because it is. I have no clue what they're doing anymore, but it seems very messy to me too. I would recommend skipping [@bs.deriving abstract] entirely and instead create the abstract type and accessors yourself using ordinary externals. There's much less confusing magic and terrible naming of things that way.