Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for @optional in record declarations and remove @obj. #5423

Merged
merged 18 commits into from
Jun 11, 2022

Conversation

cristianoc
Copy link
Collaborator

@cristianoc cristianoc commented Jun 10, 2022

Add support for @optional in record declarations. This supersedes @obj, which is now removed.

This defines a record with mandatory field x and optional field y:

type t = { x : int, @optional y : int }

A value can be constructed as

let v = { x: 3 }

to leave field y unspecified.

Or it can be constructed as

let v = { x: 3, y:4 }

to specify the value of y.

In each case, y is of optional type.

let isNone = v.y == None

The remaining thing to support is setting an optional field with an optional value. This is done by annotating the expression with @optional:

let setToNone = v => {...v, y: @optional None}

This can also be used to assign any optional values:

let setY = (v, nOpt: option<int>) => {...v, y: @optional nOpt}

This can be used to directly represent passing optional props to a component:

// <Comp x=3 y=4 />
Comp.make({x: 3, y: 4})

// <Comp x=3 y=?z />
Comp.make({x: 3, y: @optional z})

In case of inconsistency between implementation and interface, an error message outlines where is the difference in @optional annotations.

jscomp/ml/typecore.ml Outdated Show resolved Hide resolved
@cristianoc cristianoc changed the title Change update for @obj records so the option type is implicit. Add support for @optional in record declarations. Jun 10, 2022
@mununki
Copy link
Member

mununki commented Jun 10, 2022

I love this new syntax. Super awesome!! 👍

@cristianoc
Copy link
Collaborator Author

There's still the limitation that it's not possible to take an existing record and set an optional field to None.

@mununki
Copy link
Member

mununki commented Jun 10, 2022

This @optional is huge improvement in UX and expressivity even though there's the limitation!

@cristianoc
Copy link
Collaborator Author

This is ready to try now: #5423

Notice there's direct support for <C x=?y />.

@mununki
Copy link
Member

mununki commented Jun 10, 2022

Got it. I'll work on it!

@cristianoc cristianoc changed the title Add support for @optional in record declarations. Add support for @optional in record declarations and remove @obj. Jun 10, 2022
@cristianoc cristianoc merged commit a275469 into master Jun 11, 2022
@cristianoc cristianoc deleted the optional_record branch June 11, 2022 02:17
@jfrolich
Copy link

Love the proposal, but I think it's simpler in surface area (and I think easier in usage) if for the optional values always an optional value is expected. So setting y would be

let v = { x: 3, y: Some(4) }

And if y is already optional value

let v = { x: 3, y: y }

Now people only need to learn the optional on the type definition site. And refactors are also easier, because if an optional value is part of a record but not yet @optional, then refactoring it would just be marking optional at the definition site. In the proposed version, we also need to change it everywhere it's being used to add the @optional annotation.

@cristianoc
Copy link
Collaborator Author

Love the proposal, but I think it's simpler in surface area (and I think easier in usage) if for the optional values always an optional value is expected. So setting y would be

let v = { x: 3, y: Some(4) }

And if y is already optional value

let v = { x: 3, y: y }

Now people only need to learn the optional on the type definition site. And refactors are also easier, because if an optional value is part of a record but not yet @optional, then refactoring it would just be marking optional at the definition site. In the proposed version, we also need to change it everywhere it's being used to add the @optional annotation.

Unfortunately that's not expressive enough to write JSX.
rescript-lang/syntax#517 (comment)

@tsnobip
Copy link
Contributor

tsnobip commented Jun 13, 2022

if an optional value is part of a record but not yet @optional, then refactoring it would just be marking optional at the definition site. In the proposed version, we also need to change it everywhere it's being used to add the @optional annotation.

Hmm, I guess you could still have a field being

{
  x: string,
  @optional y: option<int>
}

This way, if you really don't want to touch the existing code base, you could just do this and omit it in the places where you don't want it at all.

@cristianoc
Copy link
Collaborator Author

cristianoc commented Jun 13, 2022

In general, if a field becomes optional, then there's no way to get field update and access work without changes, whatever the proposal.
One can optimize for one and not for the other.
And optimizing means losing the refactoring benefits of compiler errors.
So I think these are the trade offs and parameters one can play with.

@cristianoc
Copy link
Collaborator Author

The current proposal lets you keep all the updates unchanged, and requires changing lookups (as x.foo is now optional).

@jfrolich
Copy link

Ah I understand that this is also determining if a key appears in the object. Got it! In that case I think the current implementation makes sense, but having a syntax sugar would definitely be nice!

@cristianoc
Copy link
Collaborator Author

The task of adding surface syntax is up for anyone interested in having a go.
It should be pretty straightforward unless some issues such as conflicts come up.

@jfrolich
Copy link

Cool. What happens when ALL fields are optional? I don't know if it's possible type system wise, but it would be really nice if you can pass unit in this case, especially when we have a record with optional configuration.

@cristianoc
Copy link
Collaborator Author

Created an issue rescript-lang/syntax#544

@cristianoc
Copy link
Collaborator Author

Cool. What happens when ALL fields are optional? I don't know if it's possible type system wise, but it would be really nice if you can pass unit in this case, especially when we have a record with optional configuration.

It is possible for them to be all nullable. But there's currently no syntax to populate that value.

There's only a dirty trick one can use:

type t = { @nullable x: int }

let empty = { x: @nullable None }

which anyway is not literally the empty object.

@cristianoc
Copy link
Collaborator Author

To have an empty object, one needs some syntax, and a check that the compiler internally does not scream with some assertion failure or something.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants