Equivalent of `this` reference within a record

Hello! I’m trying to accomplish the equivalent of the following js code, which uses a this reference in a method, but in a rescript record:

const store = {
  price: 2,
  amount: 4,
  total(){
    return this.price * this.amount
  }
}

The closest I’ve managed to get to this in rescript is the following using mutually recursive functions:

let rec total = () => {
  getStore().price * getStore().amount
}
and getStore = () => {
  price: 2,
  amount: 4,
  total: total,
}

Is there a simpler or better solution?

Hi @kindsun

It depends what you’re trying to achieve, but in ReScript it’s not typical to make use of objects that contain this references.

For example, conventionally you might use something like this:

type store = {
  price: int,
  amount: int,
}

let getTotal = store => {
  store.price * store.amount
}

let store = {
  price: 2,
  amount: 4,
}

let total = getTotal(store)

But if you need to have literally that object, then you could use %raw() to escape into regular JS and write it that way:

type store

@send external getTotal: store => int = "total"

let makeStore: (int, int) => store = %raw(`
  function makeStore(price, amount) {
    return {
      price: price,
      amount: amount,
      total() {
        return this.price * this.amount
      }
    }
  }
`)

let store = makeStore(2, 4)

let total = getTotal(store)

Lastly, a bit more verbose syntax can be used to using the @this decorator. A good example is over on the Syntax Lookup page:

2 Likes

kindsun,
Though there are some cases for having methods like youre showing, try separating the data and the behavior:

module Store = {
  // When we say Store we have one piece of data in mind: a record of price and amount.
  // as short hand, with no other types defined in this module, it will often have the name `t`
  type t = {
    price: int,
    amount: int 
  }
  //  If you have a value of type t, we can tell you its total.
  // the total function is not replaceable per store value, but youre welcome to call different functions =)
  let total = (t: t) =>t.price * t.amount
}

let total = Store.total({price: 2, amount: 4})
4 Likes

Thanks @kevanstannard and @mouton! I suspect the simplest solution would be something along the lines of falling down into a %raw block for what I need here.

Since you both made reasonable comments along the lines of separating the logic, let me give some more context. In this particular case I’m handing off the object to an external lib which expects a certain shape js obj. I actually think a module would make the most sense here @mouton but it seems that if I were to pass a module out externally, I would need a way of binding a functor to external code which would then take the module as an argument. I did go down this rabbit hole but couldn’t quite get anything working. I’m staring at the documentation to determine if this is actually a reasonable solution. I can reattempt it in parallel, but is this possible?

Ah yes.
Thats one of those cases all right!

In doing Highcharts bindings I have hit something similar where the their chart configuration takes a pointFormatter function that is later invoked with this bound to a point object. My solution was to make the Highcharts bindings thicker by adding a function to convert this into an explicit argument, and then decorate the call where the non-this function is passed in, and apply convertThisToArgPoint before passing down to JS

  // Highcharts does not pass any parameters to the formatter, but
  // apparently is called within the context of a "Point" object, and seems
  // to require defining an inline function here so can use `this.` references
  // see https://api.highcharts.com/highcharts/tooltip.pointFormatter

  let convertThisToArgPoint: ((. point) => string) => ((unit) => string) = %raw(`
    (f) => {
      return function() {
        return f(this)
      };
    }
  `)
1 Like

I actually think I may have it. The solution ended up being first-class modules which CAN be passed via a normal function. Once I updated my bindings to handle them, things actually worked pretty well.

It’s still rough around the edges in terms of typing, flexibility and completeness, but it is working as expected! Here’s a more complete picture of what I’ve been working on, Mobx bindings:

// Updated bindings
module type Store = {
  let total: unit => int
}
@module("mobx") external extendObservable: ({..}, 'a) => 'a = "extendObservable"
let observable = (module(Store: Store)) => {
  let newStore = Js.Obj.empty()
  let store: module(Store) = module(Store)
  extendObservable(newStore, store)
}
// Store with mutable properties
module Store = {
  let price = ref(5)
  let amount = ref(6)
  let total = () => price.contents * amount.contents
  // ...future functions that modify price & amount
}
let _ = observable(module(Store))
4 Likes

Very cool. I hadn’t thought of FCM for this but in hindsight it fits well.

Ahh I need to read up on that too!
[ the beginner tag on this thread was a lie! ]

lol, I guess it’s all relative, but asking as fundamental a question as “is there a this equivalent” seemed beginner-ish to me. Things just kind of wandered from there. Also, while the bulk of rescript makes intuitive sense to me, anytime I’m up against anything even slightly advanced involving js interop, I feel very much like a beginner!

Thanks for nudging me to think in the direction of modules again! :pray:

1 Like

Ah I thought price and amount had to be plain number fields in this object. If you’d said the only fixed part was the total method, I might’ve suggested this earlier :sweat_smile: