#{}是否始终等同于to_s?

时间:2015-09-17 14:54:52

标签: ruby string-interpolation

在“What does it mean to use the name of a class for string interpolation?”中,Candide建议字符串中的#{}隐式调用to_s。 所以,例如:

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

但是,如果重新定义了to_s数组,如下所示,我会得到不同的结果:

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>"

我认为这种情况随时都会发生,无论如何to_s都会被覆盖。

如果可能的话,我应该怎么做才能在字符串中保持to_s和表达式#{}之间的相等性?

我在RubyMonk lesson中遇到了这个问题:根据我的经验,根据课程#{ogres}应该返回的内容是不同的。

3 个答案:

答案 0 :(得分:4)

请查看Object#to_s的文档。它说to_s应该返回String。覆盖方法时,应始终遵守其合同。看一下Array#to_s的文档。正如您所看到的,会返回String。 [请注意,所有 to_Xto_XYZ方法都是如此:必须 总是返回一个对象对应的类,他们不得 raise Exception或以其他方式失败。]

但是,to_s的实施确实 不会返回String。它返回Array,因此违反了 to_s的合同。一旦违反方法合同,所有赌注都将被取消。就个人而言,我认为这里raise TypeError例外更合适,但Ruby正试图变得更好并返回一些 String代替,在这种情况下)打印类名和一些唯一标识符。

以下是对RubySpec project的提交,其中(隐式)声明没有Exceptionraise d并明确声明实现定义但未指定的String是返回: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.

在项目结束之前,规范的最新版本看起来像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

正如您所看到的,在这种情况下保证的是,您赢得获得Exception,并且您得到一个String,然而,它没有说明String的样子。

答案 1 :(得分:2)

看看Ruby告诉你的事情:

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

这意味着Ruby看到了to_s方法返回的数组,而不是Ruby期望的字符串,并且喜欢它,看看你是否没有覆盖原始的Array.to_s

而是使用类似的东西:

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

更改您的代码以返回字符串,您就可以了。

考虑一下:

[].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]"

在一般的实践中,我建议谨慎地覆盖核心和STD-Lib类&#39; to_s因为他们正在做他们应该做的事情。对于自定义类,实现to_s模仿与核心类相同的输出是个好主意。偶尔我们必须得到一些想象,并提供更详细的视图来了解对象的实例是什么样的,但是当我们覆盖inspect时就是这样。

答案 2 :(得分:1)

您对var cursor = Collection.find(); cursor.forEach(function(doc){ console.log(doc._id); // fill new object here... }); 的定义会返回一个数组,需要进一步应用Array#to_s。这将导致无限递归。我怀疑Ruby有内部实现,以便在to_s的情况下切断这种无限递归。对于"#{my_array}"p my_array.to_s是一个数组,而my_array.to_s适用p,这不会导致无限递归。