Any record type

Hey,

from time to time I need a function, which accepts any records.
Here is an example:

let getKeysOfRecord = record => {
  record->Obj.magic->Dict.keysToArray
}

type test1 = {
  foo: string,
  bar: string,
  baz: int,
}

type test2 = {
  x: int,
  y: int,
}

These function calls will work:

testToDict->getKeysOfRecord({foo: "hello", bar: "world", baz: 1})->Console.log
testToDict->getKeysOfRecord({x: 1, y: 2})->Console.log

Unfortunately I could also pass anything else:

getKeysOfRecord("hello world")->Console.log

I know, I could also use objects, but I don’t like them:

let getKeysOfRecord = (record: {..}) => {
  record->Obj.magic->Dict.keysToArray
}

getKeysOfRecord({"foo": "hello", "bar": "world", "baz": 1})->Console.log

Or I get rid of the Obj.magic, by creating converters for my records:

let getKeysOfRecord = record => {
  record->Dict.keysToArray
}

external test1ToDict: test1 => Dict.t<'a> = "%identity"
external test2ToDict: test2 => Dict.t<'a> = "%identity"

test1ToDict({foo: "hello", bar: "world", baz: 1})->getKeysOfRecord->Console.log

TLDR;
Would it be feasible and possible to have a syntax for all record types?

let getKeysOfRecord = (record: {|..|}) => {
  record->Record.toDict->Dict.keysToArray
}

Cheers
Daniel

sorry not a full response but have you considered a binding for Object.keys instead of toDict->Keys?

external keys: 'a => array<string> = "Object.keys"

https://rescript-lang.org/try?version=v10.1.2&code=ALBWGcA8GEHsDsBmBLA5gCgN4AIBuBTAJ3GQQC5sAWAGmwFtYATfCgIgEMBXAF1jve7IAxq2wBfAJQAoKfkjci8dgBtsAa3wBPcBQDk7bAF4AfNnaFC7TQB5w3QsnipTh7KwDyAI1D4h3AHQa2qxS3JoADvjY3EbYmFLY2JAUjtzUCdiaFHYOTlJiUsr4MTGuOMnYAMy0WW4AFuwNrAUAUuD+cPDgsEX+yrAYQeDolRLSbR0I3b39g1rDrHX4yv2sY1ITndP4fQPoQ+jc6wyMnEXYcJzwCoQAsvjg4OyoUWUZwIT47H7+QnzhCHw1wyRRi-A0sXQAD8-lc0tgoZxwIp2HR8IYAPwSIymeKJRKg6LINHgWLgADuyG4Qjq2Fh1ziGUSAB9sABGHFuBBCfAhfHYVkAJk5rG4lJ5fPxrPgnIAQssAgBJa7+XgAZXsjgw8GxAGpdW4iSTJYkCkzsISlGiyZTqbSkSjrXipdg1Xx8OhWGsRQBBeAITQMJEmgWu93oK34bEmbCR82sgBygN9-vggdgwfNZv51kYyFwuIASl8fjktegAAYACWW-WwABJMJGxDUM3TlMINIx6FEK9h9UaHhIxNhrAB6PMFjIFM0nM5RH3hcKxZ0fEsBP50AHwIHcEHFejsCGudDR3Hmwnoenw5HcS7XaPYYvfAIOjUCD2nzkABmk-MvDqEJGtC3gAqsiQGolGsTPj8b7cB+6BfjGrB+gGQbgGsMg5pOxjmokmCwQEZZOJ64GOiwbjDvho6OOEPA0YkYSRAA+oYopyNwIb8rgKicOimCAc2jHYAg0ANE4An4LgpTnvy8nYNJ3AALTGERACiBAqgAYrAhB0P44SfFp3AACL4IgXDKHuCn-geQlQZCSmqRpJn+Lp+mquYLxHAA2qwvHKPxrAALoiYkYEQZG6AsUZ0mcg5aJ-rZYgFApY54Qp1ieDwvDwCJYkdkIaiGJgLFKZyzq2dgt73tw6BxbgnKNf27LJQpqWZbZhHrv4JEYKw0BFWoPZrGl8njjl3B5V1OZTXlonwENnaleVMmcrVGbXDFP7DkWvX9Z6xa3mNo5jvNCCzfi1h1UQ9yPM8UTXtgiVRBl5rjrh075FIQA

Didn’t consider this solution but unfortunately the same problem: 'a could be everything :frowning:

the same problem? seems like at least two:

  1. get the keys from any record type
  2. do not get the keys from types that are not records

anyway struck me that this is a very sound function since the Js is pretty complete…Object.keys(3) = [], as youd expect, Object.keys([1,2]) = [“1”, “2”]

The Problem you are facing is “nominal” vs “structural” typing:

  • records are nominal typed in rescript:
    type equality is only given if the name of two types are the same (optimally after resolving some aliases)
  • objects & modules are structural typed
    • two types are equivalent if they are of the same “shape”

This means, there is currently no way to express a type of “any record” in rescript.

Therefore the only solutions have been mentioned already:

  1. convert records to some common type (probably by an identity external)
  2. use objects (or any other structural type)
  3. use a function which holds it’s invariants for any given type
2 Likes

One of ReScript’s stated goals is to get away from the complex types used to model highly dynamic JS programming patterns, and encourage simpler static patterns. So I doubt that the ‘any record’ type would be possible. In this particular example it really seems like the simplest and safest approach would be to manually define a toDict function for the record type to convert it into a Js.Dict.t<_>. This is conceptually similar to defining a JSON codec, and at scale we can imagine that a codegen or PPX approach could help do it automatically for us just by annotating the record type.

2 Likes

Well, the reason I like rescript more then typescript is the simplicity.
Before it turns to a bloated and complex type system, I agree with you.

While I’m still a bit afraid of PPXs, I should consider to use them for some purposes.

Thanks all of you.