Binding to Recursive JS Data Types

I’m trying to write some Screeps bindings to play with Rescript, but I have some fundamental misunderstandings on how this all works.

There are many objects that are “Room Objects” in that they all have a room property and a position, and I tried to model that using the suggestion I received in Modelling Interfaces in ReScript - #2 by yawaramin and relying on module types:

type room
type pos

module type RoomObject = {
  let room: room
  let pos: roomPostion
}
module type Source = {
  include RoomObject
  let energy: float
  let energyCapacity: float
  let id: string
}

//...

which allows me to have a moveTo function typed as

(
    ~creep: Creep.t,
    ~target: module(RoomObject),
    ~opts: options=?,
    unit,
  ) => abs_result = "moveTo"

Unfortunately, now I need to add a property to the room type that is also a room object! The Controller, and I can’t quite get the syntax right to pull this off (or if it even needs to be pulled off)…

Am I approaching this right? Is there maybe a better way to model this? Am I being too specific here? Any help would be super, thanks!

Update: the code so far for a clearer picture of what I’m trying to do.

Inheritance, wohoooo!

not sure if viable, but what about this?

module RoomObject = {
  type t

  @get external effect: t => float = "effect"
  @get external level: t => option<float> = "effect"
  @get external ticksRemaining: t => float = "ticksRemaining"
}

module Source = {
  type t

  @get external energyCapacity: t => float = "energyCapacity"
  @get external energy: t => float = "energy"
  @get external id: t => string = "id"
  
  external asRoomObject: t => RoomObject.t = "%identity"
}


// Imaginary source
@val
external someSource: Source.t = "window.someSource"

someSource->Source.asRoomObject->RoomObject.effect->Js.log

JS output:

var RoomObject = {};

var Source = {};

console.log(window.someSource.effect);

Playground Link

A Source inherits the attributes of a RoomObject, therefore we provide a asRoomObject function that will give you access to the superset functionality of RoomObject.

Your final function would then look like this:

module Creep = {
  type t

  @send
  external moveTo: (
    t,
    ~target: RoomObject.t,
    ~opts: options=?,
    unit,
  ) => abs_result = "moveTo"
}
1 Like

Ugh of course! That actually simplifies a lot of things 🤦

Coming from Elixir, I’d probably try to use a
Protocol for this to
automatically dispatch to the correct asRoomObject function.

defprotocol RoomObject do
  def asRoomObject(object)
end

defimpl RoomObject, for: Source do
  def asRoomObject(object), do: object
end

Is there a similar feature in ocaml/rescript? It is cool if not, but if
I could just get away with writing asRoomObject(thing) instead of
Thing.asRoomObject(thing), I wouldn’t mind.

I think I could probably have a MakeRoomObject functor to at least
keep the repetitive-ness of writing external asRoomObject: t => RoomObject.t = "%identitiy" down :thinking:

UPDATE: My functor:

and module MakeRoomObject: ('a) => IsRoomObject<'a> = (M: RO) => {
  include M
  external asRoomObject: t => RoomObject.t = "%identity"
}
and module Source: {
  type t = {
    energy: float,
    energyCapacity: float,
    id: string
  }
} = MakeRoomObject({
  type t = {
    energy: float,
    energyCapacity: float,
    id: string
  }
})

but because these are all recursive modules, I have no idea how to type the thing :thinking: