Я расширил класс String с помощью метода. Скажем «фу».
class String
def foo
puts "Hello World."
end
end
Почему нельзя вызвать ни String.foo
, ни String.method("foo")
?
Я получаю NoMethodError, когда пытаюсь.
Моя конечная цель - передать "foo" другому методу. Что-то вроде bar(String.method('foo'))
заранее спасибо
foo
— это метод экземпляра. Технически вы можете вызвать String.instance_method(:foo)
, который вернет UnboundMethod
, но тогда в bar
вам понадобится bind
этот метод для экземпляра.
Например, "cat".foo
отображает "Hello World."
, "cat"
является экземпляром класса String
. Между прочим, хотя я понимаю, что вы просто проводите некоторые эксперименты по изучению Ruby, когда вы начинаете программировать по-настоящему, обычно считается плохой практикой добавлять методы в основные классы Ruby, такие как String
.
Хотя технически это возможно, в Ruby очень редко передаются ссылки на методы (с точки зрения объектов Method
/UnboundMethod
). Обычно вы используете символы для ссылки на методы по их имени. Можете ли вы уточнить, чего вы пытаетесь достичь? Может быть, с небольшим примером. Может быть более идиоматическое решение вашей проблемы.
Вы определяете метод экземпляра, но попытались вызвать статический метод. Это работает, как и ожидалось, когда вы называете это правильно.
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 Вам нужны []
, .()
или .call
. Без одного из них он просто передает вам ссылку на процесс, которая падает на пол.
Извините, если мой комментарий был неясен. Я имел в виду объявление, например. ->(){ puts "Hello" }
то же, что ->{ puts "Hello" }
Ваш первый вопрос: «Если я выполню
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. Обратите внимание на все сообщения об ошибках. Часто они определяют проблему.
Вы пытаетесь вызвать метод экземпляра класса.