
[英]In Ruby or Rails, why is “include” sometimes inside the class and sometimes outside the class?
[英]Why does ruby recognise a method outside of a class, but not inside?
我正在嘗試構建一個簡單的語言翻譯程序。 我導入了“language_converter”gem 來幫助實現這個目標。 我寫了以下代碼:
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
方法並返回一些日語。 有誰知道為什么該方法是在 class 之外而不是在內部“定義”的?
編輯:語言轉換器gem 不是我自己的。 另外,我在它的主頁上找不到源代碼。
我還嘗試在lc
方法之前添加兩個分號,如下所示: ::lc(text, @to,@from)
。 這導致錯誤: syntax error, unexpected local variable or method, expecting constant
寶石已有10多年歷史,只有一種方法。 該方法實現為 class 方法。
使用現代 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
有誰知道為什么該方法是在 class 之外而不是在內部“定義”的?
Ruby中的方法查找算法比較簡單。 (至少在引入Module#prepend
/ Module#prepend_features
之前是這樣,所以我們暫時忽略它)。 事情是這樣的:
class
指針的值。method_missing
開始算法,將原始消息的名稱及其 arguments 作為 arguments 傳遞。 就是這樣。 這總是保證終止,因為頂級 class 總是有一個名為method_missing
的方法。
現在,您可能會問:但是等等,singleton 個類呢?
答案是: class
的隱藏指針 class 指向其 singleton class,singleton 類的超類指針(直接或間接)指向“實際”883981121951 88.
現在,您可能會問:等等,為什么Object#class
不返回 singleton class?
答案是: Object#class
不直接返回隱藏的class
指針。 相反,它遵循超類鏈,直到找到第一個“正常”class。
現在,您可能會問:但是等等,使用Module#include
mixins 怎么樣?
答案是: Module#include
(或者更確切地說Module#append_features
)合成了一個新的 class (有時稱為include class ),它與模塊共享其方法表、常量表、class 變量表和實例變量表,並使 class 成為模塊混入的 class 的超類(並且它對混入該模塊的所有模塊遞歸執行此操作)。
這種將 singleton 類和包含類插入正常 class 鏈的“技巧”意味着經常發生的操作快速而簡單,即方法查找。 復雜性轉移到調用Object#class
或Module#include
等操作上,但這沒關系,因為它們很少被調用,通常只在調試或應用程序啟動期間發生。
所以,現在我們知道了確定方法是否可調用所需的兩條信息:
一旦我們知道了這兩件事,我們就可以檢查定義該方法的模塊是否是接收方查找鏈的一部分。
回答#1 很容易。 我們可以使用Object#method
為我們想知道的方法創建一個Method
object(反射代理),然后我們可以使用Method#owner
來詢問方法定義在哪里。
所以,我們知道在頂層調用方法是有效的,這意味着我們可以向頂層 object 請求方法:
m = method(:lc)
現在我們可以為它的所有者詢問方法:
m.owner
#=> #<Class:#<Object:0x0000000104c3cd00>>
呃。 好的。 那不是很有幫助。 這告訴我們以下內容:
Object
的直接實例。所以,我們所知道的是,該方法定義在某個 object 的 singleton class 中。我們必須深入挖掘一下。
一種可能性是使用Method#source_location
來查找方法的源代碼所在的位置。 但是,這不能保證有效:例如,C 中的 YARV 擴展方法或 Java 中 JRuby 或 TruffleRuby 中編寫的方法沒有“(Ruby)源代碼”,因此該方法將返回nil
。
在這種情況下,該方法將起作用,並將給出 Gem 內文件的路徑和定義該方法的行號。
但是,讓我們看看是否可以找到更多信息。 Ruby 中的每個 object 都有Object#inspect
方法,其目的是提供人類可讀的調試信息。 好吧,我們正在調試並且我們是人類(我假設),所以讓我們看看我們能得到什么。
我們不應該抱太大希望,因為對於典型的 singleton 方法,它看起來像這樣:
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>'
這基本上只是給了我們同樣的信息,我們也可以從Method#owner
、 Method#source_location
和Method#parameters
獲得相同的信息,但是是以一種漂亮而緊湊的方式。
但無論如何讓我們嘗試一下:
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>'
那很有意思! 該方法實際上顯示為main.lc(text, to, from=...)
而不是像#<Object:0x0000000109053ca0>.lc(text, to, from=...)
東西。 那么,什么是main
?
main
是所謂的頂層 object的非正式名稱,即 object 是腳本頂層代碼中的self
,不包含在任何模塊中:
toplevel = self
toplevel.inspect
#=> 'main'
這意味着方法lc
在main
的 singleton class 中定義,如下所示:
def self.lc(text, to, from='en')
# …
end
最后,這解釋了為什么你不能從頂層以外的任何地方調用它:
lc
有效,因為這里的隱式接收者self
是main
, main
的隱藏 class 指針指向main.singleton_class
,這就是定義lc
的地方。
但
t.translate(…)
不起作用,因為在translate
內部,在沒有接收者的情況下調用lc
,隱式接收者self
是t
其方法查找鏈如下所示:
t
隱藏了 class 指針,即t.singleton_class
。t.singleton_class
的隱藏超類指針是t.singleton_class.superclass
是t.class
是Translator
。Translator
的隱藏超類指針是Translator.superclass
,它是Object
。Object
的隱藏超類指針即Object
包含 class 對應Kernel
。Object
包含 class 用於Kernel
的隱藏超類指針,即Object.superclass
,即BasicObject
。您可以使用 Ruby 反射粗略估計:
t.singleton_class.ancestors
#=> [
# #<Class:#<Translator:0x00000001023c8ef0>>,
# Translator,
# Object,
# Kernel,
# BasicObject
# ]
如您所見, main.singleton_class
沒有出現在方法查找鏈中的任何位置,因此,將找不到lc
。
如果您真的、絕對必須使用此方法,則有一些可能的解決方法。
您可以獲取對該方法的引用並將其保留以備后用:
LC = method(:lc)
class Translator
# …
def translate text
LC.(text, @to, @from)
end
end
您可以獲取對main
引用並將其保留以備后用:
MAIN = self
class Translator
# …
def translate text
MAIN.lc(text, @to, @from)
end
end
事實上, Ruby 有一個預定義的常量用於整個頂級Binding
,稱為TOPLEVEL_BINDING
。 您可以使用Binding#receiver
方法獲取頂級 object:
class Translator
# …
def translate text
TOPLEVEL_BINDING.receiver.lc(text, @to, @from)
end
end
這是一種定義方法的奇怪方式。 這使得這種方法非常難以使用且令人討厭。
通常,像這樣的方法,旨在通過隱式接收器在頂層調用,被定義為Kernel
的實例方法。 您可能認識的一些方法是Kernel#puts
或Kernel#require
。
事實上,順便說一句,該方法在代碼中是縮進的,我什至不確定這是故意的。
這讓我們想到了您的另一個問題:
我在它的主頁上找不到源代碼
如上所示,你總是可以通過簡單地詢問它來找到方法源代碼的位置:
m.source_location
#=> '/usr/lib/ruby/gems/3.2.0/gems/language-converter-1.0.0/lib/language_converter.rb:14'
因此,在我的系統上, lc
的源代碼位於路徑/usr/lib/ruby/gems/3.2.0/gems/language-converter-1.0.0/lib/language_converter.rb
從第 14 行開始的文件中. 在你的系統上,路徑顯然會略有不同。
訪問整個 gem 源代碼的最簡單方法是使用gem open
命令:
gem open language-server
這將在您的默認編輯器中打開整個 gem 目錄。
另一個有時有用的命令是gem unpack
,它將安裝的 gem 解包到當前目錄中。 如果您只想閱讀源代碼,那太過分了,因為 gem 在您安裝時已經解壓到您的 gem 目錄中。 但是如果您想進行更改,您顯然不應該在 gem 目錄本身中執行此操作。
請注意:如果您決定實際執行此操作並閱讀此 gem 的源代碼,請不要從中學到任何東西,除了可能如何不寫 Ruby。這太可怕了。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.