È possibile avere metodi all'interno di metodi?


89

Ho un metodo all'interno di un metodo. Il metodo interno dipende da un ciclo variabile in esecuzione. È una cattiva idea?


2
Puoi condividere il codice di esempio o almeno un equivalente logico di ciò che stai cercando di fare.
Aaron Scruggs

Risposte:


165

AGGIORNAMENTO: poiché questa risposta sembra aver suscitato un certo interesse ultimamente, volevo sottolineare che c'è una discussione sul tracker dei problemi di Ruby per rimuovere la funzionalità discussa qui, vale a dire per proibire di avere definizioni di metodo all'interno di un corpo del metodo .


No, Ruby non ha metodi annidati.

Puoi fare qualcosa del genere:

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

Test1.new.meth1

Ma questo non è un metodo annidato. Ripeto: Ruby non ha metodi annidati.

Ciò che è, è una definizione di metodo dinamico. Quando corri meth1, il corpo di meth1verrà eseguito. Il corpo definisce semplicemente un metodo denominato meth2, motivo per cui dopo aver eseguito meth1una volta, puoi chiamare meth2.

Ma dove si meth2definisce? Ovviamente non è definito come un metodo annidato, poiché non ci sono metodi annidati in Ruby. È definito come un metodo di istanza di Test1:

Test1.new.meth2
# Yay

Inoltre, verrà ovviamente ridefinito ogni volta che esegui 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

In breve: no, Ruby non supporta i metodi annidati.

Nota anche che in Ruby, i corpi dei metodi non possono essere chiusure, ma solo i corpi dei blocchi lo possono. Questo praticamente elimina il caso d'uso principale per i metodi nidificati, poiché anche se Ruby supportava i metodi nidificati, non era possibile utilizzare le variabili del metodo esterno nel metodo nidificato.


AGGIORNAMENTO CONTINUATO: in una fase successiva , quindi, questa sintassi potrebbe essere riutilizzata per aggiungere metodi annidati a Ruby, che si comporterebbe nel modo descritto: sarebbero limitati al loro metodo contenitore, cioè invisibili e inaccessibili al di fuori del loro metodo contenitore corpo. E forse, avrebbero accesso all'ambito lessicale del loro metodo di contenimento. Tuttavia, se leggi la discussione che ho collegato sopra, puoi osservare che matz è fortemente contrario ai metodi annidati (ma ancora per rimuovere le definizioni dei metodi annidati).


6
Tuttavia, potresti anche mostrare come creare un lambda di chiusura in un metodo per DRYness o come eseguire la ricorsione.
Phrogz

119
Ho la sensazione che Ruby potrebbe non avere metodi annidati.
Mark Thomas

16
@ Mark Thomas: ho dimenticato di dire che Ruby non ha metodi annidati? :-) Scherzi a parte: al momento ho scritto questa risposta, ci sono stati già tre risposte, ognuno dei quali ha sostenuto che Ruby ha disporre di metodi annidati. Alcune di queste risposte hanno anche ricevuto voti positivi nonostante fossero palesemente sbagliate. Uno è stato persino accettato dall'OP, di nuovo, nonostante fosse sbagliato. Lo snippet di codice che la risposta utilizza per dimostrare che Ruby supporta metodi annidati, in realtà dimostra il contrario, ma apparentemente né i votanti né l'OP si sono effettivamente presi la briga di controllare. Quindi, ho dato una risposta giusta per ogni risposta sbagliata. :-)
Jörg W Mittag

10
Quando ti rendi conto che queste sono solo istruzioni per il kernel che modificano le tabelle e che metodi, classi e moduli sono solo voci nelle tabelle e non realmente reali, diventi come Neo quando vede come appare Matrix. Allora potresti davvero diventare filosofico e dire che oltre ai metodi annidati, non ci sono nemmeno metodi. Non ci sono nemmeno agenti. Sono programmi nella matrice. Anche quella succosa bistecca che stai mangiando è solo un'entrata in un tavolo.
mydoghasworms

3
Non ci sono metodi, il tuo codice è solo una simulazione in The Matrix
bbozo

13

In realtà è possibile. Puoi usare procs / lambda per questo.

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

2
Non hai torto, ma la tua risposta è formulata come una soluzione per ottenere metodi annidati. Quando in realtà stai usando solo proc che non sono metodi. È una bella risposta al di fuori della pretesa di risolvere "metodi annidati"
Brandon Buck

5

No, no, Ruby ha metodi annidati. Verificare questo:

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 non è un metodo, è una funzione / lambda / proc. Non esiste un'istanza associata di nessuna classe, quindi non è un metodo.
Sami Samhuri

2

Puoi fare qualcosa di simile

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

Questo è utile quando stai facendo cose come scrivere DSL che richiedono la condivisione dell'ambito tra i metodi. Ma per il resto, è molto meglio fare qualsiasi altra cosa, perché come dicevano le altre risposte, innerviene ridefinito ogni volta che outerviene invocato. Se vuoi questo comportamento, ea volte potresti farlo, questo è un buon modo per ottenerlo.


2

Il modo in cui Ruby è falsificarlo con hack confusi che indurranno alcuni utenti a chiedersi "Come diavolo funziona?", Mentre i meno curiosi memorizzeranno semplicemente la sintassi necessaria per usare la cosa. Se hai mai usato Rake o Rails, hai visto questo genere di cose.

Ecco un tale trucco:

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

Ciò che fa è definire un metodo di primo livello che crea una classe e la passa a un blocco. La classe usa method_missingper fingere di avere un metodo con il nome che hai scelto. "Implementa" il metodo chiamando il lambda che devi fornire. Denominando l'oggetto con un nome di una lettera, puoi ridurre al minimo la quantità di caratteri extra che richiede (che è la stessa cosa che Rails fa nel suo schema.rb). mletprende il nome dalla forma Common Lisp flet, tranne dove fsta per "funzione", msta per "metodo".

Lo usi in questo modo:

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

È possibile creare un aggeggio simile che consenta di definire più funzioni interne senza annidamenti aggiuntivi, ma che richiede un hack ancora più brutto del tipo che potresti trovare nell'implementazione di Rake o Rspec. Capire come funziona Rspec ti let!farebbe fare molta strada per essere in grado di creare un orribile abominio.


-3

MrGreen

Ruby ha metodi annidati, solo che non fanno quello che ti aspetteresti che facciano

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
Questo non è un metodo annidato ... vedere la risposta di Jörg W Mittag per una chiara comprensione.
Hardik
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.