-
Notifications
You must be signed in to change notification settings - Fork 455
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
[Feature request] zero-cost binding to tagged JS objects #5207
Comments
This would be useful in other situations too, I jump through a lot of hoops to bind to Slate's operation set using string matching and |
@TheSpyder the link is broken |
I wasn’t linking to my bindings, those aren’t public (yet). The link is to the source types I’m binding to. The specific line that defines the type is here but it needs context of the rest of the file: The operations are 9 objects that use a shared |
This type of functionality would be incredibly valuable indeed. The type of structure described is used a lot in JS-land, and hassle-free bindings to those types of structures would open up interesting possibilities with ReScript (like dealing with ASTs which ReScript in theory is very very good at, but that's painful/close to impossible to do in a sane way now that every single variant needs to be manually mapped at runtime). I wonder though if it'd be better modelled as a polyvariant? Since Flow/TS models these types of things structurally, I think it'd be valuable to model at least the tag itself structurally with a polyvariant, rather than with a normal variant. |
It looks like polymorphic variants right now are translated to almost what you'd expect: let a = #hello({ "world": 1 }) var a = {
NAME: "hello",
VAL: { world: 1 }
}; To the point that providing a stable encoding (possibly via an let a = @tagged("type") #hello({ "world": 1 }) var a = {
type: "hello",
world: 1
} Or for the long form: @tagged("type")
type ast = [
| #hello({ "hello": string })
] Of course this means that From similar work I did on Caramel, (where |
Seems a must have !
let a = @keys(["id","content"]) #hello({ "world": 1 }) var a = {
id: "hello",
content: { "world": 1}
} where that
type b = | Hello(string) | Foo(int, int)
let b1 = Hello("world")
let b2 = Foo(1, 42) var b1 = {
TAG: /* Hello */0,
_0: "world"
};
var b2 = {
TAG: /* Foo */1,
_0: 1,
_1: 42
}; We can have the same attribute to change keys, if we have different shapes: type b = @tag("id") | @keys(["text"]) Hello(string) | @keys(["n1", "n2"]) Foo(int, int)
let b1 = Hello("world")
let b2 = Foo(1, 42) var b1 = {
id: /* Hello */0,
text: "world"
};
var b2 = {
id: /* Foo */1,
n1: 1,
n2: 42
}; It will have a similar result than the polymorphic variant with type b = @keys(['id', 'content']) | Hello(string) | Foo(int)
let b1 = Hello("Foo") var b = {
id: 0,
content: "Foo"
} Again, your idea feel better, even if I would prefer using regular variant. For some extreme case, we can also imagine that the |
We love the syntax proposed by @Ostera, but since the current poly vars type cannot contain inline record definitions, I assume there will be some semantic changes for it. It feels natural to have tag = "TAG" always present in regular variants and to tell the compiler to use a custom tag. // Assume here is an implicit directive
// @tag("TAG")
type t =
| Foo({ foo: string })
| Bar({ bar: int }) Currently, we are saying users "don't rely on the internal representation". However, It may be better to make the internal representation of a regular variants more predictable rather than treating this as special case. Pros:
Rust's serde_enum provides a good summary of the predictable representation.
|
I'm creating a tool to generate ReScript bindings from |
I'm thinking of a PPX syntax that can be introduced without breaking changes. ex) @tagged(nodeName)
type rec node =
| Text({
nodeName: [#"#text"],
value: string,
})
| H1({
nodeName: [#"h1"],
childNodes: array<node>,
})
|
I just confirmed this works in ReScript v11 input code: @tag("nodeName")
type rec node =
| @as("#text") Text({value: string})
| @as("h1") H1({childNodes: array<node>})
@val external nodes: array<node> = "nodes"
let rec toText = nodes =>
nodes->Belt.Array.reduce("", (text, node) =>
text ++
" " ++
switch node {
| Text({value}) => value
| H1({childNodes}) => toText(childNodes)
}
)
nodes->toText->Js.log And the output: import * as Belt_Array from "rescript/lib/es6/belt_Array.js";
function toText(nodes) {
return Belt_Array.reduce(nodes, "", (function (text, node) {
var tmp;
tmp = node.nodeName === "#text" ? node.value : toText(node.childNodes);
return text + " " + tmp;
}));
}
console.log(toText(nodes));
export {
toText ,
} The result is exactly what I want! Thanks, @cristianoc I will write some bindings for popular parser libraries and report back if I run into any problems. |
The only problem I've noticed so far is that when I wanna reuse a tag name in the code, I have to hardcode it. |
I think this can be considered solved via these recently merged features:
Please feel free to open new issues for anything missing for this workflow to work well. |
Also, if you need a parser library, it'll be well supported in |
I explored the various ASTs on astexplorer.net and saw many possibilities. It would be fantastic to use pattern matching when dealing with trees parsed by parser libraries
But in my understanding, that requires converting JS objects to a record and is converted to a ReScript internal representation at runtime.
this feels unnecessary overhead when writing some bindings for a parser library. Because each item in the tree is already a tagged object.
Imagine a function like this:
And its output, instead of
TAG
, we can match via a tag we specify.I think this can be a more ergonomic approach when writing bindings for parsers.
Currently, I have to rely on a 3rd party ppx like decco for this kind of work. Or please let me know if there is a better way I am not aware of
The text was updated successfully, but these errors were encountered: