Apologies, Google Groups did something truly awful with the formatting on some of those example blocks, but I'm not messing with them further as it takes a lot of time to fake the coloration of a VSCode editing experience in them :) On Friday, December 20, 2024 at 5:37:00 PM UTC-6 Christopher Keele wrote:
> 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/453ed8c1-4a8c-4db2-b651-e53591e0270dn%40googlegroups.com.