Как проверить, действительно ли переменная отвечает на: dup?

Я хотел использовать value.respond_to?(:dup) ? value.dup : value, чтобы проверить, могу ли я дублировать объект, но он не удался с TypeError на booleans, nil или таких "примитивах".

Я закончил с:

begin
  value = value.dup
rescue
  #ignore, use the original if no dup-able (e.g nil, true, etc)
end

Есть ли лучший способ?

Бонус: почему он отвечает :dup?

Не глубоко dup, просто для вопроса.

EDIT: Мысли:

  • obj.class.methods.include? :new приятный, но слишком хакерский, я думаю, что он имеет плохую производительность.
  • Marshal также выглядит как overkill
  • спасение одной строки могло быть лучшим решением, но на данный момент невозможно создать однострочное спасение типа (IIUC matz на этом!), и как @JörgWMittag упомянул о своей ошибке.
  • Лично я думаю, что dup определяется на уровне объекта неправильно.

Итак, цитируя @Linuxios

На самом деле нет лучшего способа

Ответы

Ответ 1

На самом деле нет лучшего способа. dup определяется в Object, что означает, что любой класс, который хочет не отвечать на него, должен перегрузить его, чтобы создать исключение. NilClass, TrueClass, FalseClass и Number - все подклассы объекта. Это означает, что они должны переопределить метод, чтобы вызвать ошибку.

В любом случае, если вы ищете глубокую копию, это использовать обычный Marshal.load(Marshal.dump(obj)), который будет обрабатывать номера, bools и nil просто отлично.

Например:

1.9.3-p392 :001 > obj = "hi"
 => "hi"
1.9.3-p392 :002 > Marshal.load(Marshal.dump(obj)).object_id != obj.object_id
 => true
1.9.3-p392 :003 > obj = 3
 => 3
1.9.3-p392 :004 > Marshal.load(Marshal.dump(obj)).object_id != obj.object_id
 => false

Ответ 2

Вы можете написать это как одну строку следующим образом:

value = value.dup rescue value

Очень ясно.

Стандартно определить метод dup, который вызывает TypeError для типов, которые не могут быть дублированы. Таким образом, любой объект будет "реагировать" на него. Вам действительно нужно позвонить и проверить с началом-спасением.

Ответ 3

Я думаю, что причина, по которой он отвечает на dup, состоит в том, что класс наследует объект Object, который имеет метод dup.

Похоже, что в методе dup в объекте проверяется "специальная константа" и возникает ошибка, которую вы видите:

VALUE
rb_obj_dup(VALUE obj)
{
    VALUE dup;

    if (rb_special_const_p(obj)) {
        rb_raise(rb_eTypeError, "can't dup %s", rb_obj_classname(obj));
    }
    dup = rb_obj_alloc(rb_obj_class(obj));
    init_copy(dup, obj);
    rb_funcall(dup, id_init_dup, 1, obj);

    return dup;
}

Я думаю, единственное, что вы могли бы сделать, это проверить эти специальные константы в вашем методе.

Ответ 4

def dupable?(obj)
  obj.class.methods.include? :new
end

dupable?(1)     # => false
dupable?(3.2)   # => false
dupable?(:a)    # => false
dupable?(true)  # => false
dupable?(nil)   # => false
dupable?("cat") # => true

Ответ 5

Есть несколько лучший способ, не уверенный в проверке сообщения об ошибке:

begin
    value = value.dup
rescue TypeError => e
    # !!! not sure about the following line
    raise unless e.message == "can't dup %s" % value.class.name
    #ignore, use the original if no dup-able (e.g nil, true, etc)
end