Had a new idea, so necro'ing this thread with another proposal for a "tagged variable capture" syntax, this time by overloading another existing operator.
*Problems addressed from previous proposal* - The overloading the capture operator was an unpopular choice (ie &:foo # => {:foo, foo}) - A new operator adds syntactic overhead to the language - The more popular choice for new operator, $ (dollar sign), conflicts with the popular expectation that future type system improvements may want to use it - Of the other remaining new operator choices, - ` (tilde)'s association with "unquoting" in LISPs kind of makes sense-ish in this context, though is awkwardly hard to parse visually - ? (question mark) as a sigil here is prominent visually but overloaded conceptually by other conditional/optional constructs in other languages There was other feedback in this thread, less about a "tagged variable capture" construct and more about how folk who really want some sort of field punning want it to be either barewords (JS)-style syntax or keywords (ruby)-style syntax; this new proposal still does not address that. *Problems remaining from previous proposal* - Many still probably just want barewords or keywords style syntax for field punning - Overloading an existing operator adds semantic overhead to the operator, and thereby the language *New proposal: Overloading the Module Attribute Operator* I was learning another language that uses @field as syntactic sugar, and it occurred to me I'd passed over @ as a candidate for this syntax. I realized that the module attribute operator is - An operator already associated with compile-time expansion - An operator already associated with assigning/reading values - An operator with the correct associativity for this task - An operator that currently raises at compile-time if used in this way - So we can know the syntax for this new usage of it is not present in any existing Elixir programs Again, my goal with this proposal is to support field punning in a way that may not mirror other languages, but works with Elixir idioms and - Can support both strings and atoms as keys - Makes key typing syntactically explicit - Can work in list, tuple, and map container literals - Allows mixing strings/atoms inside containers - Can work in pattern matches and guards - Works with the pin operator - Usage in map literals cannot be easily confused with tuple literals - Works seamlessly with existing tooling, typechecking, etc Another perk is that it's trivial to implement support for it, and the pin operator, in just 4 significant lines of code <https://github.com/christhekeele/elixir/commit/24c472a96e681fb6ef9686d05861bde052c83ce3> . I want to re-open this discussion around a tagged-tuple variable capture feature using it, as I really like how this reads (compared to past proposals): @:atom / @"foo" . I still feel like this implementation has potential and that the full feature list above is worth the tradeoff of overloading an existing operator. Nothing from the original proposal has otherwise changed, just the operator in question. But, to reprise my examples from earlier so newcomers to the thread can get a feel for what I mean by "tagged-tuple variable capture": *Examples* 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. *Tagged-Tuple Variable Capture Literals* are what I'm calling an operator that accepts a string or an atom literal, and expands to a two-tuple with the literal as the first element, and inserts a reference to the variable named by the literal as the second element. When this happens at the right point during expansion, it naturally allows for field punning inside maps and other constructs, a commonly requested Elixir feature. Today I'm proposing overloading the module attribute operator @ with this new functionality when given a string literal, atom literal, or a pin in front of either: *Tagged Tuple Variable Capture Literals* @:foo # == *{:foo, foo}* # => {:foo, 1} @"foo" # == *{"foo", foo}* # => {"foo", 1} 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 *Tagged Variable Literals in **Lists* Since tagged variable 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} *Tagged Variable Literals in **Maps* This expression is allowed 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} *Destructuring params in Phoenix* def handle_in(event, %{@"chat", @"question_id", @"data", @"attachment", ...}, socket) do something_with(chat, question_id) ... end *Pin Operator Compatibility* foo = 1 @^:foo = {:foo, 1} *# == {:foo, ^foo} = {:foo, 1}* # => {:foo, 1} @^:foo = {:foo, 2} *# == {:foo, ^foo} = {:foo, 2}* *# !> ** (MatchError) no match of right hand side value: {:foo, 2}* On Friday, June 30, 2023 at 1:01:45 PM UTC-5 Christopher Keele wrote: > Here's a summary of feedback on this proposal so far. > > *Concerning the actual proposal itself* (ie, &:foo # => {:foo, foo}): 2 > against re-using the capture operator. > > *Concerning the followup of a dedicated operator* (ie, $:foo # => {:foo, > foo}): roughly 2 for and 1 against (because it was not barewords), no > major notes. > > - Can support strings, atoms > - Allows mixing strings/atoms > - Key typing explicit > - Works awkward with the pin operator > - Does not look like tuples > - Adds new syntax outside of collection constructs, for working with > tagged tuples for good/evil > > Overall, doesn't seem wildly popular, but I do think it does handle most > of the problems of previous discussions well. > > It does not cleanly map to anything in other languages, but I view that as > a pro because we want something more expressive than what they provide. I > feel like that is part of the lack of enthusiasm, though: people want > whatever feels most intuitive to them from other languages they've worked > with, and as little new syntax to the language as possible. That's a hard > needle to thread. > > > *Discussion on this thread about other proposals:* > > - *"Barewords"* (ES6-style): %{foo, bar} People still want this, it is > still tricky for the usual reasons, and I don't *think* much new has > been contributed to the discussion in this thread. > - Can only support strings or atoms > - No mixing strings/atoms > - Key typing hidden > - Confusable with tuples > - A new thing I've realized in my prototype is that it does work > better with the pin operator than others, something I haven't seen > discussed. > - "Unmached pair" (*Ruby style*): %{foo:, bar:} This has always been > on the table but I do see more support for it than in past discussions. > Also, personally, my least favorite. > - Lacks string support > - No mixing strings/atoms > - Key typing visually clear > - Does not look like tuples > - Pin operator problems > - "*Pair literals*": %{:foo, "bar"} Some people seem to find this too > magical, some prefer this over my proposed dedicated operator that would > make it more explicit. > - Can support strings, atoms > - Allows mixing strings/atoms > - Typing visually clear > - Does not look like tuples > - Pin operator problems > > > *New proposals:* > > - Adding support *at the tooling level*: 2 against (adding my voice > here). As was well-said, why would I write something that's not the > language's syntax? > - Austin had an idea with using my operator *immediately alongside* > the map % literal to apply it to the whole expression. There are > problems with that, but > - This in turn gave me an idea for a slightly new syntax we could use > to support barewords better. I guess call them *qualified* *barewords* > or something for now: > - %{foo, bar}: barewords string keys > - %:{foo, bar}: barewords atom keys > - %Struct{foo, bar}: barewords struct keys > - Initial thoughts: > - Can support strings and atoms > - No mixing strings/atoms > - Key typing explicit, visually clear but hidden for strings > - Sometimes looks like tuples for strings > - Plays nice with pin operator > - Still requires exploration with existing tokenization rules, > mixing rules, map update syntax > > > I'd love to see someone champion any one of these syntaxes, put up some > prototypes, get into the details of syntax ambiguities, and see dedicated > discussions of pros/cons of explicit proposals in individual threads! > > I may continue investigating a prototype for pair literals (%{:foo, "bar"}) > or qualified barewords (%:{foo, bar}) as I get bolder with the erl source > code. > > I welcome further discussion about the tagged-variable literal operator > ($:foo > # => {:foo, foo}) in this thread, but probably won't contribute too much > more to discussion on other proposals, as I want to focus on concrete > proposals and tinker with prototypes for the more promising follow-ups! > Thanks for the discussion, all! > On Thursday, June 29, 2023 at 9:50:39 AM UTC-5 torres....@gmail.com wrote: > >> I was catching up with the other discussions about this, and I'm having >> seconds thoughts about Elixir having this syntax sugar for maps. >> It all sums up to what Austin pointed out here >> <https://groups.google.com/g/elixir-lang-core/c/P6VprVlRd6k/m/hMmgSniMAgAJ> >> . >> >> The bottom line, IMO (and agreeing with Austin), would be to end having >> something like `%{"foo", 'bar', :baz} = %{"foo" => 1, 'bar' => 2, :baz => >> 3}; {foo, bar, baz} #=> {1, 2, 3}`, to solve the atom/string key problem, >> and given that I'd prefer not to have it and leave Elixir as it is today. >> I also don't see any other tool (like a formatter or a language server) >> doing this kind of job. Why would I write something if that's not the >> language's syntax? If we open this precedent I'm not sure where we may end >> up with (probably would end up like JS: being transcripted by transpilers). >> Plus, the misspelling argument is not appealing enough too, since a good >> test covered application (which IMO should be our desire and intention) >> would have that caught. >> To add a little more, since we should use atoms with caution due to >> platform's limitations, we shouldn't make easier to enforce their usage, >> and since the %{ foo } = %{ foo: "any value" } syntax sugar would favour >> it, I am convinced enough *to not have* this on Elixir, contradicting >> myself in a previous proposal. >> >> Em quinta-feira, 29 de junho de 2023 às 16:24:06 UTC+2, José Valim >> escreveu: >> >>> : is not an operator at the user level for JS but it behaves like one >>> syntactically. You can add or remove spaces on either side and it works. >>> That’s not true for Ruby or Elixir as moving the spaces around is either >>> invalid or has a different meaning. >>> >> -- 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 visit https://groups.google.com/d/msgid/elixir-lang-core/17cbce64-188c-434d-a255-ece4e2c844f8n%40googlegroups.com.