Omit attribute from record type

Is this possible?

type test = {
  attr1: option<string>,
  attr2: string
}

// I would like to omit `attr1`
// but Rescript forces me to write `attr1: None`
let aaa: test = {
  attr2: "I'm mr. Meeseeks. Look at me!"
}
type test
@obj external createTest: (~attr1: string=?, ~attr2: string, unit) => test = ""

let aaa = createTest(~attr2="I'm mr. Meeseeks. Look at me!",())

compiles to

var aaa = {
  attr2: "I'm mr. Meeseeks. Look at me!"
};

Edit: Or with a more familiar structure


module Test =  {
  type t
  @obj external make: (~attr1: string=?, ~attr2: string, unit) => test = ""
}

let aaa = Test.make(~attr2="I'm mr. Meeseeks. Look at me!", ())

That’s kinda what @deriving(abstract) is doing as well, but it also introduces a lot more functionality than one would actually need:

module Test = {
  @deriving(abstract)
  type t = {
    @optional attr1: string,
    attr2: string
  }
}

let aaa = Test.t(~attr2="I'm mr. Meeseeks. Look at me!",())
3 Likes

You can also write a constructor function for your type:

let make = (~attr1=?, ~attr2, ()) => {
  attr1: ?attr1,
  attr2: attr2,
};

let test = make(~attr2="", ());

The ?attr1 passes the argument as an optional value. The () is needed because the compiler requires a final positional argument to be able to understand when all named arguments have been passed in.

This will produce a value: {attr1: None, attr2: ""}.

4 Likes

Ever since records as objects was released I’ve often pondered whether to request a way for records with optional fields to omit keys when the value is None. Records are a much nicer way to write bindings than @deriving and @obj, those feel clunky now, but I’m forced to use them when undefined values must be omitted.

3 Likes

couldn’t this be done at a syntax level in rescript?

1 Like

Do you have an example of “at the syntax level”?

I was thinking something similar to how @deriving(abstract) works, rather than syntax, since omitting optional fields would need to happen at multiple levels of the compiler.

For example:

@omitOptions
type myRecord = {
  attr1: int,
  attr2: option<string>,
  attr3: int
};

let value = {
  attr1: 5,
  attr2: None,
  attr3: 6
};

This would then omit attr2 in JS:

var value = {
  attr1: 5,
  attr3: 6
};

value.attr2 will return undefined whether or not the key was defined, so the runtime behaviour should be the same.

no sorry my bad, you would definitely need the type information too, not doable purely on a syntax level.

This was discussed in this issue:

I just realized we can do this with just a normal record type and an @obj external:

type test = {
  attr1: option<string>,
  attr2: string,
}

@obj
external test: (~attr1: string=?, ~attr2: string, unit) => test = ""

let aaa = test(~attr2="I'm mr Meeseeks. Look at me!", ())

let () = switch aaa {
  | { attr1: Some(a1), _ } => Js.log(a1)
  | _ => ()
}

/* JS:

var aaa = {
  attr2: "I'm mr Meeseeks. Look at me!"
};

var a1 = aaa.attr1;

if (a1 !== undefined) {
  console.log(a1);
}

*/

Because of ReScript’s safe handling of undefined as an option type, this is even safe for pattern-matching or record field dereferencing. I’m surprised I didn’t think of it sooner.

EDIT: come to think of it, the @deriving(abstract) PPX can probably be rewritten to do the above transformation and still be backwards-compatible while adding the new feature of making the type a normal record type.

Yes you can do that indeed, I’ve done it a few times, especially when bindings to types you both consume and produce, but it’s not type-safe though!

We might not need that with Hongbo’s proposal:

I hope it’ll land soon!

1 Like

Can you expand on that? The limited usage I show in my example seems to be type-safe.

I mean you can change the type or the obj function and the compiler won’t warn you that the two are out of sync.

Oh yes, true. When you’re already dealing with externals though, it comes with the territory.

of course, but this adds one unsafe layer compared to regular bindings.

1 Like

Am I wrong or is this now possible with the following syntax?

type test = {
  one: string,
  two?: string,
}

example

You can create the record without the key. The key implicitly has type option. If you pattern match the key in the record you will get a warning but you can dereference the key without any warnings.

I couldn’t find any documentation for this. Can someone tell me what’s going on here?

1 Like

You’re right. This is a feature which was introduced in Rescript v10.
Looks like the docs aren’t uptodate yet.

1 Like