Project

General

Profile

Feature #15927

Allow string keys to be used for String#% and sprintf methods

Added by luke-gru (Luke Gruber) 6 months ago. Updated 6 months ago.

Status:
Open
Priority:
Normal
Assignee:
-
Target version:
-
[ruby-core:93161]

Description

Right now, in the methods sprintf() and String#%, only symbol keys can be used for named interpolation. For example (from the example documentation):

"foo = %{foo}" % { :foo => 'bar' } #=> "foo = bar"

String keys do not work like this:

"foo = %{foo}" % { 'foo' => 'bar' } #=> raises KeyError

I think string keys should work just the same as symbol keys here.

I've created a PR on github for this, perhaps misguidedly, but if can be found here: https://github.com/ruby/ruby/pull/2238

My argument for this feature is that String#gsub() and family works with string keys if given a Hash, for example:

"chocolate ice cream".gsub(/\bc/, { 'c' => 'C' }) #=> 'Chocolate ice Cream'

Also, I don't like having to symbolize keys in strings unnecessarily, but maybe that just goes back to when
Ruby couldn't GC some symbols.

History

Updated by sawa (Tsuyoshi Sawada) 6 months ago

My argument for this feature is that String#gsub() and family works with string keys if given a Hash

That is because they replace substrings, in which case it rather does not make sense to express them as symbols. The argument cannot be applied to string format, in which what is replaced is fields. Since fields are names, it makes more sense to express them as symbols.

Also, I don't like having to symbolize keys in strings unnecessarily

Field names should usually be fixed. They are not arbitrarily dynamically created. I do not think you need to worry about GC.

Updated by duerst (Martin Dürst) 6 months ago

I agree with sawa (Tsuyoshi Sawada) that there's a difference between gsub (where strings are replaced by strings) and sprintf, where it's interpolating something very close to variables.

A use case such as

"foo = %{foo}" % { 'foo' => 'bar' }

can just be rewritten to

"foo = %{foo}" % { foo: 'bar' }

Can you give us some actual use case(s) where such rewriting would not be possible, or very tedious?

Updated by ashmaroli (Ashwin Maroli) 6 months ago

Can you give us some actual use case(s) where such rewriting would not be possible, or very tedious?

One use-case would be where the Data used by sprintf is generated at runtime:

require 'yaml'

# contents of 'path/to/config.yml':
#
# ---
# title: Hello World
# author: John Doe
# url: "https://www.example.com/blog"
#

config = YAML.load_file('path/to/config.yml')
# config == {"title"=>"Hello World", "author"=>"John Doe", "url"=>"https://www.example.com/blog"}

# This will fail. The alternative would be to convert the string keys to symbols beforehand.
"title = %{title}" % config
[...]

Updated by sawa (Tsuyoshi Sawada) 6 months ago

ashmaroli (Ashwin Maroli) wrote:

One use-case would be where the Data used by sprintf is generated at runtime:

require 'yaml'

# contents of 'path/to/config.yml':
#
# ---
# title: Hello World
# author: John Doe
# url: "https://www.example.com/blog"
#

config = YAML.load_file('path/to/config.yml')
# config == {"title"=>"Hello World", "author"=>"John Doe", "url"=>"https://www.example.com/blog"}

# This will fail. The alternative would be to convert the string keys to symbols beforehand.
"title = %{title}" % config
[...]

I do not think that is the duty of string format.

If the YAML file is written by a Ruby program, then you should have saved the keys as symbols from the beginning, and you would not have such problem. If you are hand-writing the YAML file, there is a Ruby-specific way to explicitly write symbol keys in YAML, so check the documentation. If you need to write YAML file using a non-Ruby language and cannot avoid having the keys written as strings, then rather than betting on this feature request, you should make a different feature request that asks for a :symbolize_names option for YAML, as in JSON.

Updated by luke-gru (Luke Gruber) 6 months ago

Whether it's YAML or another data format like JSON, I've found it useful on occasion to treat sprintf() like a mini templating from a runtime generated Hash like ashmaroli mentioned. Sometimes ERB seems like overkill in quick scripts or internal projects. I understand now that these are considered "names" in the format string and so must be symbols, and this makes sense. It has surprised me in the past, however, and I have run into this error in my own scripts a few times.

Thank you for your time and explanations :)

Updated by shevegen (Robert A. Heiler) 6 months ago

In my own custom, hand-written yaml files I tend to use e. g:

!ruby/symbol foo: bar

for symbols as keys. This would then lead to this Hash, upon
YAML.load_file:

x = YAML.load_file 'foo.yml' # => {:foo=>"bar"}

For programmatic generation, YAML.dump() works fine for my use cases.

Converting to/from symbols in ruby is quite easy, including for
Hashes, such as via .transform_keys.

hash = { name: "joe", email: "abc@def.com" } # => {:name=>"joe", :email=>"abc@def.com"}
hash.transform_keys { |k| k.to_s } # => {"name"=>"joe", "email"=>"abc@def.com"}

I guess the issue request focuses in large part on the old debate "String versus Symbols". :)

I like Symbols. I do not remember the full quote, but matz explained the history and
reason for having Symbols in ruby, in the past; e. g. to use them as (atomic?) identifiers.
The pickaxe book had a slightly different focus, coming from a "symbol keys are more
memory-efficient in Hash keys". When frozen strings were added into ruby, I assume this
old distinction in regards to efficiency became a bit less noticable, but the design
intent is still different between strings and symbols.

I can not speak for anyone else, but I think the distinction between symbols and strings
will be kept for the most part, e. g. symbols will not be changed into being treatable
as strings by default in general. Otherwise I think sawa's advice is good - if you are
in control of the dataset, to manipulate it before, for example, storing it as yaml or
json.

Also available in: Atom PDF