From: "yertto (_ yertto) via ruby-core" Date: 2026-07-15T06:52:49+00:00 Subject: [ruby-core:126089] [Ruby Feature#22108] Computed hash keys with (expr): syntax Issue #22108 has been updated by yertto (_ yertto). kddnewton (Kevin Newton) wrote in #note-25: > We're not "locked out" of using non-symbolic keys in hash patterns because of a syntax restriction... > Also, if this was actually a desired feature with a real use case, `=>` could already be used. We don't need to introduce new syntax. Whether or not using non-symbolic keys in *Hash Patterns* is a _"desired feature"_ could be up for debate (and I'm thinking I should raise a separate proposal for that, along with a better example, and have the discussion there instead). However, I'd like to clearly highlight the syntactic limitations we currently face, and more clearly dispel any misguided belief that using `=>` as a key/value separator can already be used in *Hash Patterns*, while its still being discussed in this proposal. Yes the `=>` symbol can already be used in *Hash Patterns* but ONLY for its role in value capturing (rightward assignment), **not for key/value separation**. eg. for this *Hash Pattern*. ``` {k: String => v} ``` The key (`:k`) can ONLY be matched against a symbolic key `:k`, because *Hash Patterns* can ONLY be created using symbolic keys. And in this *Hash Pattern* the value is matched against a String that is then captured into the variable `v`. I think the reason why so many of us have confused the role of the `=>` is because *Hash Patterns* look so deceptively like *Hash Literals*. (Perhaps with an added assumption that matching against *Hash Patterns* provided a superset of what could be matched against using *Hash Literals*.) (And to be honest, before raising this, I didn't even know the words *"Hash Literal"* and *"Hash Pattern"* existed to be able to clearly distinguish between the two.) Nb. in that example above (in fact in ALL existing *Hash Patterns*) the key/value separator is effectively (and exclusively) the `:` and NEVER the `=>`. Which leaves the `String => v` value pattern side of the key/value pattern pair to get used when matching the value. (It would be completely wrong to assume that the `=>` is being used as a key/value separator here. ie. where a misguided reader might assume that the key part of the pattern is `k: String`, and possibly might even go on to incorrectly assume that the key `k` is of type `String`.) Unlike the *Hash Literal*, there is currently NO way to use eg. the `Integer` `200` as a key by using the `=>` as a key/value separator in a *Hash Pattern*. So currently Ruby _could_ match *Hash Literals* against *Hash Patterns* by very clumsily converting ALL the keys to symbolic keys like this: ```ruby { :k => "ok" } in { k: String => v } ; puts "#{v}" { :"200" => "ok" } in { "200": String => v } ; puts "200: #{v}" { :"200" => "ok" } in { "200": String } | { "201": String } | { "202": String } ; puts "2xx SUCCESS - " ``` Which produces: ``` ok 200: ok 2xx SUCCESS - ``` (_Noting that I'm choosing to keep my *Hash Literals* as uniform as possible here by exclusively using the `=>` key/value separator - eg. `{:k => "ok"}` instead of `{k: "ok"}`, so we can more easily focus in on what's happening in the *Hash Patterns* they're being matched against._) However, a new _Hash Capsule_ `(...):` proposal facilitates the use of `:` as they key/value separator for ALL key types. Which means that code using any typed key - both in the *Hash Literal* and in the *Hash Pattern* it's being matched against - can now work. eg. ```ruby { :k => "ok" } in { k: String => v } ; puts " #{v} (where :k === :k)" { 200 => "ok" } in { (200 => key): String => v } ; puts " #{key}: #{v} (where 200 === #{key})" { 200 => "ok" } in { (200..299 => key): String => v } ; puts "2xx SUCCESS - #{key}: #{v} (where 200..299 === #{key})" ``` Which produces: ``` ok (where :k === :k) 200: ok (where 200 === 200) 2xx SUCCESS - 200: ok (where 200..299 === 200) ``` A big advantage of this proposal is that the *Hash Literal* keys no longer have to be clumsily converted to symbols just to perform the pattern match. And this provides further advantages when working with those keys because when they remain in their original form because a key like `200` can then be: * matched against patterns like `200..299 === 200` * used in arithmetic or sorting (which couldn't be done had it been mangled into a symbolic form like `:"200"`) I think there is confusion here because the _Rightward Assignment_ (`=>`) was employed to work within the relatively new *Hash Pattern*, at the expense of her much older, but identical sister the _Hash Rocket_ (`=>`), who had been working as a key/value separator in *Hash Literals* for decades. So what the *Hash Pattern* discussion ultimately boils down to is Ruby's (current) inability to put both sisters to work (ie. using non-symbolic keys AND rightward assignment/value capturing) at the same time when doing the job of pattern matching. ---------------------------------------- Feature #22108: Computed hash keys with (expr): syntax https://bugs.ruby-lang.org/issues/22108#change-118094 * Author: yertto (_ yertto) * Status: Rejected ---------------------------------------- > [!WARNING] > This has potentially been obsoleted by [Feature #22111](https://bugs.ruby-lang.org/issues/22111) # Computed hash keys with `(expr):` syntax Allow `{ (expr): value }` as a computed hash key. Almost 20 years in the making, the missing puzzle piece for Hash's "new" colon notation: ```ruby h = { name: "symbol shorthand", # Ruby 1.9+ "quoted label": "symbol label", # Ruby 2.2+ (Feature #4935) value_omission: , # Ruby 3.1+ (Feature #14579) (expr): "computed", # THIS PROPOSAL } ``` ## Motivation Ruby has several colon-based hash key syntaxes but the `=>` ("hash rocket") is still needed for non-symbolic keys. Adding `(expr):` is a baby step toward allowing all-colon hashes: ```ruby ## Before -- mixed styles n = 42; { key1: "symbol", "key-#{2}": "quoted symbol", RUBY_VERSION:, n => "bar" }.keys # => [:key1, :"key-2", :RUBY_VERSION, 42] ## After -- uniform colon syntax n = 42; { key1: "symbol", "key-#{2}": "quoted symbol", RUBY_VERSION:, n : "bar" }.keys # => [:key1, :"key-2", :RUBY_VERSION, 42] ``` (And maybe a step closer to one day retiring our old friend "hash rocket" from Hashes entirely?) ### Completing the colon family | Example | Ruby | Feature | Key type | |---------|------|---------|----------| | `{ name: value }` | [v1.9](https://docs.ruby-lang.org/en/3.4/NEWS/NEWS-1_9_1.html) | | Symbol | | `{ "quoted label": value }` | [v2.2](https://docs.ruby-lang.org/en/3.4/NEWS/NEWS-2_2_0.html) | [Feature #4935](https://bugs.ruby-lang.org/issues/4935) | Symbol (quoted label) | | `{ value_omission: }` | [v3.1](https://docs.ruby-lang.org/en/3.4/NEWS/NEWS-3_1_0_md.html) | [Feature #14579](https://bugs.ruby-lang.org/issues/14579) | Value omission | | `{ (expr): value }` | *???* | [Feature #22108](https://bugs.ruby-lang.org/issues/22108) | Computed | ### Readability for computed-key-heavy code Code that builds dynamic hashes currently forces a style break midway through a literal. This is especially noticeable with interpolated keys, numeric keys, or variable-driven keys. ### Reducing `=>` overloading The `=>` token is now being used to serve more purposes than just the Hash literal (aka "Hash rocket") it was originally used for. - rightward assignment ``` expr => var ``` - pattern capture ``` case {name: "Alice", role: "admin"} in {name: String => name, role:} # capture the name String value into `name` p name # => "Alice" end ``` - rescue variables ``` rescue SomeExceptionClass => e ``` ``` rescue => e ``` Reducing its use in Hashes simplifies the language, especially for newcomers. ## Design ### `(expr):` vs `[expr]:` Parenthesized expressions were chosen over square bracket delimited: - Idempotent wrapping: `((x))` = `(x)`, but `[[x]]` != `[x]` (nested array) - Array keys: `{(["a"]): "b"}` reads naturally vs `{[["a"]]: "b"}` requiring extra wrapping - `jq` precedent: the JSON query language uses identical `{("a"+"b"): 59}` syntax (since [jq 1.6](https://jqlang.org/manual/v1.6/#object-construction), 2018) - Parentheses signals a grouping or "evaluate this", whereas square brackets signals an Array/container ### Improving on JavaScript JavaScript's "[computed property names](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer#computed_property_names)" `{ [expr]: value }` were introduced in ECMAScript 2015 (ES6), 3 years before the jq version. However in Ruby, `[]` already means Array literal and method call. Rather than overloading `[]` further, `(expr):` (from jq) improves on the JavaScript design. Parentheses naturally signal evaluation, `():` inside `{}` is unambiguous to the lexer, and it matches the well-established jq convention. ### Edge cases handled - `(expr) : value` (space before colon) -- *syntax error* (consistent with `name : value`) - `{ (expr): }` (value omission) -- *syntax error* (runtime expression, no compile-time local to infer) - Mixed styles: `{ (1): "one", two: "two", "three": "three" }` -- *valid* - Nested parens: `{ ((1 + 2) * 3): "nested" }` -- *valid* - Nested hashes: `{ ({(1): "one"}): "two" }` -- *valid* ## Implementation Three changes to `parse.y` (Lrama LALR(1) parser, zero new conflicts): 1. Lexer: emit `tLABEL_END` when `EXPR_ENDFN` state, no space before `:`, and inside `brace_nest > 0` 2. Grammar: new `assoc` alternative -- `tLPAREN compstmt ")" tLABEL_END arg_value` 3. Precedence: `%nonassoc ')'` to disambiguate Reference implementation: [feature/computed-hash-keys](https://github.com/ruby/ruby/pull/17299) PR on GitHub. ## Historical context A version of this was discussed on ruby-core in October 2007 (as part of ["General hash keys for colon notation"](https://public-inbox.org/ruby-core/E1ImQBE-00033s-IZ@x31/T/), murphy). Unfortunately it was brought up during the v1.9 feature freeze, but it looks like [Matz's invitation to discuss for v2.0](https://public-inbox.org/ruby-core/E1ImQA8-00033T-8c@x31/) didn't end up going anywhere. Since then, `"quoted label":` (Ruby 2.2, [Feature #4935](https://bugs.ruby-lang.org/issues/4935)) and value omission (Ruby 3.1, [Feature #14579](https://bugs.ruby-lang.org/issues/14579)) have expanded the colon family, making the computed-key gap more conspicuous. Meanwhile, jq introduced the identical `{("a"+"b"):59}` syntax in [jq 1.6](https://jqlang.org/manual/v1.6/#object-construction) (2018), demonstrating real-world viability. ## Open questions - Is this syntax acceptable to the community? - Is the jq precedent compelling enough to address the "no language does this" objection? ## Future directions All colon-based key syntaxes would now be available. This opens up the possibility of eventually deprecating `=>` from Hash literals (while keeping it for rescue, pattern matching, and rightward assignment). This proposal does not require that change - it is simply the enabling step, and any deprecation timeline could be a separate discussion. -- https://bugs.ruby-lang.org/ ______________________________________________ ruby-core mailing list -- ruby-core@ml.ruby-lang.org To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org ruby-core info -- https://ml.ruby-lang.org/mailman3/lists/ruby-core.ml.ruby-lang.org/