RFC: New syntax for fixed external arguments (replacing `@as(...) _`)
Maintainer antworten meist innerhalb von 1 Tag
Dieses Issue hat noch niemand übernommen.
Bewertung
- Schwierigkeit
- 5/5
- Geschätzter Aufwand
- Über eine Woche
- Anfängerfreundlichkeit
- 25/100
- Issue-Typ
- Feature
- Klarheit
- Klar beschrieben
- Aktivitätsstatus
- Aktiv
- Bereich
- compilers
Rechercherichtung
Read the RFC’s four syntax proposals and compare their tradeoffs; the payload also points to parser and printer changes and compiler work in #8875 and #8876. Start by reviewing the existing fixed argument parser and printer behavior. Done means a syntax is chosen and its parser and printer changes are specified; this RFC is seeking design feedback, so implementation work is not yet defined.
Vom Indexierungsmodell aus dem Issue-Text verfasst.
Beschreibung
Summary
External bindings sometimes need an argument that is always the same value and that callers never pass, e.g. document.createElement("img") or Object.assign({}, ...). Today such fixed arguments are written as an @as(...) attribute on a _ type:
@send external createImg: (document, @as("img") _) => element = "createElement"
@val external assign: (@as(json`{}`) _, t, {..}) => t = "Object.assign"
This RFC proposes a dedicated syntax for them and presents four options:
%raw("..."): the value as JavaScript source.=value/~label=value: the value as a ReScript literal, in "default argument" style._ = value/~label: _ = value: the value as a ReScript literal, after the_that marks the argument as not passed by callers.fixed value/~label: fixed value: the value as a ReScript literal, after a contextualfixedkeyword.
These are starting points, not a closed list: better proposals are very welcome.
Motivation
The current syntax has several problems:
- The meaning is hidden in an attribute.
@as(...)on_turns a type into a value, which is hard to discover and unlike anything else in the language.@asalso means something quite different on record fields and variant constructors. - Anything beyond strings and integers needs the special
jsontag, e.g.@as(json`{}`)for an empty object,@as(json`null`)or@as(json`false`). This madejsontemplate literals a special case in the compiler. - The value is easy to misread as a type, and the formatter and editor tooling see an attribute rather than a value.
The compiler side is being prepared independently of the syntax: #8875 gives fixed arguments their own parsetree node and removes the special json constant. #8876 adds option 1 as a provisional syntax. Whichever option this RFC settles on, it is a parser and printer change on top of that node.
Options
The examples bind document.createElement("img"), Object.assign({}, ...), history.replaceState(null, "", url) and an @obj option that is always false.
Option 1: %raw(...)
@send external createImg: (document, %raw(`"img"`)) => element = "createElement"
@val external assign: (%raw("{}"), t, {..}) => t = "Object.assign"
@val external replaceState: (%raw("null"), %raw(`""`), string) => unit = "history.replaceState"
@obj external makeOptions: (~objectMode: %raw("false"), ~name: string, unit) => options = ""
The payload is JavaScript source and must be exactly one JavaScript literal.
- Pros
- Reuses an existing construct; no new grammar.
- Clearly an escape hatch, consistent with
%rawelsewhere. - Can express any JavaScript literal, including regexps and bigints.
- Cons
- Strings need two levels of quoting (
%raw(`"img"`)). - The value is a string of JavaScript, so it gets no highlighting or formatting, and mistakes are reported late.
- Encourages raw JavaScript for what are almost always plain constants.
- Strings need two levels of quoting (
Option 2: =value and ~label=value
@send external createImg: (document, ="img") => element = "createElement"
@val external assign: (={}, t, {..}) => t = "Object.assign"
@val external replaceState: (=null, ="", string) => unit = "history.replaceState"
@obj external makeOptions: (~objectMode=false, ~name: string, unit) => options = ""
The value is a ReScript literal: string, number, boolean, null, undefined, array, object or record.
- Pros
- The labelled form mirrors the call site (
~objectMode=false). - Values are ordinary ReScript literals: no double quoting, and the compiler produces the JavaScript.
- The formatter and editor tooling treat the value like any other expression.
- The labelled form mirrors the call site (
- Cons
~x=valueis exactly the syntax of a default argument in function definitions, where the caller may still pass a different value. Here the caller cannot pass it at all.- The unlabelled form
="img"looks unusual. ~x=valueand~x: t=?side by side can be confusing.- Can't express JavaScript-only literals such as regexps.
Option 3: _ = value and ~label: _ = value
@send external createImg: (document, _ = "img") => element = "createElement"
@val external assign: (_ = {}, t, {..}) => t = "Object.assign"
@val external replaceState: (_ = null, _ = "", string) => unit = "history.replaceState"
@obj external makeOptions: (~objectMode: _ = false, ~name: string, unit) => options = ""
The values are ReScript literals, as in option 2.
- Pros
- The closest to today's syntax (
@as(x) _becomes_ = x). It keeps the_, which already says "the caller does not pass this". - The same shape for unlabelled and labelled arguments.
- Values are ordinary ReScript literals, with the same benefits as option 2.
- The closest to today's syntax (
- Cons
- Slightly more verbose than option 2.
~x: _ = valueresembles a typed default argument (~x: int=1) in function definitions.- Can't express JavaScript-only literals such as regexps.
Option 4: fixed value and ~label: fixed value
@send external createImg: (document, fixed "img") => element = "createElement"
@val external assign: (fixed {}, t, {..}) => t = "Object.assign"
@val external replaceState: (fixed null, fixed "", string) => unit = "history.replaceState"
@obj external makeOptions: (~objectMode: fixed false, ~name: string, unit) => options = ""
The values are ReScript literals, as in options 2 and 3. fixed is a contextual keyword: it only has this meaning when a value follows it, which is never valid type syntax today, so types, record fields and variables named fixed keep working.
- Pros
- Says what it means:
fixed {}reads as "this argument is always{}", with no resemblance to default arguments. - The same shape for unlabelled and labelled arguments, and reads well in both.
- Values are ordinary ReScript literals, with the same benefits as option 2.
- Easy to search for and to document.
- Says what it means:
- Cons
- Adds a (contextual) keyword to the language.
- Editor syntax highlighting needs to learn it.
- Can't express JavaScript-only literals such as regexps.
Details common to all options
- Where they're allowed. Fixed arguments are only allowed as direct arguments of an
external, inside the parenthesized argument list, soexternal f: _ = "f"keeps its current meaning. - Optional arguments. A fixed argument can't be optional, because it is always passed.
- Values in options 2 to 4. The value is read as written and isn't type-checked:
{}means an empty JavaScript object, andnull/undefinedmean the JavaScript values. - Migration.
rescript formatrewrites the existing@as(...) _forms to the new syntax automatically. Until then they keep working with a deprecation warning, and they will be removed in a later major version. The stdlib and Belt would be migrated in the same release. - PPX compatibility. PPXs keep seeing fixed arguments in the existing form,
_ [@as ...], so they aren't affected.
Feasibility
All four options have been implemented or prototyped in the parser. Option 1 is in #8876. Options 2 to 4 parse without conflicting with existing syntax, compile to the expected JavaScript, and leave the existing parser and printer test suites unchanged. For option 4, existing code using fixed as a type, field or variable name is unaffected.
Question
Which option do you prefer, and why? Or do you have a better idea?
- Vorherrschende Sprache
- OCaml
- Sterne
- 7.5k
- Forks
- 483
- Ø Merge
- 21 Std. 8 Min.
- Gemergte PRs (30 T.)
- 183
Entwicklungsumgebung
Startet den Dev-Container des Projekts im Browser, mit Ihrem eigenen GitHub-Konto.
- Kein Dockerfile und keine Docker-Compose-Datei
- Hat eine Pull-Request-Vorlage
- Beitragsleitfaden lesen
Erste Schritte
- Lesen Sie das ganze Issue und danach den Beitragsleitfaden des Projekts.
- Schreiben Sie ins Issue, dass Sie es übernehmen — das erspart doppelte Arbeit.
- Forken Sie das Repository und arbeiten Sie in einem Branch.
- Öffnen Sie einen Pull Request, der die Issue-Nummer nennt.
Mehr aus rescript-lang/rescript
-
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 72/100
rescript-lang/rescript#8996 ·
Maintainer antworten meist innerhalb von 1 Tag
-
Schwierigkeit 3/5 1-2 Tage Anfängerfreundlichkeit 70/100
rescript-lang/rescript#8997 ·
Maintainer antworten meist innerhalb von 1 Tag
-
Schwierigkeit 4/5 1-2 Tage Anfängerfreundlichkeit 55/100
rescript-lang/rescript#8995 ·
Maintainer antworten meist innerhalb von 1 Tag
-
Schwierigkeit 3/5 1-2 Tage Anfängerfreundlichkeit 68/100
rescript-lang/rescript#8994 ·
Maintainer antworten meist innerhalb von 1 Tag
-
Issues from differential semantic drift analysis (a7721303 to b123b7c3)Evtl. vergeben @cknitt hat das heute übernommen. Offen
rescript-lang/rescript#8980 · 1 zugewiesene Person ·
Maintainer antworten meist innerhalb von 1 Tag
Alle Issues in rescript-lang/rescript
Ähnliche Issues
-
clang:frontend code-cleanup HLSL
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 74/100
llvm/llvm-project#235100 · 1 Kommentar ·
Maintainer antworten meist innerhalb von 1 Tag
-
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 83/100
objectionary/phino#2075 ·
Maintainer antworten meist innerhalb von 1 Tag
-
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 75/100
Maintainer antworten meist innerhalb von 1 Tag
-
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 83/100
Maintainer antworten meist innerhalb von 1 Tag
-
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 68/100