Ho un metodo all'interno di un metodo. Il metodo interno dipende da un ciclo variabile in esecuzione. È una cattiva idea?
Risposte:
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 meth1
verrà eseguito. Il corpo definisce semplicemente un metodo denominato meth2
, motivo per cui dopo aver eseguito meth1
una volta, puoi chiamare meth2
.
Ma dove si meth2
definisce? 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).
In realtà è possibile. Puoi usare procs / lambda per questo.
def test(value)
inner = ->() {
value * value
}
inner.call()
end
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"
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, inner
viene ridefinito ogni volta che outer
viene invocato. Se vuoi questo comportamento, ea volte potresti farlo, questo è un buon modo per ottenerlo.
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_missing
per 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
). mlet
prende il nome dalla forma Common Lisp flet
, tranne dove f
sta per "funzione", m
sta 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.
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"