Czy można mieć metody w metodach?

89

Mam metodę wewnątrz metody. Metoda wewnętrzna zależy od zmiennej pętli, która jest uruchamiana. Czy to zły pomysł?

Wyjazd
źródło
2
Czy możesz udostępnić przykładowy kod lub przynajmniej logiczny odpowiednik tego, co próbujesz zrobić.
Aaron Scruggs

Odpowiedzi:

165

AKTUALIZACJA: Ponieważ ta odpowiedź wydaje się ostatnio wzbudzać zainteresowanie, chciałem zwrócić uwagę, że toczy się dyskusja na temat modułu śledzenia problemów w Rubim, aby usunąć omawianą tutaj funkcję, a mianowicie zakazać umieszczania definicji metod w treści metody .


Nie, Ruby nie ma zagnieżdżonych metod.

Możesz zrobić coś takiego:

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

Test1.new.meth1

Ale to nie jest metoda zagnieżdżona. Powtarzam: Ruby nie ma zagnieżdżonych metod.

To jest dynamiczna definicja metody. Kiedy uciekniesz meth1, ciało meth1zostanie wykonane. Po prostu ciało definiuje metodę o nazwie meth2, dlatego po meth1jednokrotnym uruchomieniu możesz wywołać meth2.

Ale gdzie jest meth2zdefiniowane? Cóż, oczywiście nie jest zdefiniowana jako metoda zagnieżdżona, ponieważ w Rubim nie ma zagnieżdżonych metod. Jest zdefiniowany jako metoda instancji Test1:

Test1.new.meth2
# Yay

Poza tym będzie on oczywiście przedefiniowywany za każdym razem, gdy uruchomisz 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

W skrócie: nie, Ruby nie obsługuje zagnieżdżonych metod.

Zauważ również, że w Rubim, ciała metod nie mogą być zamknięciami, mogą nimi być tylko obiekty blokowe. To praktycznie eliminuje główny przypadek użycia metod zagnieżdżonych, ponieważ nawet jeśli Ruby obsługuje metody zagnieżdżone, nie można użyć zmiennych metody zewnętrznej w metodzie zagnieżdżonej.


AKTUALIZACJA KONTYNUOWANA: na późniejszym etapie ta składnia może zostać ponownie użyta do dodania zagnieżdżonych metod do Rubiego, co zachowywałoby się tak, jak opisałem: byłyby ograniczone do ich metody zawierającej, tj. Byłyby niewidoczne i niedostępne poza ich metodą zawierającą ciało. Być może mieliby dostęp do zakresu leksykalnego swojej metody zawierającej. Jeśli jednak przeczytasz dyskusję, do której dołączyłem powyżej, możesz zauważyć, że matz jest silnie przeciwny metodom zagnieżdżonym (ale nadal usuwa definicje metod zagnieżdżonych).

Jörg W Mittag
źródło
6
Możesz również pokazać, jak utworzyć lambdę zamknięcia w metodzie DRYness lub uruchomić rekursję.
Phrogz
119
Mam wrażenie, że Ruby może nie mieć zagnieżdżonych metod.
Mark Thomas
16
@Mark Thomas: Czy zapomniałem wspomnieć, że Ruby nie ma zagnieżdżonych metod? :-) Poważnie: w tym czasie pisałem tę odpowiedź, nie były już trzy odpowiedzi, z których każdy jeden twierdził, że Ruby nie posiada metody zagnieżdżonych. Niektóre z tych odpowiedzi otrzymały nawet głosy za, mimo że były rażąco błędne. Jeden z nich został nawet zaakceptowany przez PO, mimo że się mylił. Fragment kodu, którego używa odpowiedź, aby udowodnić, że Ruby obsługuje zagnieżdżone metody, w rzeczywistości udowadnia coś przeciwnego, ale najwyraźniej ani użytkownicy upvoters, ani OP nie zawracali sobie głowy sprawdzaniem. Więc podałem jedną dobrą odpowiedź na każdą złą. :-)
Jörg W Mittag
10
Kiedy zdasz sobie sprawę, że są to tylko instrukcje dla jądra, które modyfikują tabele i że metody, klasy i moduły są tylko wpisami w tabelach i nie są tak naprawdę rzeczywiste, stajesz się jak Neo, gdy widzi, jak wygląda Matrix. Wtedy można by naprawdę stać się filozofem i powiedzieć, że poza metodami zagnieżdżonymi nie ma nawet metod. Nie ma nawet agentów. Są to programy w macierzy. Nawet ten soczysty stek, który jesz, to tylko wpis na stole.
mydoghasworms
3
Nie ma żadnych metod, twój kod jest tylko symulacją w Matrixie
bbozo
13

Właściwie to możliwe. Możesz do tego użyć procs / lambda.

def test(value)
  inner = ->() {
    value * value
  }
  inner.call()
end
SuperManEver
źródło
2
Nie mylisz się, ale twoja odpowiedź jest sformułowana jako rozwiązanie umożliwiające osiągnięcie zagnieżdżonych metod. Kiedy w rzeczywistości używasz tylko procsów, które nie są metodami. To dobra odpowiedź poza twierdzeniem, że rozwiązuje "zagnieżdżone metody"
Brandon Buck
5

Nie, nie, Ruby ma zagnieżdżone metody. Sprawdź to:

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

outer_method "x" # prints "x", "y"
iirekm
źródło
9
wewnętrzna_metoda nie jest metodą, jest funkcją / lambda / proc. Nie ma skojarzonej instancji żadnej klasy, więc nie jest to metoda.
Sami Samhuri
2

Możesz zrobić coś takiego

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

Jest to przydatne, gdy robisz takie rzeczy, jak pisanie DSL, które wymagają dzielenia zakresu między metodami. Ale w przeciwnym razie znacznie lepiej zrobisz cokolwiek innego, ponieważ jak mówią inne odpowiedzi, innerjest przedefiniowywana za każdym razem, gdy outerjest wywoływana. Jeśli chcesz takiego zachowania, a czasami możesz, jest to dobry sposób, aby to osiągnąć.

mdmoskwa
źródło
2

Sposób w Ruby polega na sfałszowaniu go za pomocą mylących hacków, które sprawią, że niektórzy użytkownicy będą się zastanawiać „Jak to do cholery w ogóle działa?”, Podczas gdy mniej ciekawi po prostu zapamiętają składnię potrzebną do użycia tej rzeczy. Jeśli kiedykolwiek korzystałeś z Rake lub Rails, widziałeś tego rodzaju rzeczy.

Oto taki hack:

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

To, co robi, to zdefiniowanie metody najwyższego poziomu, która tworzy klasę i przekazuje ją do bloku. Klasa używa method_missingdo udawania, że ​​ma metodę o wybranej przez Ciebie nazwie. „Implementuje” metodę, wywołując lambdę, którą musisz podać. Nazywając obiekt jednoliterową nazwą, możesz zminimalizować ilość dodatkowego wpisywania, którego wymaga (co jest tym samym, co robi w nim Rails schema.rb). mletjest nazwany na podstawie formy Common Lisp flet, z wyjątkiem sytuacji, gdy foznacza „funkcję”, moznacza „metodę”.

Używasz tego w ten sposób:

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

Możliwe jest stworzenie podobnego urządzenia, które pozwala na zdefiniowanie wielu funkcji wewnętrznych bez dodatkowego zagnieżdżania, ale wymaga to jeszcze brzydszego hackowania, takiego jaki można znaleźć w implementacji Rake'a lub Rspec. Dowiedzenie się, jak let!działa Rspec, pozwoliłoby ci stworzyć tak okropną obrzydliwość.

Wyrzuć konto
źródło
-3

:-RE

Ruby ma zagnieżdżone metody, ale nie robią one tego, czego można by od nich oczekiwać

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" 
bbozo
źródło
4
To nie jest metoda zagnieżdżona ... zobacz odpowiedź Jörga W. Mittaga dla jasnego zrozumienia.
Hardik,