> My personal suggestion on the proposal would be to use `$` as you've said
> Either way, let's not lose the momentum on this! I'm working on a prototype and proposal for exactly that as we speak, but for the sake of momentum of the prototype, repurposed the capture operator in my initial experimentation. Perhaps I should have framed this as "new feature: tagged-variable captures" *first*, and mentioned "overloading the capture operator" *as a followup* extension of the proposal *second*, instead of the other way around. I got excited by having a working prototype with the existing operator I suppose:D On Wednesday, June 28, 2023 at 7:50:59 PM UTC-5 zachary....@gmail.com wrote: > Perhaps removing the `:` is a bad idea actually? I see now it removes the > consistency on values. Either way, let's not lose the momentum on this! I > feel like we can get this over the line (or put the last nails in its > coffin for good ) > > > On Wed, Jun 28, 2023 at 8:45 PM, Zach Daniel <zachary....@gmail.com> > wrote: > >> I agree with Paul on the specific operator, however I feel like you've >> just done a great thing laying out most if not all of the considerations >> we've had on this conversation to date. I know that these conversations can >> get long and sometimes not produce fruit, but I feel like we should try to >> chase this down and come to a conclusion on the matter if at all possible. >> >> My personal suggestion on the proposal would be to use `$` as you've >> said, and to remove the need for `:` for the case of atoms. >> >> %{$foo, $bar} = %{foo: 10, bar: 10} >> >> %{$"foo", $"bar"} = map >> >> It is a new operator, but it feels expressive to me, and the $ currently >> has no use in mainstream elixir syntax (its used in ets match specs as a >> value, not as an operator). That seems like a good solution to me. >> >> >> On Wed, Jun 28, 2023 at 8:45 PM, Christopher Keele < >> christheke...@gmail.com> wrote: >> >>> > My thoughts on the proposal itself aside, I’d just like to say that I >>> think you’ve set a great example of what proposals on this list should look >>> like. Well done! >>> >>> Much appreciated! >>> >>> > I have an almost visceral reaction to the use of capture syntax for >>> this though. >>> >>> > I think calling the `&…` syntax “capture syntax” is actually >>> misleading, and only has that name because it can be used to construct >>> closures by “capturing” a function name, but it is more accurate to >>> consider it closure syntax, in my opinion. >>> >>> This is a very salient point. How do you feel about introducing a new >>> operator for this sugar, such as $:foo? >>> On Wednesday, June 28, 2023 at 7:41:05 PM UTC-5 Paul Schoenfelder wrote: >>> >>>> My thoughts on the proposal itself aside, I’d just like to say that I >>>> think you’ve set a great example of what proposals on this list should >>>> look >>>> like. Well done! >>>> >>>> I have an almost visceral reaction to the use of capture syntax for >>>> this though, and I don’t believe any of the languages you mentioned that >>>> support field punning do so in this fashion. They all use a similar >>>> intuitive syntax where the variable matches the field name, and they don’t >>>> make any effort to support string keys. >>>> >>>> If Elixir is to ever support field punning, I strongly believe it >>>> should follow their example. However, there are reasons why Elixir cannot >>>> do so due to syntax ambiguities (IIRC). In my mind, that makes any effort >>>> to introduce this feature a non-starter, because code should be first and >>>> foremost easy to read, and I have yet to see a proposal for this that >>>> doesn’t make the code harder to read and understand, including this one. >>>> >>>> I’d like to have field punning, but by addressing, if possible, the >>>> core issue that is blocking it. If that can’t be done, I just don’t think >>>> the cost of overloading unrelated syntax is worth it. I think calling the >>>> `&…` syntax “capture syntax” is actually misleading, and only has that >>>> name >>>> because it can be used to construct closures by “capturing” a function >>>> name, but it is more accurate to consider it closure syntax, in my >>>> opinion. >>>> Overloading it to mean capturing things in a more general sense will be >>>> confusing for everyone, and would only work in a few restricted forms, >>>> which makes it more difficult to teach and learn. >>>> >>>> That’s my two cents anyway, I think you did a great job with the >>>> proposal, but I’m very solidly against it as the solution to the problem >>>> being solved. >>>> >>>> Paul >>>> >>>> >>>> >>>> On Wed, Jun 28, 2023, at 7:56 PM, Christopher Keele wrote: >>>> >>>> This is a formalization of my concept here >>>> <https://groups.google.com/g/elixir-lang-core/c/oFbaOT7rTeU/m/BWF24zoAAgAJ>, >>>> >>>> as a first-class proposal for explicit discussion/feedback, since I now >>>> have a working prototype >>>> <https://github.com/elixir-lang/elixir/compare/main...christhekeele:elixir:tagged-variable-capture> >>>> . >>>> >>>> *Goal* >>>> >>>> The aim of this proposal is to support a commonly-requested feature: >>>> *short-hand >>>> construction and pattern matching of key/value pairs of associative data >>>> structures, based on variable names* in the current scope. >>>> >>>> *Context* >>>> >>>> Similar shorthand syntax sugar exists in many programming languages >>>> today, known variously as: >>>> >>>> - Field Punning <https://dev.realworldocaml.org/records.html> — >>>> OCaml >>>> - Record Puns >>>> >>>> <https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts/record_puns.html> >>>> — Haskell >>>> - Object Property Value Shorthand >>>> >>>> <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer#property_definitions> >>>> >>>> — ES6 Javascript >>>> >>>> This feature has been in discussion for a decade, on this mailing list ( >>>> 1 >>>> <https://groups.google.com/g/elixir-lang-core/c/4w9eOeLvt-8/m/WOkoPSMm6kEJ>, >>>> >>>> 2 >>>> <https://groups.google.com/g/elixir-lang-core/c/NoUo2gqQR3I/m/WTpArTGMKSIJ>, >>>> >>>> 3 >>>> <https://groups.google.com/g/elixir-lang-core/c/3XrVXEVSixc/m/NHU2M4QFAQAJ>, >>>> >>>> 4 >>>> <https://groups.google.com/g/elixir-lang-core/c/OvSQkvXxsmk/m/bKKHbBxiCwAJ>, >>>> >>>> 5 >>>> <https://groups.google.com/g/elixir-lang-core/c/XxnrGgZsyVc/m/1W-d_XAlBgAJ> >>>> , 6 <https://groups.google.com/g/elixir-lang-core/c/oFbaOT7rTeU>) >>>> and the Elixir forum (1 >>>> <https://elixirforum.com/t/proposal-add-field-puns-map-shorthand-to-elixir/15452>, >>>> >>>> 2 >>>> <https://elixirforum.com/t/shorthand-for-passing-variables-by-name/30583>, >>>> 3 >>>> <https://elixirforum.com/t/if-you-could-change-one-thing-in-elixir-language-what-you-would-change/19902/17>, >>>> >>>> 4 >>>> <https://elixirforum.com/t/has-map-shorthand-syntax-in-other-languages-caused-you-any-problems/15403>, >>>> >>>> 5 >>>> <https://elixirforum.com/t/es6-ish-property-value-shorthands-for-maps/1524>, >>>> >>>> 6 >>>> <https://elixirforum.com/t/struct-creation-pattern-matching-short-hand/7544>), >>>> >>>> and has motivated many libraries (1 >>>> <https://github.com/whatyouhide/short_maps>, 2 >>>> <https://github.com/meyercm/shorter_maps>, 3 >>>> <https://hex.pm/packages/shorthand>, 4 <https://hex.pm/packages/synex>). >>>> These narrow margins cannot fit the full history of possibilities, >>>> proposals, and problems with this feature, and I will not attempt to >>>> summarize them all. For context, I suggest reading this mailing list >>>> proposal >>>> <https://groups.google.com/g/elixir-lang-core/c/XxnrGgZsyVc/m/1W-d_XAlBgAJ> >>>> >>>> and this community discussion >>>> <https://elixirforum.com/t/proposal-add-field-puns-map-shorthand-to-elixir/15452> >>>> in >>>> particular. >>>> >>>> However, in summary, this particular proposal tries to solve a couple >>>> of past sticking points: >>>> >>>> 1. Atom vs String >>>> >>>> <https://groups.google.com/g/elixir-lang-core/c/NoUo2gqQR3I/m/IpZQHbZk4xEJ> >>>> >>>> key support >>>> 2. Visual clarity >>>> >>>> <https://groups.google.com/g/elixir-lang-core/c/XxnrGgZsyVc/m/NBkAVto0BAAJ> >>>> >>>> that atom/string matching is occurring >>>> 3. Limitations of string-based sigil parsing >>>> >>>> <https://groups.google.com/g/elixir-lang-core/c/XxnrGgZsyVc/m/TiZw6xM3BAAJ> >>>> 4. Easy confusion >>>> >>>> <https://groups.google.com/g/elixir-lang-core/c/XxnrGgZsyVc/m/WRhXxHDfBAAJ> >>>> >>>> with tuples >>>> >>>> I have a working fork of Elixir here >>>> <https://github.com/christhekeele/elixir/tree/tagged-variable-capture> >>>> where this proposed syntax can be experimented with. Be warned, it is >>>> buggy. >>>> >>>> *Proposal: Tagged Variable Captures* >>>> >>>> I propose we overload the unary capture operator (*&*) to accept >>>> compile-time atoms and strings as arguments, for example *&:foo* and >>>> *&"bar"*. This would *expand at compile time* into *a tagged tuple >>>> with the atom/string and a variable reference*. For now, I am calling >>>> this a *"tagged-variable capture"* to differentiate it from a >>>> function capture. >>>> >>>> For the purposes of this proposal, assume: >>>> >>>> {foo, bar} = {1, 2} >>>> >>>> Additionally, >>>> >>>> - Lines beginning with *# == * indicate what the compiler expands >>>> an expression to. >>>> - Lines beginning with *# => * represent the result of evaluating >>>> that expression. >>>> - Lines beginning with *# !> * represent an exception. >>>> >>>> *Bare Captures* >>>> >>>> I'm not sure if we should support *bare* tagged-variable capture, but >>>> it is illustrative for this proposal, so I left it in my prototype. It >>>> would look like: >>>> >>>> &:foo >>>> *# == **{:foo, foo}* >>>> *# => *{:foo, 1} >>>> &"foo" >>>> *# == **{"foo", foo}* >>>> *# => *{"foo", 1} >>>> >>>> If bare usage is supported, this expansion would work as expected in >>>> match and guard contexts as well, since it expands before variable >>>> references are resolved: >>>> >>>> {:foo, baz} = &:foo >>>> *# == {:foo, baz} = {:foo, foo}* >>>> *# => *{:foo, 1} >>>> baz >>>> *# => *1 >>>> >>>> *List Captures* >>>> >>>> Since capture expressions are allowed in lists, this can be used to >>>> construct Keyword lists from the local variable scope elegantly: >>>> >>>> list = [&:foo, &:bar] >>>> *# == **list = [{:foo, foo}, {:bar, bar}]* >>>> *# => *[foo: 1, bar: 2] >>>> >>>> This would work with other list operators like *|*: >>>> >>>> baz = 3 >>>> list = [&:baz | list] >>>> *# == **list = [**{:baz, baz} **| **list**]* >>>> *# => *[baz: 3, foo: 1, bar: 2] >>>> >>>> And list destructuring: >>>> >>>> {foo, bar, baz} = {nil, nil, nil} >>>> [&:baz, &:foo, &:bar] = list >>>> *# == [{:baz, baz}, {:foo, foo}, {:bar, bar}] = list* >>>> *# => *[baz: 3, foo: 1, bar: 2] >>>> {foo, bar, baz} >>>> *# => *{1, 2, 3} >>>> >>>> *Map Captures* >>>> >>>> With a small change to the parser, >>>> <https://github.com/elixir-lang/elixir/commit/0a4f5376c0f9b4db7d71514d05df6b8b6abc96a9> >>>> >>>> we can allow this expression inside map literals. Because this expression >>>> individually gets expanded into a tagged-tuple before the map associations >>>> list as a whole are processed, it allow this syntax to work in all >>>> existing >>>> map/struct constructs, like map construction: >>>> >>>> map = %{&:foo, &"bar"} >>>> *# == %{:foo => foo, "bar" => bar}* >>>> *# => *%{:foo => 1, "bar" => 2} >>>> >>>> Map updates: >>>> >>>> foo = 3 >>>> map = %{map | &:foo} >>>> *# == %{map | :foo => foo}* >>>> *# => *%{:foo => 3, "bar" => 2} >>>> >>>> And map destructuring: >>>> >>>> {foo, bar} = {nil, nil} >>>> %{&:foo, &"bar"} = map >>>> *# == %{:foo => foo, "bar" => bar} = map* >>>> *# => *%{:foo => 3, "bar" => 2} >>>> {foo, bar} >>>> *# => *{3, 2} >>>> >>>> *Considerations* >>>> >>>> Though just based on an errant thought >>>> <https://groups.google.com/g/elixir-lang-core/c/oFbaOT7rTeU/m/BWF24zoAAgAJ> >>>> >>>> that popped into my head yesterday, I'm unreasonably pleased with how well >>>> this works and reads in practice. I will present my thoughts here, though >>>> again I encourage you to grab my branch >>>> <https://github.com/christhekeele/elixir/tree/tagged-variable-capture>, >>>> compile it from source >>>> <https://github.com/christhekeele/elixir/tree/tagged-variable-capture#compiling-from-source>, >>>> and >>>> play with it yourself! >>>> >>>> *Pro: solves existing pain points* >>>> >>>> As mentioned, this solves flaws previous proposals suffer from: >>>> >>>> 1. Atom vs String >>>> >>>> <https://groups.google.com/g/elixir-lang-core/c/NoUo2gqQR3I/m/IpZQHbZk4xEJ> >>>> key >>>> support >>>> This supports both. >>>> 2. Visual clarity >>>> >>>> <https://groups.google.com/g/elixir-lang-core/c/XxnrGgZsyVc/m/NBkAVto0BAAJ> >>>> that >>>> atom/string matching is occurring >>>> This leverages the appropriate literal in question within the >>>> syntax sugar. >>>> 3. Limitations of string-based sigil parsing >>>> >>>> <https://groups.google.com/g/elixir-lang-core/c/XxnrGgZsyVc/m/TiZw6xM3BAAJ> >>>> This is compiler-expansion-native. >>>> 4. Easy confusion >>>> >>>> <https://groups.google.com/g/elixir-lang-core/c/XxnrGgZsyVc/m/WRhXxHDfBAAJ> >>>> with >>>> tuples >>>> %{&:foo, &"bar"} is very different from {foo, bar}, instead of >>>> 1-character different. >>>> >>>> Additionally, it solves my main complaint with historical proposals: >>>> syntax to combine a variable identifier with a literal must either obscure >>>> that we are building an identifier, or obscure the key/string typing of >>>> the >>>> literal. >>>> >>>> I'm proposing overloading the capture operator rather than introducing >>>> a new operator because the capture operator already has a semantic >>>> association with messing with variable scope, via the nested integer-based >>>> positional function argument syntax (ex *& &1*). >>>> >>>> By using the capture operator we indicate that we are messing with an >>>> identifier in scope, but via a literal atom/string we want to associate >>>> with, to get the best of both worlds. >>>> >>>> *Pro: works with existing code* >>>> >>>> The capture today operator has well-defined compile-time-error >>>> semantics if you try to pass it an atom or a string. All compiling Elixir >>>> code today will continue to compile as before. >>>> >>>> *Pro: works with existing tooling* >>>> >>>> By overloading an existing operator, this approach works seamlessly for >>>> me with the syntax highlighters I have tried it with so far, and >>>> reasonable >>>> with the formatter. >>>> >>>> In my experimentation I've found that the formatter wants to rewrite >>>> *&:baz >>>> *to *(&:baz)* pretty often. That's good, because there are several >>>> edge cases in my prototype where not doing so causes it to behave >>>> strangely; I'm sure it's resolving ambiguities that would occur in >>>> function >>>> captures that impact my proposal in ways I have yet fully anticipated. >>>> >>>> *Pros: minimizes surface area of the language* >>>> >>>> By overriding the capture operator instead of introducing a new >>>> operator or sigil, we are able to keep the surface area of this feature >>>> slim. >>>> >>>> *Cons: overloads the capture operator* >>>> >>>> Of course, much of the virtues of this proposal comes from overloading >>>> the capture operator. But it is an already semantically fraught syntactic >>>> sugar construct that causes confusion to newcomers, and this would place >>>> more strain on it. >>>> >>>> We would need to augment it with more than the meager error message >>>> modification >>>> <https://github.com/elixir-lang/elixir/commit/3d83d21ada860d03cece8c6f90dbcf7bf9e737ec#diff-92b98063d1e86837fae15261896c265ab502b8d556141aaf1c34e67a3ef3717cL199-R207> >>>> in >>>> my prototype, as well as documentation and anticipate a new wave of >>>> questions from the community upon release. >>>> >>>> This inelegance really shows when considering embedding a tagged >>>> variable capture inside an anonymous function capture, ex *& &1 = >>>> &:foo*. In my prototype I've chosen to allow this rather than error on >>>> "nested captures not allowed" (would probably become: "nested >>>> *function* captures not allowed"), but I'm not sure I found all the >>>> edge-cases of mixing them in all possible constructions. >>>> >>>> Additionally, since my proposal now allows the capture operator as an >>>> associative element inside map literal parsing, that would change the >>>> syntax error reported by providing a function capture as an associative >>>> element to be generated during expansion rather than during parsing. I am >>>> not fluent enough in leex to have have updated the parser to preserve the >>>> exact old error, but serendipitously what it reports in my prototype >>>> today is pretty good regardless, but I prefer the old behaviour: >>>> >>>> Old: >>>> %{& &1} >>>> *# !> **** (SyntaxError) syntax error before '}'* >>>> *# !> * | >>>> *# !> * 1 | %{& &1} >>>> *# !> * | ^ >>>> New: >>>> %{& &1} >>>> *# => error: expected key-value pairs in a map, got: & &1* >>>> *# => ** (CompileError) cannot compile code (errors have been logged)* >>>> >>>> *Cons: here there be dragons I cannot see* >>>> >>>> I'm quite sure a full implementation would require a lot more knowledge >>>> of the compiler than I am able to provide. For example, *&:foo = &:foo >>>> *raises an exception where *(&:foo) = &:foo* behaves as expected. I >>>> also find the variable/context/binding environment implementation in the >>>> erlang part of the compiler during expansion to be impenetrable, and I'm >>>> sure my prototype fails on edge cases there. >>>> >>>> *Open Question: the pin operator* >>>> >>>> As this feature constructs a variable ref for you, it is not clear >>>> if/how we should support attempts to pin the generated variable to avoid >>>> new bindings. In my prototype, I have tried to support the pin operator >>>> via >>>> the *&^:atom *syntax, though I'm pretty sure it's super buggy on bare >>>> out-of-data-structure cases and I only got it far enough to work in >>>> function heads for basic function head map pattern matching. >>>> >>>> *Open Question: charlists* >>>> >>>> I did not add support for charlist tagged variable captures in my >>>> prototype, as it would be more involved to differentiate a capture of list >>>> mean to become a tagged tuple from a list representing the AST of a >>>> function capture. I would not lose a lot of sleep over this. >>>> >>>> *Open Question: allowed contexts* >>>> >>>> Would we even want to allow this syntax construct outside of map >>>> literals? Or list literals? >>>> >>>> I can certainly see people abusing the >>>> bare-outside-of-associative-datastructure syntax to make some neigh >>>> impenetrable code where it's really unclear where assignment and pattern >>>> matching is occuring, and relatedly this is where I see a lot of odd >>>> edge-case behaviour in my prototype. I allowed it to speed up the >>>> implementation, but it merits more discussion. >>>> >>>> On the other hand, this does seem like an... interesting use-case: >>>> >>>> error = "rate limit exceeded" >>>> &:error *# return error tuple* >>>> >>>> *Thanks for reading! What do you think?* >>>> >>>> >>>> -- >>>> You received this message because you are subscribed to the Google >>>> Groups "elixir-lang-core" group. >>>> To unsubscribe from this group and stop receiving emails from it, send >>>> an email to elixir-lang-co...@googlegroups.com. >>>> To view this discussion on the web visit >>>> https://groups.google.com/d/msgid/elixir-lang-core/ad7e0313-4207-4cb7-a5f3-d824735830abn%40googlegroups.com >>>> >>>> <https://groups.google.com/d/msgid/elixir-lang-core/ad7e0313-4207-4cb7-a5f3-d824735830abn%40googlegroups.com?utm_medium=email&utm_source=footer> >>>> . >>>> >>>> >>>> -- >>> You received this message because you are subscribed to the Google >>> Groups "elixir-lang-core" group. >>> To unsubscribe from this group and stop receiving emails from it, send >>> an email to elixir-lang-core+unsubscr...@googlegroups.com. >>> To view this discussion on the web visit >>> https://groups.google.com/d/msgid/elixir-lang-core/2b46232e-04f1-4b21-87e6-9c098741cd36n%40googlegroups.com >>> >>> <https://groups.google.com/d/msgid/elixir-lang-core/2b46232e-04f1-4b21-87e6-9c098741cd36n%40googlegroups.com?utm_medium=email&utm_source=footer> >>> . >>> >> -- You received this message because you are subscribed to the Google Groups "elixir-lang-core" group. To unsubscribe from this group and stop receiving emails from it, send an email to elixir-lang-core+unsubscr...@googlegroups.com. To view this discussion on the web visit https://groups.google.com/d/msgid/elixir-lang-core/07aeda87-2b71-4db1-8fad-6d60c0c3d3c2n%40googlegroups.com.