2015-09-17 2 views
4

В «What does it mean to use the name of a class for string interpolation?» Кандид предположил, что #{} внутри строки неявно вызывает to_s. Так, например:Является ли # {} всегда эквивалентным 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} должно вернуться, согласно моему опыту, это нечто иное.

+5

to_s должен возвращать строку, ваш вызов карты возвращает массив –

ответ

2

Посмотрите на то, что Руби говорит вам:

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

Это означает, что Руби видит массив, возвращаемый ваш метод to_s, а не строка, как Руби ожидает и как было бы увидеть, если бы вы не переопределяется оригинал 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 классы to_s, как они делают то, что они должны. Для пользовательских классов рекомендуется реализовать to_s, имитируя тот же результат, что и основные классы. Иногда нам нужно проявлять фантазию и предлагать более подробные представления о том, как выглядит экземпляр объекта, но тогда мы переопределяем inspect.

1

Ваше определение Array#to_s возвращает массив, по которому необходимо будет применять to_s. Это приведет к бесконечной рекурсии. Я подозреваю, что Ruby имеет внутреннюю реализацию, чтобы отключить такую ​​бесконечную рекурсию в случае "#{my_array}". Для p my_array.to_s, my_array.to_s - массив, а p применяет Array#inspect, что не приводит к бесконечной рекурсии.

4

Пожалуйста, ознакомьтесь с документацией для Object#to_s. В нем говорится, что to_s должен вернуть String. Когда вы переопределяете метод, вы всегда должны соблюдать его контракт. Взгляните на документ для Array#to_s Как вы можете видеть, это также возвращает String.[Обратите внимание, что это верно для всех в to_X и to_XYZ методы:. Они должнывсегда возвращать объект соответствующего класса, и они неraise должны Exception или иным образом не]

Ваш осуществление to_s, однако нет возвращение a String. Он возвращает Array, таким образом нарушаетto_s контракт. Как только вы нарушите контракт метода, все ставки отключены. Лично я считаю, что было бы более целесообразно исключить здесь raiseTypeError, но Ruby пытается быть красивым и вместо этого возвращает String, который (в этом случае) печатает имя класса и некоторый уникальный идентификатор.

Вот коммита к RubySpec project который (неявно) говорится, что ни Exception не raise 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

RubySpec больше не поврежден: https://github.com/ruby/rubyspec – cremno