Возможно ли иметь методы внутри методов? - PullRequest
83 голосов
/ 01 февраля 2011

У меня есть метод внутри метода. Внутренний метод зависит от переменной цикла, который выполняется. Это плохая идея?

Ответы [ 6 ]

155 голосов
/ 01 февраля 2011

ОБНОВЛЕНИЕ: Поскольку в последнее время этот ответ, похоже, вызвал некоторый интерес, я хотел бы отметить, что существует обсуждение средства отслеживания проблем Ruby для удаления обсуждаемой здесь функции, а именно запретить иметь определения метода внутри тела метода .


Нет, у Ruby нет вложенных методов.

Вы можете сделать что-то вроде этого:

class Test1
  def meth1
    def meth2
      puts "Yay"
    end
    meth2
  end
end

Test1.new.meth1

Но это не вложенный метод. Я повторяю: Ruby не имеет вложенных методов.

Это динамическое определение метода. При запуске meth1 будет выполнено тело meth1. Тело просто определяет метод с именем meth2, поэтому после однократного запуска meth1 вы можете вызвать meth2.

Но где определяется meth2? Что ж, очевидно, что не определен как вложенный метод, поскольку в Ruby нет вложенных методов . Он определен как метод экземпляра Test1:

Test1.new.meth2
# Yay

Кроме того, он, очевидно, будет переопределяться при каждом запуске meth1:

Test1.new.meth1
# Yay

Test1.new.meth1
# test1.rb:3: warning: method redefined; discarding old meth2
# test1.rb:3: warning: previous definition of meth2 was here
# Yay

Короче говоря: нет, Ruby не не поддерживает вложенные методы.

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


ОБНОВЛЕНИЕ ПРОДОЛЖАЕТСЯ: на этапе позже этот синтаксис может быть повторно использован для добавления вложенных методов в Ruby, который будет вести себя так, как я описал: они будут ограничены их содержащим методом, то есть невидимые и недоступные вне тела, содержащего метод. И, возможно, они будут иметь доступ к лексической области действия своего содержащего метода. Однако, если вы прочитаете обсуждение, которое я связал выше, вы заметите, что matz сильно против вложенных методов (но все же для удаления определений вложенных методов).

10 голосов
/ 02 марта 2016

На самом деле это возможно.Для этого вы можете использовать procs / lambda.

def test(value)
  inner = ->() {
    value * value
  }
  inner.call()
end
5 голосов
/ 05 января 2013

Нет, нет, у Ruby есть вложенные методы.Проверьте это:

def outer_method(arg)
    outer_variable = "y"
    inner_method = lambda {
      puts arg
      puts outer_variable
    }
    inner_method[]
end

outer_method "x" # prints "x", "y"
1 голос
/ 16 июня 2014

Вы можете сделать что-то вроде этого

module Methods
  define_method :outer do 
    outer_var = 1
    define_method :inner do
      puts "defining inner"
      inner_var = outer_var +1
    end
    outer_var
  end
  extend self
end

Methods.outer 
#=> defining inner
#=> 1
Methods.inner 
#=> 2

Это полезно, когда вы делаете такие вещи, как написание DSL, которые требуют разделения области действия между методами. Но в остальном вам гораздо лучше делать что-то еще, потому что, как говорили другие ответы, inner переопределяется всякий раз, когда вызывается outer. Если вы хотите такого поведения, а иногда и можете, это хороший способ получить его.

0 голосов
/ 13 мая 2017

Способ Ruby состоит в том, чтобы подделать его путаницами, которые заставят некоторых пользователей задуматься: «Как, черт возьми, это вообще работает?», А менее любопытные просто запомнят синтаксис, необходимый для использования этой вещи. Если вы когда-либо использовали Rake или Rails, вы видели подобные вещи.

Вот такой вот взлом:

def mlet(name,func)
  my_class = (Class.new do
                def initialize(name,func)
                  @name=name
                  @func=func
                end
                def method_missing(methname, *args)
                  puts "method_missing called on #{methname}"
                  if methname == @name
                    puts "Calling function #{@func}"
                    @func.call(*args)
                  else
                    raise NoMethodError.new "Undefined method `#{methname}' in mlet"
                  end
                end
              end)
  yield my_class.new(name,func)
end

Что определяет метод верхнего уровня, который создает класс и передает его в блок. Класс использует method_missing, чтобы притвориться, что у него есть метод с именем, которое вы выбрали. Он «реализует» метод, вызывая лямбду, которую вы должны предоставить. Называя объект однобуквенным именем, вы можете минимизировать количество дополнительной типизации, которое ему требуется (это то же самое, что делает Rails в его schema.rb). mlet назван в честь формы Common Lisp flet, за исключением случаев, когда f обозначает «функцию», m обозначает «метод».

Вы используете это так:

def outer
   mlet :inner, ->(x) { x*2 } do |c|
     c.inner 12
   end
end

Можно создать аналогичное устройство, которое позволяет определять несколько внутренних функций без дополнительного вложения, но требует еще более уродливого взлома, подобного тому, который вы можете найти в реализации Rake или Rspec. Выяснение того, как работает let! Rspec, поможет вам создать такую ​​ужасную мерзость.

0 голосов
/ 31 января 2014

: - D

В Ruby есть вложенные методы, только они не делают того, чего вы ожидаете от них

1.9.3p484 :001 > def kme; 'kme'; def foo; 'foo'; end; end              
 => nil 
1.9.3p484 :003 >   self.methods.include? :kme
 => true 
1.9.3p484 :004 > self.methods.include? :foo
 => false 
1.9.3p484 :005 > kme
 => nil 
1.9.3p484 :006 > self.methods.include? :foo
 => true 
1.9.3p484 :007 > foo
 => "foo" 
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...