简体   繁体   English

#{}总是等同于to_s吗?

[英]Is #{} always equivalent to to_s?

In " What does it mean to use the name of a class for string interpolation? ", Candide suggested that #{} inside a string implicitly calls to_s . 在“ 使用类的名称进行字符串插值是什么意思? ”,Candide建议字符串中的#{}隐式调用to_s So, for instance: 所以,例如:

my_array = [1, 2, 3, 4]
p my_array.to_s # => "[1, 2, 3, 4]"
p "#{my_array}" # => "[1, 2, 3, 4]"

However, if to_s for Array is redefined as shown below, I would get different results: 但是,如果重新定义了to_s for Array,如下所示,我会得到不同的结果:

class Array
  def to_s
    self.map { |elem| elem.to_s }
  end
end

p my_array.to_s # => ["1", "2", "3", "4"]
p "#{my_array}" # => "#<Array:0x007f74924c2bc0>"

I suppose this happens any time and anyhow to_s is overridden. 我想这会在任何时候发生,无论如何to_s被覆盖。

What am I supposed to do to keep the equality between to_s and the expression #{} in a string, if possible? 如果可能的话,我应该做些什么来保持字符串中的to_s和表达式#{}之间的相等性?

I came across this issue in a RubyMonk lesson : what according to the lesson # {ogres} should return, according to my experience is something different. 我在RubyMonk课程中遇到了这个问题:根据我的经验,根据课程# {ogres}应该返回的是不同的东西。

Please take a look at the documentation for Object#to_s . 请查看Object#to_s的文档。 It says that to_s should return a String . 它说to_s应该返回一个String When you override a method, you should always honor its contract. 覆盖方法时,应始终遵守其合同。 Take a look at the documentatio for Array#to_s As you can see, it also returns a String . 看看在documentatio的Array#to_s正如你所看到的,它返回一个String [Note that this is true for all the to_X and the to_XYZ methods: they must always return an object of the corresponding class and they must not raise an Exception or otherwise fail.] [请注意,对于所有 to_Xto_XYZ方法都是如此:它们必须 始终返回相应类的对象,并且它们不得 raise Exception或以其他方式失败。

Your implementation of to_s , however, does not return a String . 但是,您的to_s实现不会返回String It returns an Array , thus violating to_s 's contract. 它返回一个Array ,因此违反了 to_s的合同。 Once you violate a method's contract, all bets are off. 一旦违反方法的合同,所有投注都将被取消。 Personally, I think it would be more appropriate to raise a TypeError exception here, but Ruby is trying to be nice and returns some String instead, which (in this case) prints the class name and some unique identifier. 就个人而言,我认为在这里raise一个TypeError异常会更合适,但是Ruby试图变得更好并且返回一些 String ,而在这种情况下,它会打印出类名和一些唯一标识符。

Here is the commit to the RubySpec project which (implicitly) states that no Exception is raise d and explicitly states that an implementation-defined but otherwise unspecified String is returned: The spec for interpolation when Object#to_s did not return a String was confusing the default representation of an arbitrary object and Object#inspect . 这是对RubySpec项目的提交,它(隐式地)声明没有raise Exception并且明确声明返回实现定义但未指定的StringObject#to_s没有返回String时插值的规范令人困惑任意对象的默认表示和Object#inspect

The latest version of the spec, before the project was closed looks like this language/string_spec.rb#L197-L208 : 在项目关闭之前,规范的最新版本看起来像这个language/string_spec.rb#L197-L208

 it "uses an internal representation when #to_s doesn't return a String" do obj = mock('to_s') obj.stub!(:to_s).and_return(42) # See rubyspec commit 787c132d by yugui. There is value in # ensuring that this behavior works. So rather than removing # this spec completely, the only thing that can be asserted # is that if you interpolate an object that fails to return # a String, you will still get a String and not raise an # exception. "#{obj}".should be_an_instance_of(String) end 

As you can see, all that is guaranteed in this case, is that you won't get an Exception , and that you will get a String , however, it says nothing about what that String looks like. 正如您所看到的,在这种情况下保证的是,您将不会获得Exception ,并且您获得一个String ,但是,它没有说明String外观。

Look at what Ruby is telling you: 看看Ruby告诉你的内容:

"#{my_array}" # => "#<Array:0x007f74924c2bc0>"

That means Ruby is seeing an array returned by your to_s method, not a string as Ruby expects and like it'd see if you hadn't overridden the original Array.to_s . 这意味着Ruby会看到你的to_s方法返回一个数组,而不是像Ruby期望的那样的字符串,就像你看到你是否没有覆盖原始的Array.to_s

Instead use something like: 而是使用类似的东西:

'[%s]' % self.map { |elem| elem.to_s }.join(', ')

Change your code to return a String and you'll be okay. 更改你的代码以返回一个字符串,你会没事的。

Consider this: 考虑一下:

[].class # => Array
[].to_s.class # => String

class Array
  def to_s
    self.map { |elem| elem.to_s }
  end
end

[].to_s.class # => Array

class Array
  def to_s
    '[%s]' % self.map { |elem| elem.to_s }.join(', ')
  end
end

[].to_s.class # => String

my_array = [1, 2, 3, 4]
"#{my_array}" # => "[1, 2, 3, 4]"

In general practice, I'd recommend being cautious overriding core and STD-Lib classes' to_s as they're doing what they should. 在一般实践中,我建议谨慎地覆盖核心和STD-Lib类的to_s因为它们正在做它们应该做的事情。 For custom classes it's a good idea to implement to_s mimicking the same output as the core classes. 对于自定义类,实现to_s模仿与核心类相同的输出是个好主意。 Occasionally we'll have to get fancy and offer more detailed views into what an instance of an object looks like, but that's when we override inspect . 偶尔我们必须得到幻想,并提供更详细的视图来查看对象的实例是什么样的,但是当我们覆盖inspect

Your definition of Array#to_s returns an array, on which to_s would need to be further applied. 您对Array#to_s定义返回一个数组,在该数组上需要进一步应用to_s This would result in infinite recursion. 这将导致无限递归。 I suspect Ruby has internal implementation to cut off such infinite recursion in case of "#{my_array}" . 我怀疑Ruby有内部实现,以便在"#{my_array}"情况下切断这种无限递归。 For p my_array.to_s , my_array.to_s is an array, and p applies Array#inspect , which does not result in infinite recursion. 对于p my_array.to_smy_array.to_s是一个数组, p应用Array#inspect ,这不会导致无限递归。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM