-
Notifications
You must be signed in to change notification settings - Fork 463
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
Introduce untagged variants. #6103
Conversation
8e429c4
to
044624d
Compare
1fed32b
to
ad3e1d8
Compare
Unmarshalling Binary Data and Computing the Sum of an OCaml ListIn this post, we'll explore how to read binary marshalled data produced by an OCaml compiler and perform high-level list operations on it. Here's a step-by-step explanation of the code discussed in the conversation above: First, we have a native OCaml program that creates a list of integers, marshals it to a string, and saves that string into let foo v =
let s = Marshal.to_string v [Compat_32] in
let ch = open_out_bin "aaa.marshal" in
let () = output_string ch s in
close_out ch
foo [1;2;3;4;5] Next, we read the marshalled file, unmarshal it, and pass it to the sum function. The sum function operates at a high level on lists, but its definition uses the runtime representation that corresponds to OCaml's runtime representation for lists: let s = caml_read_file_content("./aaa.marshal")
@unboxed
type rec myList<'a> = | @as(0) Empty | Cons((unknown, int, myList<'a>))
let v: myList<int> = unmarshal(s)
let rec sum = l =>
switch l {
| Empty => 0
| Cons((_, i, l)) => i + sum(l)
}
Js.log2("v", v)
Js.log2("sum:", sum(v)) To see what the runtime representation looks like, the first log gives:
The sum function walks the runtime representation directly and gives the correct sum This approach demonstrates a neat technique for working with OCaml's runtime representation to manipulate lists while maintaining high-level abstractions. |
Just disable the type error for now.
no other blocks when there's an unknown
And add built-in knowledge that Js.Dict.t is an object.
ad3e1d8
to
7781a38
Compare
Can we use |
Perhaps, but we need some measures. Perf etc. |
just tested on Node.js v18.14.0 (V8 v10.2)
test.mjsimport b from 'benchmark';
const { Benchmark } = b;
const suite = new Benchmark.Suite();
const x = [1, 'a', null];
suite.add('x instanceof Array', () => {
void (x instanceof Array);
});
suite.add('Array.isArray(x)', () => {
void (Array.isArray(x));
});
suite.on('cycle', (event) => {
console.log(event.target.toString());
});
suite.run(); |
Tested using another framework on Node.js and Bun
test2.mjsimport { Bench } from 'tinybench';
const bench = new Bench();
const x = [1, 'a', null];
bench.add('x instanceof Array', () => {
void (x instanceof Array);
});
bench.add('Array.isArray(x)', () => {
void (Array.isArray(x));
});
await bench.warmup();
await bench.run();
console.table(
bench.tasks.map(({ name, result }) => ({
'Task Name': name,
'Average Time (ps)': result?.mean * 1000,
'Variance (ps)': result?.variance * 1000,
})),
); |
Thanks! |
Good! I think it's OK |
Merged. |
The other reason to not use There are also edge cases like What may be particularly useful in future is the generic I will admit that some of this relies on modern browsers, so if IE support is still important for some reason there is a less capable but IE-compatible version from our previous release. |
That's great thanks. |
That’s We don’t have a generic way to check object shapes, for everything TypeScript we trust the type system, but it does come up in two places:
We do have object equality code but I don’t think that’s really what you’re looking for 🤔 |
My interest is in the use cases you found. And the reply answers it perfectly. |
Introduce untagged variants (see forum discussion).
In this guide, we'll explain how to work with untagged variants in ReScript using the @unboxed attribute. This new feature allows you to eliminate the need for tags when working with different types of data, providing better JavaScript interop. We'll go through several examples to help you understand how to use this feature.
To define an untagged variant, use the
@unboxed
attribute followed by the type definition. For example:You can use pattern matching with untagged variants just like with ordinary variants. Here's an example:
You can use untagged variants with nested types and recursive types, as shown in the following examples:
In some cases, you might have overlapping cases like a string or a number being used as both a literal and a payload. You can handle these cases using untagged variants as well:
These examples should help you understand how to work with untagged variants in ReScript using the
@unboxed
attribute. The key takeaway is that this new feature provides a simpler and more expressive way to handle different types of data without using tags, while still maintaining the necessary level of type safety and pattern matching capabilities.Semi-formal description
Literals are:
true
,0
,3.14
,"abc"
,A
(a single payload variant with no arguments),null
,undefined
.Block kinds are:
int
,float
,string
,object
,array
unknown
. Whereobject
includes cases with multiple payloads, as inX(int, string)
and inline-records, as inX({x:int, y:string})
, and array is a single payload with array type. Instead,unknown
is any other case with 1 payload which is not one of the 3 base types listed above.Restriction on blocks:
unknown
, then it must be the only case in the blocks.object
orarray
.Runtime checks that need expressing:
isSomeLiteralCase
checks if the value is one of the literals and not one of the blocks.isLiteralCase
matches individual literals using ===.isBlockCase
matches individual blocks using typeof.The
unknown
case always ends up in the final else ..., so it does not need any specific runtime type checks.Some details by example:
isSomeLiteralCase(true | 0 | A | C({x:int}))
checks if the value is one of the literals (true
,0
,A
) and not one of the blocks (C({x:int})
). Expressed astypeof x !== "object"
.isBlockCase(int)
istypeof x === "number"
, andisBlockCase(C({x:int, y:string}))
istypeof x === "object"
.