Чи можна мати Методи всередині Методів?


89

У мене є метод всередині методу. Внутрішній метод залежить від циклу змінних, який виконується. Це погана ідея?


2
Чи можете ви поділитися зразком коду або хоча б логічним еквівалентом того, що ви намагаєтесь зробити.
Aaron Scruggs

Відповіді:


165

ОНОВЛЕННЯ: Оскільки ця відповідь останнім часом викликала певний інтерес, я хотів би зазначити, що в трекері видань Ruby ведеться дискусія щодо видалення обговорюваної тут функції, а саме щодо заборони мати визначення методів всередині тіла методу .


Ні, у Ruby немає вкладених методів.

Ви можете зробити щось подібне:

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

Test1.new.meth1

Але це не вкладений метод. Повторюю: Рубі не має вкладених методів.

Що це таке - це визначення динамічного методу. Коли ви запустите 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 в значній мірі проти вкладених методів (але все одно для видалення визначень вкладених методів).


6
Ви також можете показати, як створити замикання лямбда в методі для СУХОСТІ або запустити рекурсію.
Phrogz

119
У мене складається відчуття, що Рубі, можливо, не вклала методи.
Mark Thomas

16
@ Марк Томас: Я забув згадати, що Ruby не має вкладених методів? :-) Серйозно: на момент написання цієї відповіді вже було три відповіді, кожна з яких стверджувала, що Рубі дійсно має вкладені методи. Деякі з цих відповідей навіть мали позитивні голоси, незважаючи на грубу помилку. Одного навіть ОП прийняла, знову ж таки, незважаючи на помилку. Фрагмент коду, який використовує відповідь, щоб довести, що Ruby підтримує вкладені методи, насправді доводить протилежне, але, мабуть, ані виборці, ані OP насправді не потрудились перевірити. Отже, я дав одну правильну відповідь на кожну неправильну. :-)
Йорг Ш Міттаг

10
Коли ви усвідомлюєте, що це все лише інструкції до ядра, які змінюють таблиці, і що методи, класи та модулі - це лише записи в таблицях, а насправді не справжні, ви стаєте схожими на Нео, коли він бачить, як виглядає Матриця. Тоді ви могли б справді стати філософським і сказати, що крім вкладених методів, немає навіть методів. Тут навіть немає агентів. Вони є програмами в матриці. Навіть той соковитий стейк, який ви їсте, - це лише запис у таблиці.
mydoghasworms

3
Немає методів, ваш код - це просто імітація в The Matrix
bbozo 02

13

Насправді це можливо. Для цього ви можете використовувати procs / lambda.

def test(value)
  inner = ->() {
    value * value
  }
  inner.call()
end

2
Ви не помиляєтесь, але ваша відповідь сформульована як рішення для досягнення вкладених методів. Насправді ви просто використовуєте прокси, які не є методами. Це прекрасна відповідь поза заявою про вирішення "вкладених методів"
Брендон Бак,

5

Ні, ні, у 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"

9
inner_method - це не метод, це функція / lambda / proc. Немає пов'язаного екземпляра жодного класу, тому це не метод.
Самі Самхурі

2

Ви можете зробити щось подібне

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викликається. Якщо ви хочете такої поведінки, а іноді можливо, це хороший спосіб отримати її.


2

Шлях Ruby полягає в тому, щоб підробити його з заплутаними хаками, через які деякі користувачі задаються питанням: "Як, чорт візьми, це взагалі працює?", Тоді як менш цікаві просто запам'ятають синтаксис, необхідний для використання цієї речі. Якщо ви коли-небудь користувались граблями або рейками, ви бачили подібні речі.

Ось такий хак:

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. З’ясувавши, як роботи Rspec let!допоможуть вам створити таку жахливу гидоту.


-3

:-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" 

4
Це не вкладений метод ... див. Відповідь Йорга В. Міттага для чіткого розуміння.
Хардік
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.