Feature #15927
closedAllow string keys to be used for String#% and sprintf methods
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.
Updated by sawa (Tsuyoshi Sawada) over 5 years 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) over 5 years 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) over 5 years 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) over 5 years 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) over 5 years 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) over 5 years 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.
Updated by luke-gru (Luke Gruber) 11 months ago
This can be closed as I agree with the feedback given. Thanks!
Updated by jeremyevans0 (Jeremy Evans) 11 months ago
- Status changed from Open to Closed