I've realized that this syntax is now sufficiently viable outside of the parser to be implemented as a pure Elixir macro library, without need for a fork of the language, so will be releasing it and circulating on the forums for more broader feedback. It's hard to sell even myself on such novel syntax, so I figure userland is a better space for it to be tested out before re-mounting a language proposal.
On Friday, December 20, 2024 at 5:40:45 PM UTC-6 Christopher Keele wrote: > 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/e93a8414-46e6-4574-90d6-0b6b01ed7ea7n%40googlegroups.com.