In Ruby 1.8, ci sono sottili differenze tra proc / lambda da un lato e Proc.new
dall'altro.
- Quali sono queste differenze?
- Puoi fornire delle linee guida su come decidere quale scegliere?
- In Ruby 1.9, proc e lambda sono diversi. Qual è l'accordo?
In Ruby 1.8, ci sono sottili differenze tra proc / lambda da un lato e Proc.new
dall'altro.
Risposte:
Un'altra differenza importante ma sottile tra proc creati con lambda
e proc creati con Proc.new
è come gestiscono l' return
affermazione:
lambda
proc creato, la return
dichiarazione ritorna solo dal proc stessoProc.new
proc creato, l' return
affermazione è un po 'più sorprendente: restituisce il controllo non solo dal proc, ma anche dal metodo che racchiude il proc!Ecco lambda
-creato proc return
in azione. Si comporta in un modo che probabilmente ti aspetti:
def whowouldwin
mylambda = lambda {return "Freddy"}
mylambda.call
# mylambda gets called and returns "Freddy", and execution
# continues on the next line
return "Jason"
end
whowouldwin
#=> "Jason"
Ora ecco un Proc.new
proc creato sta return
facendo la stessa cosa. Stai per vedere uno di quei casi in cui Ruby infrange il tanto decantato Principio di Least Surprise:
def whowouldwin2
myproc = Proc.new {return "Freddy"}
myproc.call
# myproc gets called and returns "Freddy",
# but also returns control from whowhouldwin2!
# The line below *never* gets executed.
return "Jason"
end
whowouldwin2
#=> "Freddy"
Grazie a questo comportamento sorprendente (oltre che a una minore digitazione), tendo a preferire l'uso di lambda
over Proc.new
quando realizzo procs.
proc
metodo. È solo una scorciatoia per Proc.new
?
proc
è equivalente aProc.new
proc
comporta come lambda
e non come Proc.new
per quanto riguarda le dichiarazioni di ritorno. Ciò significa che il documento ruby non è preciso.
proc
si comporta come lambda
in 1.8, ma si comporta come Proc.new
in 1.9. Vedi la risposta di Peter Wagenet.
lambda
è un metodo anonimo. Poiché è un metodo, restituisce un valore e il metodo che lo ha chiamato può fare con esso tutto ciò che vuole, incluso ignorarlo e restituire un valore diverso. A Proc
è come incollare in uno snippet di codice. Non si comporta come un metodo. Quindi quando si verifica un ritorno all'interno di Proc
, questo è solo una parte del codice del metodo che lo ha chiamato.
Per fornire ulteriori chiarimenti:
Joey afferma che il comportamento di ritorno di Proc.new
è sorprendente. Tuttavia, se si considera che Proc.new si comporta come un blocco, ciò non sorprende in quanto è esattamente come si comportano i blocchi. i lambas d'altra parte si comportano più come i metodi.
Questo in realtà spiega perché Procs è flessibile quando si tratta di arity (numero di argomenti) mentre lambda non lo sono. I blocchi non richiedono che vengano forniti tutti i loro argomenti, ma i metodi lo fanno (a meno che non venga fornito un valore predefinito). Pur fornendo l'argomento lambda il default non è un'opzione in Ruby 1.8, ora è supportato in Ruby 1.9 con la sintassi lambda alternativa (come notato da webmat):
concat = ->(a, b=2){ "#{a}#{b}" }
concat.call(4,5) # => "45"
concat.call(1) # => "12"
E Michiel de Mare (il PO) non è corretto sul fatto che Procs e lambda si comportino allo stesso modo con arità in Ruby 1.9. Ho verificato che mantengono ancora il comportamento da 1.8 come sopra specificato.
break
le affermazioni in realtà non hanno molto senso in Procs o lambdas. In Procs, la pausa ti restituirebbe da Proc.new che è già stato completato. E non ha alcun senso rompere da un lambda poiché è essenzialmente un metodo e non ti spezzeresti mai dal livello superiore di un metodo.
next
, redo
e si raise
comportano allo stesso modo sia in Procs che in lambdas. Considerando che retry
non è consentito in nessuno dei due e solleverà un'eccezione.
E infine, il proc
metodo non dovrebbe mai essere usato in quanto incoerente e ha un comportamento inaspettato. In Ruby 1.8 in realtà restituisce un lambda! In Ruby 1.9 questo è stato corretto e restituisce un Proc. Se vuoi creare un Proc, segui Proc.new
.
Per ulteriori informazioni, consiglio vivamente il Ruby Programming Language di O'Reilly, che è la mia fonte per la maggior parte di queste informazioni.
break
da Procs si alza LocalJumpError
, mentre break
da lambdas si comporta proprio come return
( cioè , return nil
).
Ho trovato questa pagina che mostra quali sono le differenze tra Proc.new
e lambda
. Secondo la pagina, l'unica differenza è che un lambda è rigoroso sul numero di argomenti che accetta, mentre Proc.new
converte gli argomenti mancanti nil
. Ecco una sessione IRB di esempio che illustra la differenza:
irb (principale): 001: 0> l = lambda {| x, y | x + y} => # <Proc: 0x00007fc605ec0748 @ (irb): 1> irb (principale): 002: 0> p = Proc.new {| x, y | x + y} => # <Proc: 0x00007fc605ea8698 @ (irb): 2> irb (principale): 003: 0> l.call "ciao", "mondo" => "mondo" irb (principale): 004: 0> p. call "ciao", "mondo" => "mondo" irb (principale): 005: 0> l.call "ciao" ArgumentError: errato numero di argomenti (1 per 2) da (irb): 1 da (irb): 5: in `call ' da (irb): 5 da: 0 irb (principale): 006: 0> p.call "hello" TypeError: impossibile convertire zero in String da (irb): 2: in `+ ' da (irb): 2 da (irb): 6: in `call ' da (irb): 6 da: 0
La pagina consiglia inoltre di utilizzare lambda a meno che non si desideri specificamente il comportamento tollerante agli errori. Sono d'accordo con questo sentimento. L'uso di una lambda sembra un po 'più conciso e, con una differenza così insignificante, sembra la scelta migliore nella situazione media.
Per quanto riguarda Ruby 1.9, scusate, non ho ancora esaminato la versione 1.9, ma non immagino che cambierebbe così tanto (non credetemi, sembra che abbiate sentito parlare di alcuni cambiamenti, quindi Probabilmente ho sbagliato lì).
Proc è più vecchio, ma la semantica del ritorno è molto controintuitiva per me (almeno quando stavo imparando la lingua) perché:
Lambda è funzionalmente più sicuro e più facile da ragionare: lo uso sempre al posto di proc.
Non posso dire molto delle sottili differenze. Tuttavia, posso sottolineare che Ruby 1.9 ora consente parametri opzionali per lambda e blocchi.
Ecco la nuova sintassi per la stabby lambdas sotto 1.9:
stabby = ->(msg='inside the stabby lambda') { puts msg }
Ruby 1.8 non aveva quella sintassi. Né il modo convenzionale di dichiarare blocchi / lambda supportava argomenti facoltativi:
# under 1.8
l = lambda { |msg = 'inside the stabby lambda'| puts msg }
SyntaxError: compile error
(irb):1: syntax error, unexpected '=', expecting tCOLON2 or '[' or '.'
l = lambda { |msg = 'inside the stabby lambda'| puts msg }
Ruby 1.9, tuttavia, supporta argomenti opzionali anche con la vecchia sintassi:
l = lambda { |msg = 'inside the regular lambda'| puts msg }
#=> #<Proc:0x0e5dbc@(irb):1 (lambda)>
l.call
#=> inside the regular lambda
l.call('jeez')
#=> jeez
Se vuoi creare Ruby1.9 per Leopard o Linux, dai un'occhiata a questo articolo (autopromozione spudorata).
Risposta breve: Ciò che conta è ciò che return
fa: lambda ritorna da se stessa e proc ritorna da sé E dalla funzione che l'ha chiamata.
Ciò che è meno chiaro è il motivo per cui si desidera utilizzare ciascuno. lambda è ciò che ci aspettiamo che le cose dovrebbero fare in termini di programmazione funzionale. È fondamentalmente un metodo anonimo con l'ambito corrente automaticamente associato. Dei due, lambda è quello che dovresti probabilmente usare.
Proc, d'altra parte, è davvero utile per implementare il linguaggio stesso. Ad esempio, è possibile implementare istruzioni "if" o "for" con loro. Qualsiasi ritorno trovato nel proc tornerà dal metodo che lo ha chiamato, non solo dall'istruzione "if". Ecco come funzionano le lingue, come funzionano le dichiarazioni "if", quindi la mia ipotesi è che Ruby lo usi sotto le copertine e lo hanno appena esposto perché sembrava potente.
Ne avresti davvero bisogno solo se stai creando nuovi costrutti di linguaggio come loop, costrutti if-else, ecc.
Non ho notato alcun commento sul terzo metodo nel queston, "proc" che è deprecato, ma gestito diversamente in 1.8 e 1.9.
Ecco un esempio abbastanza dettagliato che rende facile vedere le differenze tra le tre chiamate simili:
def meth1
puts "method start"
pr = lambda { return }
pr.call
puts "method end"
end
def meth2
puts "method start"
pr = Proc.new { return }
pr.call
puts "method end"
end
def meth3
puts "method start"
pr = proc { return }
pr.call
puts "method end"
end
puts "Using lambda"
meth1
puts "--------"
puts "using Proc.new"
meth2
puts "--------"
puts "using proc"
meth3
proc
restituito un lambda in 1.8; ora è stato corretto per restituire un proc in 1.9 - tuttavia si tratta di un cambiamento decisivo; quindi non è più raccomandato l'uso
Chiusure in Ruby è una buona panoramica di come blocchi, lambda e proc funzionano in Ruby, con Ruby.
lambda funziona come previsto, come in altre lingue.
Il filo Proc.new
è sorprendente e confuso.
L' return
istruzione in proc creata da Proc.new
restituirà il controllo non solo da se stessa, ma anche dal metodo che la racchiude .
def some_method
myproc = Proc.new {return "End."}
myproc.call
# Any code below will not get executed!
# ...
end
Puoi sostenere che Proc.new
inserisce il codice nel metodo che racchiude, proprio come il blocco. Ma Proc.new
crea un oggetto, mentre i blocchi fanno parte di un oggetto.
E c'è un'altra differenza tra lambda e Proc.new
, che è la loro gestione di argomenti (sbagliati). lambda se ne lamenta, mentre Proc.new
ignora argomenti aggiuntivi o considera l'assenza di argomenti nulla.
irb(main):021:0> l = -> (x) { x.to_s }
=> #<Proc:0x8b63750@(irb):21 (lambda)>
irb(main):022:0> p = Proc.new { |x| x.to_s}
=> #<Proc:0x8b59494@(irb):22>
irb(main):025:0> l.call
ArgumentError: wrong number of arguments (0 for 1)
from (irb):21:in `block in irb_binding'
from (irb):25:in `call'
from (irb):25
from /usr/bin/irb:11:in `<main>'
irb(main):026:0> p.call
=> ""
irb(main):049:0> l.call 1, 2
ArgumentError: wrong number of arguments (2 for 1)
from (irb):47:in `block in irb_binding'
from (irb):49:in `call'
from (irb):49
from /usr/bin/irb:11:in `<main>'
irb(main):050:0> p.call 1, 2
=> "1"
A proposito, proc
in Ruby 1.8 crea un lambda, mentre in Ruby 1.9+ si comporta come Proc.new
, il che è davvero confuso.
Per approfondire la risposta di Accordion Guy:
Si noti che Proc.new
crea un proc out passando un blocco. Credo che lambda {...}
sia analizzato come una sorta di letterale, piuttosto che una chiamata di metodo che passa un blocco. return
dall'interno di un blocco collegato a una chiamata di metodo tornerà dal metodo, non dal blocco, e il Proc.new
caso ne è un esempio in corso di esecuzione.
(Questo è 1.8. Non so come questo si traduca in 1.9.)
Sono un po 'in ritardo su questo, ma c'è una cosa grande ma poco nota su Proc.new
non menzionata affatto nei commenti. Come da documentazione :
Proc::new
può essere chiamato senza un blocco solo all'interno di un metodo con un blocco collegato, nel qual caso quel blocco viene convertitoProc
nell'oggetto.
Detto questo, Proc.new
consente di concatenare i metodi di rendimento:
def m1
yield 'Finally!' if block_given?
end
def m2
m1 &Proc.new
end
m2 { |e| puts e }
#⇒ Finally!
&block
argomento nella def
, ma senza farlo nella lista def arg.
Vale la pena sottolineare che return
in un proc ritorna dal metodo lessicicamente racchiuso, cioè il metodo in cui è stato creato il proc , non il metodo che ha chiamato proc. Questa è una conseguenza della proprietà di chiusura di procs. Quindi il seguente codice non genera nulla:
def foo
proc = Proc.new{return}
foobar(proc)
puts 'foo'
end
def foobar(proc)
proc.call
puts 'foobar'
end
foo
Sebbene il proc foobar
venga eseguito , è stato creato foo
e quindi le return
uscite foo
, non solo foobar
. Come ha scritto Charles Caldwell sopra, ha un aspetto GOTO. Secondo me, return
va bene in un blocco che viene eseguito nel suo contesto lessicale, ma è molto meno intuitivo se usato in un proc che viene eseguito in un contesto diverso.
La differenza di comportamento con return
IMHO è la differenza più importante tra i 2. Preferisco anche lambda perché è meno digitante di Proc.new :-)
proc {}
. Non sono sicuro di quando sarà entrato in vigore, ma è (leggermente) più facile che dover digitare Proc.new.