Изящный способ струнного набора символов, символов и массивов? - PullRequest
5 голосов
/ 25 августа 2009

Это для уже существующего публичного API, который я не могу взломать, но я хочу его расширить.

В настоящее время метод принимает строку или символ или что-либо еще, что имеет смысл, когда передается в качестве первого параметра в send

Я бы хотел добавить возможность отправлять список строк, символов и так далее. Я мог бы просто использовать is_a? Array, но есть и другие способы отправки списков, и это не очень хорошо.

Я буду звонить map в списке, поэтому первым делом стоит использовать respond_to? :map. Но строка также отвечает на :map, так что это не сработает.

Ответы [ 8 ]

6 голосов
/ 27 августа 2009

Как насчет их всех как Arrays? Требуемое вами поведение для String s такое же, как и для Array, содержащего только это String:

def foo(obj, arg)
  [*arg].each { |method| obj.send(method) }
end

Трюк [*arg] работает, потому что оператор splat (*) превращает отдельный элемент в себя или Array в встроенный список его элементов.

Позже

Это в основном просто синтаксически подслащенная версия или ответ Арно , хотя есть небольшие различия, если вы передаете Array, содержащий другие Array с.

Позже

Есть еще одна разница, связанная с возвращаемым значением foo. Если вы позвоните foo(bar, :baz), вы можете быть удивлены, получив [baz] обратно. Чтобы решить эту проблему, вы можете добавить Kestrel :

def foo(obj, arg)
  returning(arg) do |args|
    [*args].each { |method| obj.send(method) }
  end
end

, который всегда будет возвращать arg как пройдено. Или вы можете сделать returning(obj), чтобы вы могли связывать вызовы с foo. Вам решать, какое поведение возвращаемого значения вы хотите.

2 голосов
/ 04 мая 2016

Важная деталь, которая была упущена во всех ответах: строки не отвечают на :map, поэтому самый простой ответ в исходном вопросе: просто используйте respond_to? :map.

1 голос
/ 27 августа 2009

Допустим, ваша функция называется func

Я бы сделал массив из параметров с

def func(param)
  a = Array.new
  a << param
  a.flatten!
  func_array(a)
end

В итоге вы реализуете свою функцию func_array только для массивов

с func ("привет мир") вы получите a.flatten! => ["Привет, мир"] с func (["hello", "world"]) вы получите a.flatten! => ["Привет", "Мир"]

1 голос
/ 26 августа 2009

Поскольку Array и String оба Enumerables, не существует элегантного способа сказать «вещь, которая является Enumberable, но не String», по крайней мере, не обсуждаемым способом.

То, что я хотел бы сделать, это тип утки для Enumerable (responds_to? :[]), а затем использовать выражение case, например:

def foo(obj, arg)
  if arg.respond_to?(:[])
    case arg
    when String then obj.send(arg)
    else arg.each { |method_name| obj.send(method_name) }
    end
  end
end

или даже чище:

def foo(obj, arg)
  case arg
  when String then obj.send(arg)
  when Enumerable then arg.each { |method| obj.send(method) }
  else nil
  end
end
0 голосов
/ 26 августа 2009

Возможно, вопрос не был достаточно ясным, но ночной сон показал мне два чистых способа ответить на этот вопрос.

1: to_sym доступно для String и Symbol и должно быть доступно для всего, что крякает как строка.

if arg.respond_to? :to_sym
    obj.send(arg, ...)
else
    # do array stuff
end

2: отправка выдает TypeError при передаче массива.

begin
  obj.send(arg, ...)
rescue TypeError
  # do array stuff
end

Мне особенно нравится # 2. Я сильно сомневаюсь, что кто-либо из пользователей старого API ожидает, что TypeError будет вызван этим методом ...

0 голосов
/ 26 августа 2009

Если вы не хотите monkeypatch, просто массируйте список до соответствующей строки перед отправкой. Если вы не возражаете против создания или наследования обезьян, но хотите сохранить ту же сигнатуру метода:

class ToBePatched
    alias_method :__old_takes_a_string, :takes_a_string

    #since the old method wanted only a string, check for a string and call the old method
    # otherwise do your business with the map on things that respond to a map.
    def takes_a_string( string_or_mappable )
        return __old_takes_a_string( string_or_mappable ) if String === string_or_mappable
        raise ArgumentError unless string_or_mappable.responds_to?( :map )
        # do whatever you wish to do
    end
end
0 голосов
/ 26 августа 2009

Используйте Маршал для сериализации ваших объектов перед их отправкой.

0 голосов
/ 25 августа 2009

Можете ли вы просто переключать поведение, основываясь на параметре.class.name? Это уродливо, но если я правильно понимаю, у вас есть единственный метод, которому вы будете передавать несколько типов - вам придется как-то различать.

В качестве альтернативы, просто добавьте метод, который обрабатывает параметр типа массива. Это немного другое поведение, поэтому дополнительный метод может иметь смысл.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...