-
Notifications
You must be signed in to change notification settings - Fork 2k
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
Proposal: bind a name to a destructurable place with @ #5471
Comments
Especially if we enforce that the |
FWIW, these are called as-patterns in the ML languages (Ocaml, Haskell etc). (the name "places" is more often used to talk about assignables, like I'm going to pull the answer from these:
Thank you for your well-redacted issue nevertheless |
Thanks so much for the prompt response! ^_^ Part of what I redacted was a discussion of I will look at a PR! Thanks again! |
Adding pattern matching without macros or language support does not seem possible, no.
Even with the name, looking up "as patterns" in any search engine matches a lot of non-references. |
Sorry, just to be clear -- are you describing these as likely prerequisites for a potential path to that functionality in CoffeeScript, or are you saying macros and/or language support for pattern matching are likely out of scope for CoffeeScript? I was under the impression that either of those features were unlikely to be accepted in CoffeeScript, so I was trying to indicate that I would respect that in any further proposals/PRs. I focused on this proposal (gonna try to prototype it now) bc it avoids introducing any new semantics/control flow, and the codegen remains extremely simple, but I am definitely interested in researching further extensions. My vaporware programming language is heavily inspired by CoffeeScript, and the biggest differences are in its more extensive pattern matching and pipelining control flow. I have been heavily inspired by R's magrittr pipelining approach, as well as R's NSE framework for stuff like macros but more introspectable and extensible. A transpiling language like CoffeeScript does seem amenable to macros, but I generally assume most languages are against that sort of thing (although it does mean users have to ask much more from language maintainers). Notably, macros can also be used to implement an extensible compile-time type system (Racket has done incredible work on this). I think JSDoc generation is a fantastic way to interop with typescript communities, but I'm less confident about deeper typescript integration and would love for CoffeeScript to have a type system as flexible as CoffeeScript itself! But I don't know what that would look like at all right now (my vaporware language does not yet have macros for this reason). Anyway, I'll focus for now on a PR for |
More likely would be that someone PRs something that TC39 added in its pattern matching proposal.
I think if TC39 settles on a pattern matching syntax, and someone PRs that with a syntax that doesn't break anything, it has a chance to be included. I am however saying that macros are out of scope. You can take a look at #3171, or the BlackCoffee fork. |
Thanks so much!! :D :D really really appreciate taking the time to elaborate here, especially on how CoffeeScript makes decisions in general, and looking to TC39.
This is what I expected ❤️ but wanted to make sure! Will totally check out those links. Also, I have created a |
Ended up playing around a lot with the grammar, and identified some miscompiles in function param destructuring: # Prior miscompiles (before this commit) from failing to sufficiently differentiate value vs place
# expressions in function params (different than LHS of assignment--cannot assign to existing
# values except this-properties e.g. `@x`).
; coffee -c -b -s --no-header <<EOF
({a: (x)}) -> x
EOF
# (function(arg) {
# var arg, x;
# x = arg.undefined;
# });
; coffee -c -b -s --no-header <<EOF
({["x"]}) -> x
EOF
# (function({["x"]: "x"}) {
# return x;
# });
; coffee -c -b -s --no-header <<EOF
([x[1]]) -> x
EOF
# (function([x[1]]) {
# return x;
# });
# This is an ICE!
; coffee -c -b -s --no-header <<EOF
({x: x[1]}) -> x
EOF
# TypeError: Cannot read properties of undefined (reading 'value')
# at atParam (.../lib/coffeescript/nodes.js:6543:54)
# ... These cases (which currently generate invalid js or ICE) are now recognized as invalid at parse time: COMP=y AST_PATH='.body.expressions[0].body.expressions[0]' coffee semantics.coffee "$(echo -e '({a: (x)}) -> x')"
{
text: undefined,
expected: [ "'IDENTIFIER'", "'JSX_TAG'", "'['", "'@'", "'{'" ]
}
[stdin]:1:6: error: unexpected (
({a: (x)}) -> x This stricter parsing mode should not modify any runtime semantics, but if we want backwards compatibility, it should probably not fail at coffeescript compile time. I am going to split these changes to the parser (with fixes for the miscompiles I found) out into a separate branch, and will propose a PR for that before looking at extending any semantics with |
Have been making a lot of progress on harnesses for testing changes, and optimized jison quite a bit. See #5473. I think the |
I created #5474 for a necessary prerequisite to this work. |
@aurium mentioned |
This is a feature request for new syntax. I believe this would be entirely backwards-compatible.
Problem
Consider the function:
Because
otherFunc
requires both the parent objectx
as well as some of its fieldsa
andb
, we have to perform an additional destructuring step. This can be error-prone, as the namex
must be duplicated for the required boilerplate. It also makes it impossible to write this logic in one line without avoiding destructuring entirely!This syntactic limitation applies equally to all forms of destructuring assignment. The proposed solution frames the existing "destructuring" mechanics in terms of what I'm calling "place expressions", expounding on the second half of "destructuring assignment".
Proposal:
@
to bind a name to a placeAbstractly:
place-expr
) can also be given a name before applying the requested destructuring:place-expr = <name>@<destructuring-expr>
(except for object keys which use@:
)@
was motivated by the@
operator from rust, wherelet
andmatch
introduce similarly "destructurable" place expressions.Concretely:
This new syntax should be entirely backwards-compatible, and will show up in four places:
<name>@<destructuring-expr> = <value-expr>
(<name>@<destructuring-expr>) -> <value-expr>
{<name>@: <destructuring-expr>, ...}
[<name>@<destructuring-expr>, ...]
More concretely:
These four separate places are actually all the same place: a place expression. The new syntax is backwards-compatible because it extends CoffeeScript's simple, elegant, and wholly underrated framework for binding names to values.
Prior Discussion
From searching issues, I have found multiple feature requests which I believe to be the result of this exact conundrum:
This is a pretty clear statement, but notably it was in response to vague mentions of haskell terms without a concrete proposal. The only concrete proposal was an analogy from livescript to use
({bar, baz}:kwargs)
, but it was not explained what that syntax actually meant or what equivalent js was generated.In particular, this issue focused heavily on the immediate case of function arguments, which are much less flexible than other forms of destructuring in CoffeeScript because they need to conform to the limitations of javascript functions. The current proposal instead leverages an existing mechanism in CoffeeScript to directly address the awkwardness this issue tries to describe but fails to generalize.
Add example of destructuring assignment in parameter list to documentation #2663 (comment)
as
patterns are mentioned again, and a haskell code snippet is provided, but no proposal for how to express this in CoffeeScript syntax.Allow to change variables names in destructuring assignements #2879
Background: Place Expressions
This proposal attempts to differentiate itself by characterizing and expanding on the very powerful paradigms CoffeeScript has already developed. While the term "destructuring" has become popular to describe any sort of declarative syntax for extracting fields from a value, the full phrase is "destructuring assignment": and this second task is rarely given as much thought.
Comparison: "destructuring" in js
Let's see how javascript is getting along these days:
It's true that the surface syntax is slightly different, but it would be more appropriate to emphasize their wildly different semantics. This distinction is worth dwelling on.
Binding patterns:
let
andconst
Their "binding" patterns are exposed via a top-level
let
orconst
statement, which imposes the mutability semantics oflet
orconst
onto every attempted name binding. Sinceconst
produces an error at compile time, if there is a mixture of mutable and immutable variables to extract, the programmer has to extract their data in sequential "destructuring" statements:The page then continues without any sense of shame:
Of course, there's a much easier alternative:
Without "destructuring" at all:
The recommended "destructuring" approach takes more lines of code than the old-school version, because it artificially splits the description of the input data across sequential statements which must be executed in order. This tightly couples the structure of the input data to our internal representation. It would seem much more reasonable for the
let
/const
syntax to apply to the right-hand side of the=
!Destructuring: it's not uniform syntax, it's consistent semantics!
javascript has many built-in control structures (especially loops) which will generate a
var
-like binding, or dereference one if it exists already, but do not have the exact same semantics as avar
binding. So of course, the language added this "destructuring" syntax to every control structure:From one point of view, this sounds great! Instead of having to learn the pitfalls of all these implicit binding sites, we can just use destructuring! Indeed, this is exactly what CoffeeScript succeeds at! The language never "binds a variable for you" -- every binding is intentional, and has the exact same semantics because the compiler helps you succeed!
But in javascript, the binding destructuring syntax actually just adds complexity instead of simplifying it, because it has absolutely no bearing on the semantics of how those variables are declared or dereferenced: those mechanics still vary wildly, and you still have to learn the pitfalls of all these implicit binding sites! Importantly, abusing destructuring syntax like this without a consistent semantics only further obscures intent! The reason CoffeeScript destructuring works is because the compiler ensures a consistent semantics for variable declarations! Meanwhile, the default semantics for loop variables in js is to pollute the global namespace!
Destructuring assignment: a self-documenting executable data model
One of the best features of destructuring assignment is how compactly it represents complex queries over unstructured data, by attaching a specific label to each component it extracts! Destructuring assignment attaches meaning to data in an executable specification. Consider the following hypothetical assignment:
I think there's a lot more we could do with place expressions (start adding predicates, transformations, type annotations, dependency injection, ...). But I have been playing around with my own language for a while as a playground for that stuff, and I don't think CoffeeScript really needs much! The strong distinction between place and value expression (and corresponding effort on the compiler to achieve that) are exactly what makes it unique!
The text was updated successfully, but these errors were encountered: