Как я могу сослаться на добавленный метод в существующий класс в Ruby?

Я расширил класс String с помощью метода. Скажем «фу».

class String
  def foo
    puts "Hello World."
  end
end

Почему нельзя вызвать ни String.foo, ни String.method("foo")? Я получаю NoMethodError, когда пытаюсь.

Моя конечная цель - передать "foo" другому методу. Что-то вроде bar(String.method('foo'))

заранее спасибо

Вы пытаетесь вызвать метод экземпляра класса.

user3574603 09.02.2023 16:35
foo — это метод экземпляра. Технически вы можете вызвать String.instance_method(:foo), который вернет UnboundMethod, но тогда в bar вам понадобится bind этот метод для экземпляра.
engineersmnky 09.02.2023 16:43

Например, "cat".foo отображает "Hello World.", "cat" является экземпляром класса String. Между прочим, хотя я понимаю, что вы просто проводите некоторые эксперименты по изучению Ruby, когда вы начинаете программировать по-настоящему, обычно считается плохой практикой добавлять методы в основные классы Ruby, такие как String.

Cary Swoveland 09.02.2023 20:40

Хотя технически это возможно, в Ruby очень редко передаются ссылки на методы (с точки зрения объектов Method/UnboundMethod). Обычно вы используете символы для ссылки на методы по их имени. Можете ли вы уточнить, чего вы пытаетесь достичь? Может быть, с небольшим примером. Может быть более идиоматическое решение вашей проблемы.

Stefan 10.02.2023 15:54
Пошаговое руководство по созданию собственного Slackbot: От установки до развертывания
Пошаговое руководство по созданию собственного Slackbot: От установки до развертывания
Шаг 1: Создание приложения Slack Чтобы создать Slackbot, вам необходимо создать приложение Slack. Войдите в свою учетную запись Slack и перейдите на...
0
4
57
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

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

class String
  def foo
    puts "Hello World."
  end
end

String.new("test").foo # output: Hello World.
Ответ принят как подходящий

foo — это метод экземпляра. Вы можете использовать Module#instance_method, чтобы получить метод, затем привязать его к объекту String, используя .bind(string), а затем вызвать его, используя .call(args).

class String
  def foo
    puts "Hello #{self}."
  end
end

p String.instance_method(:foo)
p String.instance_method(:foo).bind("World")
String.instance_method(:foo).bind("World").call

Выход:

#<UnboundMethod: String#foo() a.rb:2>
#<Method: String#foo() a.rb:2>
Hello World.

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

class String
  def self.foo
    puts "Hello"
  end
end

String.foo    # => Hello

Вы также можете заставить его возвращать proc, чтобы его можно было пройти в другом месте:

class String
  def self.foo = -> { puts "Hello" }
end

String.foo    # => #<Proc:0x0000000102a05ea8 (irb):14 (lambda)>
String.foo[]  # => Hello

С аргументами:

class String
  def self.foo = ->(name){ puts "Hello #{name}" }
end

String.foo["Malte"]  # => Hello Malte

Проголосуйте за сокращенный синтаксис с лямбдой (хотя первому не нужны пустые скобки)

engineersmnky 09.02.2023 21:12

@engineersmnky Вам нужны [], .() или .call. Без одного из них он просто передает вам ссылку на процесс, которая падает на пол.

pjs 09.02.2023 21:49

Извините, если мой комментарий был неясен. Я имел в виду объявление, например. ->(){ puts "Hello" } то же, что ->{ puts "Hello" }

engineersmnky 09.02.2023 21:51

Ваш первый вопрос: «Если я выполню

class String
  def foo
    puts "Hello World."
  end
end

почему нельзя вызвать ни String.foo, ни String.method("foo")?

Когда вы выполняете

String.foo
  #=> NoMethodError: undefined method 'foo' for String:Class

сообщение об ошибке1 очень специфично: класс String не имеет метода foo. Это согласуется со следующим.

String.methods.include?(:foo)
  #=> false

Что вы сделали, так это создали метод экземпляра foo строки класса. Возьмем "cat", экземпляр класса String:

"cat".methods.include?(:foo)
  #=> true

"cat".foo
  #=> "Hello World."

Мы также можем посмотреть на

String.instance_methods
  #=> [:unicode_normalize, :unicode_normalize!, :ascii_only?,
  #    :to_r,..., :foo, :count,..., :instance_exec]

или чтобы легче найти :foo среди примерно 187 методов экземпляра String, мы могли бы изучить

String.instance_methods.sort
  #=> [:!, :!=,..., :extend, :foo, :force_encoding,..., :yield_self]

См. Kernel#methods и Module#instance_methods


Если вы хотите вызвать foo на String, вам нужно написать

class String
  def self.foo
    puts "Hello World."
  end
end

String.foo
  #=> "Hello World."

Когда метод построен, self равно String, поэтому вышеприведенное то же самое, что и

class String
  def String.foo
    puts "Hello World."
  end
end

Причина, по которой self обычно используется вместо буквального имени класса, заключается в том, что это позволяет изменить имя класса без необходимости изменения определения метода. (Конечно, мы не стали бы переименовывать String, но мы могли бы изменить определяемое пользователем имя класса (например, изменить Amount на Quantity.)

На самом деле в Ruby есть только методы экземпляра, поэтому давайте посмотрим, что означает def String.foo в терминах метода экземпляра.

Мы видим, что

String.class
  #=> Class

это означает, что String является экземпляром Class. Поэтому мы могли бы написать

class Class
  def foo
    puts "Hello World."
  end
end
 
String.foo
  #=> "Hello World."

но это имеет нежелательный эффект, делая этот метод экземпляра Class доступным для всех классов:

Array.foo
  #=> "Hello World."
Hash.foo
  #=> "Hello World."

Вместо этого мы хотим ограничить доступность foo только одним из экземпляров Class, String.

Каждый объект Ruby имеет специальный класс, называемый «классом-одиночкой» (одно из нескольких используемых имен). Я говорю «специальный», потому что он отличается от обычных классов тем, что не может быть подклассом и не может создавать его экземпляры.

Мы можем написать следующее, чтобы создать метод foo, который мы можем вызывать для String.

string_single = String.singleton_class
  #=> #<Class:String>

string_single.define_method(:foo) { puts "Hello World." }
  #=> :foo

string_single.instance_methods.include?(:foo)
  #=> true

String.foo
  #=> Hello World.

См. Модуль#define_method.

Фактически,

def String.foo
  puts "Hello World."
end

это просто сокращение для использования define_method выше. Аналогично, знакомый

class String
  def foo
    puts "Hello World."
  end
end
  #=> :foo

это просто сокращение для

String.define_method(:foo) { puts "Hello World." }
  #=> :foo

"cat".foo
  #=> "Hello World."

Вторая часть ваших вопросов спрашивает, как передать один одноэлементный метод другому. Это легко. Мы уже определили foo в классе singleton String, поэтому давайте определим другой, bar, который вызывает foo.

String.singleton_class.define_method(:bar) { foo }
  #=> :bar

String.bar
  #=> Hello World.

1. Обратите внимание на все сообщения об ошибках. Часто они определяют проблему.

Другие вопросы по теме