Domanda

In Ruby 1.8 ci sono sottili differenze tra proc/lambda da un lato e Proc.new dall'altra.

  • Quali sono queste differenze?
  • Potete dare delle linee guida su come decidere quale scegliere?
  • In Ruby 1.9, proc e lambda sono diversi.Qual è l'accordo?
È stato utile?

Soluzione

Un'altra differenza importante ma sottile tra i proc creati con lambda e processi creati con Proc.new è così che gestiscono il return dichiarazione:

  • In un lambda-creato proc, il return l'istruzione restituisce solo dal proc stesso
  • In un Proc.new-creato proc, il return l'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 nel 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-creato proc return facendo la stessa cosa.Stai per vedere uno di quei casi in cui Ruby infrange il tanto decantato Principio della Minima Sorpresa:

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 alla minore digitazione), tendo a favorire l'utilizzo lambda Sopra Proc.new quando si effettuano i proc.

Altri suggerimenti

Per fornire ulteriori chiarimenti:

Joey dice che il comportamento di ritorno di Proc.new è sorprendente.Tuttavia, se si considera che Proc.new si comporta come un blocco, ciò non sorprende poiché è esattamente il modo in cui si comportano i blocchi.i lambas d'altra parte si comportano più come metodi.

Questo in realtà spiega perché i Proc sono flessibili quando si tratta di arità (numero di argomenti) mentre i lambda non lo sono.I blocchi non richiedono che vengano forniti tutti gli argomenti ma i metodi sì (a meno che non venga fornito un valore predefinito).Sebbene fornire l'argomento lambda predefinito non sia 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 (l'OP) non è corretto riguardo al fatto che Procs e lambda si comportino allo stesso modo con arity in Ruby 1.9.Ho verificato che mantengono ancora il comportamento da 1.8 come sopra specificato.

break le dichiarazioni in realtà non hanno molto senso né in Procs né in lambda.In Procs, l'interruzione ti riporterebbe da Proc.new che è già stato completato.E non ha alcun senso uscire da un lambda poiché è essenzialmente un metodo e non usciresti mai dal livello più alto di un metodo.

next, redo, E raise si comportano allo stesso modo sia in Proc che in lambda.Mentre retry non è consentito in nessuno dei due e solleverà un'eccezione.

E infine, il proc Il metodo non dovrebbe mai essere utilizzato poiché è incoerente e presenta un comportamento imprevisto.In Ruby 1.8 restituisce effettivamente un lambda!In Ruby 1.9 questo è stato risolto e restituisce un Proc.Se vuoi creare un Proc, mantieni Proc.new.

Per ulteriori informazioni, consiglio vivamente O'Reilly's Il linguaggio di programmazione Ruby che è la mia fonte per la maggior parte di queste informazioni.

ho trovato questa pagina che mostra qual è la differenza tra Proc.new E lambda Sono.Secondo la pagina, l'unica differenza è che una lambda è rigorosa riguardo al numero di argomenti che accetta, mentre Proc.new converte gli argomenti mancanti in nil.Ecco un esempio di sessione IRB che illustra la differenza:

irb(main):001:0> l = lambda { |x, y| x + y }
=> #<Proc:0x00007fc605ec0748@(irb):1>
irb(main):002:0> p = Proc.new { |x, y| x + y }
=> #<Proc:0x00007fc605ea8698@(irb):2>
irb(main):003:0> l.call "hello", "world"
=> "helloworld"
irb(main):004:0> p.call "hello", "world"
=> "helloworld"
irb(main):005:0> l.call "hello"
ArgumentError: wrong number of arguments (1 for 2)
    from (irb):1
    from (irb):5:in `call'
    from (irb):5
    from :0
irb(main):006:0> p.call "hello"
TypeError: can't convert nil into String
    from (irb):2:in `+'
    from (irb):2
    from (irb):6:in `call'
    from (irb):6
    from :0

La pagina consiglia inoltre di utilizzare lambda a meno che non si desideri specificamente il comportamento di tolleranza 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, mi dispiace, non ho ancora esaminato la 1.9, ma non immagino che cambierebbero molto (non credermi sulla parola però, sembra che tu abbia sentito parlare di alcuni cambiamenti, quindi probabilmente mi sbaglio lì).

Proc è più vecchio, ma la semantica di return è altamente controintuitiva per me (almeno quando stavo imparando la lingua) perché:

  1. Se stai usando proc, molto probabilmente stai usando una sorta di paradigma funzionale.
  2. Proc può tornare fuori dall'ambito di inclusione (vedere le risposte precedenti), che è fondamentalmente un goto e di natura altamente non funzionale.

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

Non posso dire molto sulle sottili differenze.Tuttavia, posso sottolineare che Ruby 1.9 ora consente parametri opzionali per lambda e blocchi.

Ecco la nuova sintassi per le lambda stabby 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 supporta argomenti opzionali:

# 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 Questo articolo (autopromozione spudorata).

Risposta breve:Ciò che conta è cosa return fa:lambda ritorna da se stesso e proc ritorna da se stesso E dalla funzione che lo ha chiamato.

Ciò che è meno chiaro è il motivo per cui si desidera utilizzarli.lambda è ciò che ci aspettiamo che le cose facciano in senso di programmazione funzionale.Si tratta fondamentalmente di un metodo anonimo con l'ambito corrente associato automaticamente.Dei due, lambda è quello che probabilmente dovresti usare.

Proc, d'altro canto, è davvero utile per implementare il linguaggio stesso.Ad esempio, puoi implementare le istruzioni "if" o i cicli "for".Qualsiasi risultato trovato nel proc verrà restituito dal metodo che lo ha chiamato, non solo dall'istruzione "if".Ecco come funzionano le lingue, come funzionano le istruzioni "se", quindi suppongo che Ruby lo usi sotto le coperte e lo abbiano semplicemente esposto perché sembrava potente.

Ne avresti davvero bisogno solo se stai creando nuovi costrutti linguistici come loop, costrutti if-else, ecc.

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

Non ho notato alcun commento sul terzo metodo in queston, "proc", che è deprecato, ma gestito diversamente nelle versioni 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

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

lambda funziona come previsto, come in altre lingue.

Il cablato Proc.new è sorprendente e confuso.

IL return dichiarazione in proc creata da Proc.new non solo restituirà il controllo solo da se stesso, ma anche dal metodo che lo racchiude.

def some_method
  myproc = Proc.new {return "End."}
  myproc.call

  # Any code below will not get executed!
  # ...
end

Puoi discuterne Proc.new inserisce il codice nel metodo di inclusione, proprio come block.Ma Proc.new crea un oggetto, mentre block are parte di un oggetto.

E c'è un'altra differenza tra lambda e Proc.new, che è la loro gestione degli argomenti (sbagliati).lambda se ne lamenta, mentre Proc.new ignora gli argomenti aggiuntivi o considera l'assenza di argomenti come 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 una lambda, mentre in Ruby 1.9+ si comporta come Proc.new, il che è davvero confuso.

Per approfondire la risposta di Accordion Guy:

Notare che Proc.new crea un proc out ricevendo un blocco.Credo che lambda {...} viene analizzato come una sorta di letterale, piuttosto che come una chiamata al metodo che passa un blocco. returning dall'interno di un blocco allegato a una chiamata al metodo restituirà dal metodo, non dal blocco, e il Proc.new Il caso è un esempio di ciò in gioco.

(Questo è 1.8.Non so come questo si traduca in 1.9.)

Sono un po' in ritardo su questo, ma c'è una cosa fantastica ma poco conosciuta Proc.new non menzionato affatto nei commenti.Come da documentazione:

Proc::new può essere chiamato senza blocco solo all'interno di un metodo con un blocco allegato, nel qual caso that il blocco viene convertito in Proc oggetto.

Detto ciò, 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!

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

Vale la pena sottolinearlo return in un proc ritorna dal metodo di inclusione lessicale, i.e. il metodo in cui è stato creato il proc, non il metodo che ha chiamato il proc.Questa è una conseguenza della proprietà di chiusura di procs.Quindi il seguente codice non restituisce nulla:

def foo
  proc = Proc.new{return}
  foobar(proc)
  puts 'foo'
end

def foobar(proc)
  proc.call
  puts 'foobar'
end

foo

Sebbene il proc venga eseguito in foobar, è stato creato nel foo e così il return esce foo, non solo foobar.Come ha scritto Charles Caldwell sopra, ha un'atmosfera GOTO.Secondo me, return va bene in un blocco eseguito nel suo contesto lessicale, ma è molto meno intuitivo se utilizzato in un proc eseguito in un contesto diverso.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top