Может кто-нибудь объяснить, пожалуйста, как работает код - PullRequest
4 голосов
/ 16 октября 2019

Есть некоторый код:

def func
   def func
      1
   end
end

, затем я пробую следующее в irb:

func
func.func
func

и получаю результат:

:func
1
1

Может ли кто-нибудь объяснить, что происходит? Я вроде понимаю первый вывод, но не последний. Спасибо!

Ответы [ 2 ]

5 голосов
/ 16 октября 2019

Вы определяете метод внутри метода в глобальной области видимости. Определение метода возвращает символ с его именем.

  1. Когда вы звоните func в первый раз, он переопределяется внутренним func. Вот почему последующие вызовы func return 1.
  2. Определение метода возвращает символ, и вы можете вызвать любой глобально определенный метод для символа, поэтому вы можете вызвать func.func. Попробуйте определить другой метод, и вы сможете вызвать его для любого символа:
def func
   def func
      1
   end
end
def a
  'a'
end
func.a
# 'a'
:asd.a
# 'a'
4 голосов
/ 16 октября 2019

Это сложно. Это действительно не должно работать в Ruby, но оно работает.

def func на верхнем уровне определяет глобальный метод. Но как это работает в Ruby? На самом деле он использует два хака:

  1. Он добавляет метод к Object, чтобы вы могли вызывать его из любого места (поскольку self всегда является Object)
  2. Этоделает метод private, потому что правило для частных методов состоит в том, что у них не может быть «явного получателя», то есть вы должны вызывать их, не ставя перед ними объект и точку. Это предотвращает ошибки, например, если я думаю, что у класса Foo есть собственный метод puts, но на самом деле его нет, Foo.new.puts вызовет ошибку, а не вызовет глобальный puts.

Так что, если бы ваш код был просто

def func
  1
end

1.func

Сбой, потому что вы пытаетесь вызвать закрытый метод для объекта 1.

Но вот где это становится странным. Если вы определяете метод внутри другого метода, он определяет обычный метод экземпляра, как если бы он вообще не был вложенным

class A
  def outer
    def inner
      3
    end
  end
end

x = A.new
y = A.new
x.outer
y.inner # calls the method defined by x

Это не было намеренным проектом в Ruby, носкорее запасной вариант для странной ситуации. ведущему конструктору не нравится, что это даже возможно

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

Ваш код ведет себя так странно, потому что вы делаете это на верхнем уровне, где self простоObject:

def func # normal private method in Object
  def func # adds a normal instance method to the current class i.e. Object
    1
  end
end

Object.private_methods.include?(:func) # true
func # returns :func, but also now re-defines func to be a normal method on Object
Object.private_methods.include?(:func) # false
func.func # same as 1.func, which is OK because it's not private
...