英文:
Why does ruby recognise a method outside of a class, but not inside?
问题
我正在尝试构建一个简单的语言翻译程序。我导入了'language_converter'宝石来帮助实现这个目标。我编写了以下代码:
require 'language_converter'
class Translator
def initialize
@to = 'ja';
@from = 'en';
end
def translate text
lc(text, @to, @from)
end
end
#puts lc('welcome to Japan!', 'ja','en');
t = Translator.new
p t.translate('welcome to Japan!');
这段代码导致错误:`undefined method 'lc' for #<Translator:0x0000000101167a90 @to="ja", @from="en"> (NoMethodError)`
然而,当我取消注释第15行的代码时,Ruby可以访问lc方法并返回一些日文。有人知道为什么该方法在类内部没有被'定义'吗?
编辑:[language-converter][1]宝石不是我的。此外,我在其主页上找不到源代码。
我还尝试在`lc`方法之前添加两个冒号,如下所示:`::lc(text, @to, @from)`。这导致错误:`语法错误,意外的局部变量或方法,期望常量`
英文:
I am trying to build a simple language translating program. I imported the 'language_converter' gem to aid with this goal. I wrote the following code:
require 'language_converter'
class Translator
def initialize
@to = 'ja';
@from = 'en';
end
def translate text
lc(text, @to,@from)
end
end
#puts lc('welcome to Japan!', 'ja','en');
t = Translator.new
p t.translate('welcome to Japan!');
This code results in the error: undefined method 'lc' for #<Translator:0x0000000101167a90 @to="ja", @from="en"> (NoMethodError)
However, when i uncomment the code on line 15, ruby can access the lc
method and return some japanese. Does anyone know why the method is 'defined' outside of the class but not inside?
Edit: the language-converter gem is not my own. also, I cannot find the source code on its homepage.
I have also tried adding two semicolons before the lc
method like so: ::lc(text, @to,@from)
. This results in the error: syntax error, unexpected local variable or method, expecting constant
答案1
得分: 4
这个 gem 已经有超过 10 年的历史,只有一个方法,并且该方法实现为一个类方法。
最好的做法是在你的应用程序中使用现代的 Ruby 语法和正确的错误处理来重写该方法。
关于这个 gem 中 lib/language_converter.rb
的代码如下:
require 'net/http'
require 'rubygems'
require "uri"
require 'json'
class UnSupportedLanguage < RuntimeError
def initialize(message='')
@msg = "not supported."
end
end
def self.lc( text, to, from='en' )
begin
uri = URI.parse("http://mymemory.translated.net/api/get")
response = Net::HTTP.post_form(uri, {"q" => text,"langpair"=>"#{from.to_s.downcase}|#{to.to_s.downcase}", "per_page" => "50"})
json_response_body = JSON.parse( response.body )
if json_response_body['responseStatus'] == 200
json_response_body['responseData']['translatedText']
else
puts json_response_body['responseDetails']
raise StandardError, response['responseDetails']
end
rescue UnSupportedLanguage
raise UnSupportedLanguage.new
rescue => err_msg
puts "#{err_msg}"
end
end
希望这有助于你理解代码。
英文:
The gem is more than 10 years old and only has one method. And that method is implemented as a class method.
You are properly better off with just rewriting that method in your application with a modern Ruby syntax and proper error handling.
For reference, this it how lib/language_converter.rb
in the gem looks like:
require 'net/http'
require 'rubygems'
require "uri"
require 'json'
class UnSupportedLanguage < RuntimeError
def initialize(message='')
@msg = "not supported."
end
end
def self.lc( text, to, from='en' )
begin
uri = URI.parse("http://mymemory.translated.net/api/get")
response = Net::HTTP.post_form(uri, {"q" => text,"langpair"=>"#{from.to_s.downcase}|#{to.to_s.downcase}", "per_page" => "50"})
json_response_body = JSON.parse( response.body )
if json_response_body['responseStatus'] == 200
json_response_body['responseData']['translatedText']
else
puts json_response_body['responseDetails']
raise StandardError, response['responseDetails']
end
rescue UnSupportedLanguage
raise UnSupportedLanguage.new
rescue => err_msg
puts "#{err_msg}"
end
end
答案2
得分: 0
Does anyone know why the method is 'defined' outside of the class but not inside?
有人知道为什么方法在类外部被“定义”,而不在类内部吗?
The method lookup algorithm in Ruby is relatively simple.
Ruby中的方法查找算法相对简单。
-
Retrieve the value of the hidden
class
pointer of the receiver. -
检索接收器的隐藏
class
指针的值。 -
Check whether the method table contains the method.
-
检查方法表是否包含该方法。
-
If yes, you are done.
-
如果是,就完成了。
-
If not, continue to step #3.
-
如果不是,继续到步骤#3。
-
-
Retrieve the value of the superclass pointer.
-
检索超类指针的值。
-
If there is no superclass, go to step #4.
-
如果没有超类,转到步骤#4。
-
If there is a superclass, repeat from step #2.
-
如果有超类,从步骤#2重复。
-
-
Start the algorithm from the beginning with the message selector
method_missing
, passing the name of the original message and its arguments as arguments. -
从头开始使用消息选择器
method_missing
启动算法,将原始消息的名称和其参数作为参数传递。
And that's it. This is always guaranteed to terminate because the top-level class always has a method named method_missing
.
就是这样。这总是保证会终止,因为顶级类始终具有名为method_missing
的方法。
Now, you might ask: but wait, what about singleton classes?
现在,您可能会问:但等等,单例类怎么办?
The answer is: the hidden class
pointer of an object points to its singleton class, and the singleton class's superclass pointer points (directly or indirectly) to the "actual" class.
答案是:对象的隐藏class
指针指向其单例类,而单例类的超类指针指向(直接或间接)“实际”类。
Now, you might ask: but wait, why doesn't Object#class
return the singleton class, then?
现在,您可能会问:但等等,为什么Object#class
不返回单例类呢?
The answer is: Object#class
doesn't directly return the hidden class
pointer. Instead, it follows the superclass chain until it finds the first "normal" class.
答案是:Object#class
不直接返回隐藏的class
指针。相反,它会遵循超类链,直到找到第一个“正常”的类。
Now, you might ask: but wait, what about mixins using Module#include
?
现在,您可能会问:但等等,使用Module#include
的混合有什么问题?
The answer is: Module#include
(or rather Module#append_features
) synthesizes a new class (sometimes called an include class) which shares its method table, constant table, class variable table, and instance variable table with the module and makes that class the superclass of the class the module is mixed into (and it does that recursively for all modules that were mixed into that module).
答案是:Module#include
(或者更确切地说是Module#append_features
)合成一个新的类(有时称为include类),该类与模块共享其方法表、常量表、类变量表和实例变量表,并将该类作为模块混合到的类的超类(对于所有混合到该模块的模块,它都会递归执行此操作)。
So, now we know the two pieces of information required to figure out whether or not a method is callable:
因此,现在我们知道了确定方法是否可调用所需的两个信息:
-
We need to know in which method table, i.e. in which module the method is defined.
-
我们需要知道方法定义在哪个方法表中,即在哪个模块中。
-
We need to know what the chain of superclasses is for the receiver.
-
我们需要知道接收器的超类链是什么样的。
Once we know those two things, we can check whether the module the method is defined in is part of the lookup chain of the receiver.
一旦我们知道这两点,我们可以检查方法定义所在的模块是否是接收器查找链的一部分。
Answering #1 is easy. We can use Object#method
to create a Method
object (a reflective proxy) for the method we want to know about, and then we can use Method#owner
to ask the method where it is defined.
回答#1很容易。我们可以使用Object#method
为我们想了解的方法创建一个Method
对象(反射代理),然后我们可以使用Method#owner
来询问方法在哪里定义。
So, we know that calling the method at the top-level works, which means we can ask the top-level object for the method:
所以,我们知道在顶层调用方法是有效的,这意味着我们可以向顶层对象询问该方法:
m = method(:lc)
And now we can ask the method for its owner:
现在,我们可以询问方法的所有者:
m.owner
#=> #<Class:#<Object:0x0000000104c3cd00>>
Uh. Okay. That's not very helpful. What this tells us is the following:
嗯。好吧。这并不是非常有帮助。这告诉我们以下内容:
-
The method is defined in a singleton class.
-
该方法在单例类中定义。
-
The object the singleton class belongs to is a direct instance of
Object
. -
单例类所属的对象是
Object
的直接实例。 -
And that's it.
-
就是这样。
So, all we know is that the method is defined in a singleton class of some object. We have to dig a bit deeper.
因此,我们只知道该方法在某个对象的单例类中定义。我们需要深入挖掘一下。
One possibility would
英文:
> Does anyone know why the method is 'defined' outside of the class but not inside?
The method lookup algorithm in Ruby is relatively simple. (At least it was until the introduction of Module#prepend
/ Module#prepend_features
, so let's ignore that for now). Here's how it goes:
- Retrieve the value of the hidden
class
pointer of the receiver. - Check whether the method table contains the method.
- If yes, you are done.
- If not, continue to step #3.
- Retrieve the value of the superclass pointer.
- If there is no superclass, go to step #4.
- If there is a superclass, repeat from step #2.
- Start the algorithm from the beginning with the message selector
method_missing
, passing the name of the original message and its arguments as arguments.
And that's it. This is always guaranteed to terminate because the top-level class always has a method named method_missing
.
Now, you might ask: but wait, what about singleton classes?
The answer is: the hidden class
pointer of an object points to its singleton class, and the singleton class's superclass pointer points (directly or indirectly) to the "actual" class.
Now, you might ask: but wait, why doesn't Object#class
return the singleton class, then?
The answer is: Object#class
doesn't directly return the hidden class
pointer. Instead, it follows the superclass chain until it finds the first "normal" class.
Now, you might ask: but wait, what about mixins using Module#include
?
The answer is: Module#include
(or rather Module#append_features
) synthesizes a new class (sometimes called an include class) which shares its method table, constant table, class variable table, and instance variable table with the module and makes that class the superclass of the class the module is mixed into (and it does that recursively for all modules that were mixed into that module).
This "trick" of inserting singleton classes and include classes into the normal class chain means that operations which happen often are fast and simple, namely method lookup. The complexity is moved to operations like calling Object#class
or Module#include
, but that's okay because those are only rarely called and typically only happen during debugging or application startup.
So, now we know the two pieces of information required to figure out whether or not a method is callable:
- We need to know in which method table, i.e. in which module the method is defined.
- We need to know what the chain of superclasses is for the receiver.
Once we know those two things, we can check whether the module the method is defined in is part of the lookup chain of the receiver.
Answering #1 is easy. We can use Object#method
to create a Method
object (a reflective proxy) for the method we want to know about, and then we can use Method#owner
to ask the method where it is defined.
So, we know that calling the method at the top-level works, which means we can ask the top-level object for the method:
m = method(:lc)
And now we can ask the method for its owner:
m.owner
#=> #<Class:#<Object:0x0000000104c3cd00>>
Uh. Okay. That's not very helpful. What this tells us is the following:
- The method is defined in a singleton class.
- The object the singleton class belongs to is a direct instance of
Object
. - And that's it.
So, all we know is that the method is defined in a singleton class of some object. We have to dig a bit deeper.
One possibility would be to use Method#source_location
to find where the source code of the method is. However, this not guaranteed to work: for example, methods in C extensions for YARV or methods written in Java in JRuby or TruffleRuby do not have "(Ruby) source code", so this method will return nil
.
In this case, the method will work and will give the path to the file inside the Gem and the line number where the method is defined.
However, let's see if we can find out more. Every object in Ruby has the Object#inspect
method, whose purpose it is to provide human-readable debug information. Well, we are debugging and we are humans (I assume), so let's see what we can get.
We shouldn't be too hopeful, because for a typical singleton method, that will look something like this:
foo = Object.new
def foo.bar(a, b = 23, *c, d, e:, f: 42, **g, &h) end
b = foo.method(:bar)
b.inspect
#=> '#<Method: #<Object:0x0000000109053ca0>.bar(a, b=..., *c, d, e:, f: ..., **g, &h) /path/to/source/file.rb:2>'
Which basically just gives us the same information we can also get from Method#owner
, Method#source_location
, and Method#parameters
, but in a nice and compact way.
But let's try it anyway:
m.inspect
#=> '#<Method: main.lc(text, to, from=...) /usr/lib/ruby/gems/3.2.0/gems/language-converter-1.0.0/lib/language_converter.rb:14>'
That's interesting! The method is actually displayed as main.lc(text, to, from=...)
and not something like #<Object:0x0000000109053ca0>.lc(text, to, from=...)
. So, what is main
?
main
is the informal name of the so-called top-level object, i.e. the object which is self
in code that is at the top-level of a script, not enclosed in any module:
toplevel = self
toplevel.inspect
#=> 'main'
This means that the method lc
is defined in the singleton class of main
, something like this:
def self.lc(text, to, from='en')
# …
end
And this, finally, explains why you can't call it from anywhere but the top-level:
lc
works, because the implicit receiver self
here is main
, the hidden class pointer of main
points to main.singleton_class
, and that's where lc
is defined.
But
t.translate(…)
doesn't work, because inside translate
, where lc
is called without a receiver, the implicit receiver self
is t
whose method lookup chain looks something like this:
t
's hidden class pointer which ist.singleton_class
.t.singleton_class
's hidden superclass pointer which ist.singleton_class.superclass
which ist.class
which isTranslator
.Translator
's hidden superclass pointer which isTranslator.superclass
which isObject
.Object
's hidden superclass pointer which isObject
's include class forKernel
.Object
's include class forKernel
's hidden superclass pointer which isObject.superclass
which isBasicObject
.
You can roughly approximate this by using Ruby Reflection:
t.singleton_class.ancestors
#=> [
# #<Class:#<Translator:0x00000001023c8ef0>>,
# Translator,
# Object,
# Kernel,
# BasicObject
# ]
As you can see, main.singleton_class
does not appear anywhere in the method lookup chain, and thus, lc
will not be found.
If you really, absolutely, have to use this method, there are a few possible workarounds.
You could grab a reference to the method and keep it around for later:
LC = method(:lc)
class Translator
# …
def translate text
LC.(text, @to, @from)
end
end
You could grab a reference to main
and keep it around for later:
MAIN = self
class Translator
# …
def translate text
MAIN.lc(text, @to, @from)
end
end
In fact, Ruby has a pre-defined constant for the whole top-level Binding
called TOPLEVEL_BINDING
. You can get the top-level object using the Binding#receiver
method:
class Translator
# …
def translate text
TOPLEVEL_BINDING.receiver.lc(text, @to, @from)
end
end
This is a weird way to define a method. It makes this method very hard and annoying to use.
Typically, methods like this, which are meant to be called at the top-level with an implicit receiver are defined as instance methods of Kernel
. Some methods you probably recognize are Kernel#puts
or Kernel#require
.
In fact, by the way the method is indented in the code, I am not even sure this was intended.
Which brings us to your other question:
> I cannot find the source code on its homepage
As shown above, you can always find the location of the source code of a method by simply asking it:
m.source_location
#=> '/usr/lib/ruby/gems/3.2.0/gems/language-converter-1.0.0/lib/language_converter.rb:14'
So, on my system, the source code for lc
is in the file at the path /usr/lib/ruby/gems/3.2.0/gems/language-converter-1.0.0/lib/language_converter.rb
starting at line 14. On your system, the path will obviously be slightly different.
The easiest way to get access to the source code of the entire gem would be to use the gem open
command:
gem open language-server
This will open the whole gem directory in your default editor.
Another command that is sometimes useful is gem unpack
which unpacks an installed gem into the current directory. If you only want to read the source code, that is overkill since the gem is already unpacked into your gem directory when you install it. But if you want to make changes, you obviously shouldn't do this in the gem directory itself.
Please note: if you decide to actually do this and read this gem's source code, please do not learn anything from it, except maybe how not to write Ruby. It is horrible.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论