Skip to content

RFC: Protected value representation #6738

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

Closed
cometkim opened this issue Apr 21, 2024 · 4 comments
Closed

RFC: Protected value representation #6738

cometkim opened this issue Apr 21, 2024 · 4 comments

Comments

@cometkim
Copy link
Member

cometkim commented Apr 21, 2024

Use symbol for internal value representation which should be protected.

Motivation

  1. Determine a value generated by ReScript
  2. Protect (poly-)variants from outside mutation
  3. Provide efficent shallow equality check (ReScript-aware memo rescript-react#111)

Detailed Design

JavaScript has the standard method for this, Symbol

export const kind = Symbol("kind");
// 0: record
// 1: variant
// 2: polyvar
// ...so on

// hold variant tag
export const tag = Symbol("TAG");
// hold variant value
export const val = Symbol("VAL");

These symbols are assigned to every "object" value that ReScript generates.

It is not assigned to primitive values such as string and number. They are already inherently immutable, and assigning to primitives will invalidate optimization in JS engines.

Record

type t = {
  foo: string,
  bar: string,
};

let t = {
  foo: "foo",
  bar: "bar",
}
import * as S from "internal/symbol";

var t = {
  [S.kind]: 0,
  foo: "foo",
  bar: "bar",
};

Variants

type t = Foo({foo: string}) | Bar({bar: string})

let t = Foo({foo: "foo"})
import * as S from "internal/symbol";

var t = {
  [S.kind]: 1,
  // tag can be replaced by regular field if `@tag()` attribute
  [S.tag]: "Foo",
  foo: "foo",
};

Untagged variants

No changes

Assume they are explicitly opt-out.

Polymorphic variants

type color = [
  | #hex(string)
  | #rgb(int, int, int)
  | #ansi256(int)
]

let color = #hex("ffffff")
import * as S from "internal/symbol";

var color = {
  [S.Kind]: 2,
  [S.Tag]: "hex",
  [S.Val]: "ffffff",
};

External values

FFI allows asserting unsafe values from outside. There are two options:

  1. Assign symbols every time using it
  2. Do nothing

I prefer 2. There should be no cases where the ReScript type is coming from FFI.

Ser/de

TBD

If this cannot be solved, don't touch the current behavior. just adding it will increase the output size but no other problems.

Prior arts

  • React elements
@cometkim
Copy link
Member Author

I will experiment with a new @deriving attr in a branch

@cometkim
Copy link
Member Author

I ran a few more thought experiments, and here is the counter I found:

Symbols are not sent out of bounds without explicit serialization. This potentially creates many problems in web environments with different context boundaries.

For example, React RSC is sent from another boundary. React elements are included in the serialization because it is their knowledge, but there is no way to include ReScript-specific knowledge in the serialization. It all happens implicitly (unfortunately). As a result, any deriving comparison of it with values outside the boundary will fail and fallback to deep-equal.

@cometkim
Copy link
Member Author

cometkim commented May 8, 2024

Closing this, @deriving(shallowEquals) for each record would be better for rescript-lang/rescript-react#111

@cometkim cometkim closed this as not planned Won't fix, can't repro, duplicate, stale May 8, 2024
@cometkim
Copy link
Member Author

cometkim commented May 8, 2024

I think we still need a way to determine which values are ReScript values, but we can't do that before a standardized serialization method.

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

No branches or pull requests

1 participant