In Ruby 1.8, ci sono sottili differenze tra proc / lambda da un lato e Proc.newdall'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.newdall'altro.
Risposte:
Un'altra differenza importante ma sottile tra proc creati con lambdae proc creati con Proc.newè come gestiscono l' returnaffermazione:
lambdaproc creato, la returndichiarazione ritorna solo dal proc stessoProc.newproc creato, l' returnaffermazione è un po 'più sorprendente: restituisce il controllo non solo dal proc, ma anche dal metodo che racchiude il proc!Ecco lambda-creato proc returnin 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.newproc creato sta returnfacendo 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 lambdaover Proc.newquando realizzo procs.
procmetodo. È solo una scorciatoia per Proc.new?
procè equivalente aProc.new
proccomporta come lambdae non come Proc.newper quanto riguarda le dichiarazioni di ritorno. Ciò significa che il documento ruby non è preciso.
procsi comporta come lambdain 1.8, ma si comporta come Proc.newin 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.
breakle 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, redoe si raisecomportano allo stesso modo sia in Procs che in lambdas. Considerando che retrynon è consentito in nessuno dei due e solleverà un'eccezione.
E infine, il procmetodo 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.
breakda Procs si alza LocalJumpError, mentre breakda lambdas si comporta proprio come return( cioè , return nil).
Ho trovato questa pagina che mostra quali sono le differenze tra Proc.newe lambda. Secondo la pagina, l'unica differenza è che un lambda è rigoroso sul numero di argomenti che accetta, mentre Proc.newconverte 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 returnfa: 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
procrestituito 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' returnistruzione in proc creata da Proc.newrestituirà 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.newinserisce il codice nel metodo che racchiude, proprio come il blocco. Ma Proc.newcrea 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.newignora 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, procin 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.newcrea 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. returndall'interno di un blocco collegato a una chiamata di metodo tornerà dal metodo, non dal blocco, e il Proc.newcaso 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.newnon menzionata affatto nei commenti. Come da documentazione :
Proc::newpuò essere chiamato senza un blocco solo all'interno di un metodo con un blocco collegato, nel qual caso quel blocco viene convertitoProcnell'oggetto.
Detto questo, Proc.newconsente 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!
&blockargomento nella def, ma senza farlo nella lista def arg.
Vale la pena sottolineare che returnin 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 foobarvenga eseguito , è stato creato fooe quindi le returnuscite foo, non solo foobar. Come ha scritto Charles Caldwell sopra, ha un aspetto GOTO. Secondo me, returnva 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 returnIMHO è 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.