Quando usare lambda, quando usare Proc.new?


336

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?

3
Vedi anche: il libro Ruby Programming Language di Matz e Flanagan, ha trattato in modo esauriente questo argomento. proc si comporta come una semantica a blocchi, mentre come lambda si comporta come una semantica chiamata metodo. Anche return, break, et. tutti si comportano diversamente in procs n lambdas
Gishu


hai accettato la risposta che dice solo qual è la differenza tra proc e lambda, mentre il titolo della tua domanda è quando usare quelle cose
Shri

Risposte:


378

Un'altra differenza importante ma sottile tra proc creati con lambdae proc creati con Proc.newè come gestiscono l' returnaffermazione:

  • In un lambdaproc creato, la returndichiarazione ritorna solo dal proc stesso
  • In un Proc.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.


12
Quindi c'è anche il procmetodo. È solo una scorciatoia per Proc.new?
Panzi,


4
@mattdipasquale Nei miei test, si proccomporta come lambdae non come Proc.newper quanto riguarda le dichiarazioni di ritorno. Ciò significa che il documento ruby ​​non è preciso.
Kelvin,

31
@mattdipasquale Scusa, avevo solo la metà. procsi comporta come lambdain 1.8, ma si comporta come Proc.newin 1.9. Vedi la risposta di Peter Wagenet.
Kelvin,

55
Perché questo comportamento "sorprendente"? A 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.
Arcolye,

96

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.


1
"" "Tuttavia, se si considera che Proc.new si comporta come un blocco, ciò non sorprende in quanto è esattamente come si comportano i blocchi." "" <- block fa parte di un oggetto, mentre Proc.new crea un oggetto. Sia lambda che Proc.new creano un oggetto la cui classe è Proc, perché diff?
debole

1
A partire da Ruby 2.5, breakda Procs si alza LocalJumpError, mentre breakda lambdas si comporta proprio come return( cioè , return nil).
Masa Sakano,

43

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ì).


2
anche i proc ritornano in modo diverso rispetto agli lambda.
Cam

"" "Proc.new converte gli argomenti mancanti in zero" "" Proc.new ignora anche argomenti extra (ovviamente lambda si lamenta di questo con un errore).
debole

16

Proc è più vecchio, ma la semantica del ritorno è molto controintuitiva per me (almeno quando stavo imparando la lingua) perché:

  1. Se stai usando proc, molto probabilmente stai usando un qualche tipo di paradigma funzionale.
  2. Proc può tornare al di fuori del campo di applicazione (vedi le risposte precedenti), che è fondamentalmente un goto e di natura altamente non funzionale.

Lambda è funzionalmente più sicuro e più facile da ragionare: lo uso sempre al posto di proc.


11

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).


I parametri opzionali all'interno di lambda erano molto necessari, sono contento che li abbiano aggiunti in 1.9. Presumo che anche i blocchi possano avere anche parametri opzionali (in 1.9)?
mpd,

non stai dimostrando i parametri predefiniti in blocchi, solo lambdas
iconoclasta

11

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.


1
"lambda ritorna da se stesso, e proc ritorna da se stesso E la funzione che lo ha chiamato" è chiaramente sbagliata e un malinteso molto comune. Un proc è una chiusura e ritorna dal metodo che l'ha creato. Vedi la mia risposta completa altrove sulla pagina.
ComDubh,

10

Un buon modo per vederlo è che i lambda sono eseguiti nel loro ambito (come se fosse una chiamata di metodo), mentre Procs può essere visto come eseguito in linea con il metodo di chiamata, almeno questo è un buon modo di decidere quale usare in ogni caso.


8

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

1
Matz aveva dichiarato che aveva pianificato di deprecarlo perché era confuso che proc e Proc.nuovo restituissero risultati diversi. In 1.9 si comportano allo stesso modo (proc è un alias di Proc.new). eigenclass.org/hiki/Changes+in+Ruby+1.9#l47
Dave Rapin

@banister: 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
Gishu

Penso che il piccone affermi in una nota a piè di pagina che Proc è effettivamente deprivato o qualcosa del genere. Non ho il numero esatto di pagina.
Dertoni,

7

Chiusure in Ruby è una buona panoramica di come blocchi, lambda e proc funzionano in Ruby, con Ruby.


Ho smesso di leggerlo dopo aver letto "una funzione non può accettare più blocchi - violando il principio secondo cui le chiusure possono essere passate liberamente come valori". I blocchi non sono chiusure. I proc sono e una funzione può accettare più proc.
ComDubh,

5

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.


3

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.)


3

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 convertitoProc nell'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!

Interessante, fa la stessa cosa di dichiarare un &blockargomento nella def, ma senza farlo nella lista def arg.
jrochkind,

2

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.


1

La differenza di comportamento con returnIMHO è la differenza più importante tra i 2. Preferisco anche lambda perché è meno digitante di Proc.new :-)


2
Per aggiornare: ora è possibile creare procs usando proc {}. Non sono sicuro di quando sarà entrato in vigore, ma è (leggermente) più facile che dover digitare Proc.new.
aceofbassgreg,
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.