How to define an "any" type in ReScript

I’m trying to write bindings for a list box control where each column can be of a different type. There can be an arbitrary number of columns. Here is a simplified example. I can’t figure out how to type it.

module DetailsListBox = {
  type column<'a> = {title: string, value: 'a}
  type columns<_> = array<column<'a>> // syntax error
  let intColumn = {title: "number", value: 3}
  let stringColumn = {title: "string", value: "abc"}
  let c: columns = [intColumn, stringColumn] // syntax error
}

I’d like to say “this column can hold any type of value” but can’t figure it out. I tried defining a variant but couldn’t get it to compile. I kind of need the any type from TypeScript.

type columnType =
  | String(string)
  | Integer(int)
  | JavascriptObjectA({..}) // syntax error
  | JavascriptObjectB(Js.Types.t<Js.Types.obj_val>) // syntax error

Here I’m trying to define a variable that can hold any type of {…} object - doesn’t work.

let y: {..} = {"title": "bool", "value": true}
let z: {..} = {"title": "string", "value": "abc"}
let yAndz: array<{..}> = [y, z] // syntax error

Here is one possible solution. Seems a bit hacky.

module DetailsListBox = {
  type column<'a> = {name: string, onRender: 'a => React.element}
  type columnOpaque
  type columns = array<columnOpaque>
  external makeColumn: column<'a> => columnOpaque = "%identity"
  let intColumn = {name: "number", onRender: i => <div> {i->Int.toString->React.string} </div>}
  let stringColumn = {name: "text", onRender: s => <div> {s->React.string} </div>}
  let c: columns = [intColumn->makeColumn, stringColumn->makeColumn]
}

Not sure you can do it in a type safe fashion with all JavaScript objects. But if you know the shape of the possible column types your own solution is pretty close. Something like this should work (link to playground):

type columnType =
  | String(string)
  | Integer(int)
  | Object1({id: string, value: int})

module DetailsListBox = {
  type column = {title: string, value: columnType}

  let intColumn = {title: "number", value: Integer(3)}
  let stringColumn = {title: "string", value: String("abc")}
  let objColumn = {title: "object", value: Object1({id: "abc", value: 3})}

  let columns = [intColumn, stringColumn, objColumn]
}

I should have provided more detail in my initial question. Each column can supply a callback that receives the type of the data in that particular column. So using a variant isn’t helpful - even though I tried to make it work - because each callback will need to handle all the different types. There are those polymorphic variants and extensible types t<+> but I’m not very familiar with them and whether they’d be better than using the opaque type option shown above.


type columnType =
  | String(string)
  | Integer(int)
  | Object1({id: string, value: int})

module DetailsListBox = {
  type column = {title: string, callback: columnType => unit}

  let intColumn = {
    title: "number",
    callback: x =>
      switch x {
      | Integer(n) => Js.Console.log(n)
      | _ => Js.Console.log("not handled by this column")
      },
  }
}

Hi @jmagaram

I’m not quite understanding the binding that you’re trying to create. Do you have some example JS code you can share?

However here’s another syntax that might help.

module type Column = {
  type t
  let name: string
  let onRender: t => string
}

type person = {name: string, age: int}

module NameColumn: Column = {
  type t = person
  let name = "Name"
  let onRender = person => person.name
}

module AgeColumn: Column = {
  type t = person
  let name = "Age"
  let onRender = person => person.age->Js.Int.toString
}

module DetailsListBox = {
  let c: array<module(Column)> = [module(NameColumn), module(AgeColumn)]
}

I keep forgetting about that technique of using modules to simulate interfaces. I’m trying to create a DetailedList with the Microsoft FluentUI React library. To initialize the list in the props you provide an array of columns. Each column can have a callback which allows custom rendering. Now that I look at the Typescript again, I see there are no generics involved and the DetailList allows any everywhere. So I think my problem was on the ReScript side.

interface IColumn {
  onRender?: (item?: any, index?: number, column?: IColumn) => any;
}

In ReScript I want each column strongly typed, like this…

type column<'t> = {
  fieldName:option<string>
  onRender:('t)=>React.element
}

But to create the array of columns to pass into the props, I’ve got to put a bunch of these differently-typed columns into an array. I could do what you suggest, an array of modules, which seems the most type-safe. Another way is this…

type any
type rendererT<'t> = 't => React.element
type rendererAny = any => React.element
type column = {onRender: rendererAny}
external asAnyRenderer: rendererT<'t> => rendererAny = "%identity"
let intRenderer = n => React.string("The number is " ++ n->Int.toString)
let textRenderer = s => React.string("The text is " ++ s)
let columns = [intRenderer->asAnyRenderer, textRenderer->asAnyRenderer]

== UPDATE ==
I think the column renderer function gets passed the entire object for the row, NOT just the data in that particular column. This makes the job easier since every row has the same type of data. Overall I think I can handle this. Thanks for the help.