Qual é a diferença entre um proc e um lambda em Ruby?


Respostas:


260

Uma diferença está na maneira como eles lidam com argumentos. Criando um proc usando proc {}e Proc.new {}é equivalente. No entanto, usar lambda {}fornece um processo que verifica o número de argumentos passados ​​para ele. De ri Kernel#lambda:

Equivalente a Proc.new , exceto os objetos Proc resultantes, verifica o número de parâmetros passados ​​quando chamados.

Um exemplo:

p = Proc.new {|a, b| puts a**2+b**2 } # => #<Proc:0x3c7d28@(irb):1>
p.call 1, 2 # => 5
p.call 1 # => NoMethodError: undefined method `**' for nil:NilClass
p.call 1, 2, 3 # => 5
l = lambda {|a, b| puts a**2+b**2 } # => #<Proc:0x15016c@(irb):5 (lambda)>
l.call 1, 2 # => 5
l.call 1 # => ArgumentError: wrong number of arguments (1 for 2)
l.call 1, 2, 3 # => ArgumentError: wrong number of arguments (3 for 2)

Além disso, como Ken aponta, usar returndentro de um lambda retorna o valor desse lambda, mas usar returnem proc proc retorna do bloco anexo.

lambda { return :foo }.call # => :foo
return # => LocalJumpError: unexpected return
Proc.new { return :foo }.call # => LocalJumpError: unexpected return

Portanto, para os usos mais rápidos, eles são os mesmos, mas se você desejar a verificação automática de argumentos estritos (que às vezes também pode ajudar na depuração), ou se precisar usar a returninstrução para retornar o valor do proc, use lambda.


8
Seria correto dizer que lambdas são muito parecidos com métodos (argumentos de verificação e retorno retornará deles) enquanto procs são muito parecidos com blocos (argumentos não são verificados e um retorno retornará do método ou lambda)?
pedz

Estive em Deus sabe quantos sites e artigos até agora e ninguém parece falar sobre a utilidade do Procs vs. métodos vs. lambdas. Toda explicação fornece apenas um detalhe impressionante de como os valores de retorno etc. são diferentes, mas nenhum sobre o motivo. Por enquanto, devo concluir que essa é uma bagunça no design do Ruby.
ankush981 6/04

76

A diferença real entre procs e lambdas tem tudo a ver com palavras-chave de fluxo de controle. Estou a falar de return, raise, break, redo, retryetc. - essas palavras de controle. Digamos que você tenha uma declaração de retorno em um proc. Quando você chama seu proc, ele não apenas o despeja, mas também retorna do método anexo, por exemplo:

def my_method
  puts "before proc"
  my_proc = Proc.new do
    puts "inside proc"
    return
  end
  my_proc.call
  puts "after proc"
end

my_method

shoaib@shoaib-ubuntu-vm:~/tmp$ ruby a.rb
before proc
inside proc

A final putsdo método nunca foi executada, pois quando chamamos nosso proc, a parte returninterna dele nos expulsou do método. Se, no entanto, convertermos nosso proc em um lambda, obteremos o seguinte:

def my_method
  puts "before proc"
  my_proc = lambda do
    puts "inside proc"
    return
  end
  my_proc.call
  puts "after proc"
end

my_method
shoaib@shoaib-ubuntu-vm:~/tmp$ ruby a.rb
before proc
inside proc
after proc

O retorno dentro do lambda apenas nos despeja do próprio lambda e o método envolvente continua em execução. A maneira como as palavras-chave do fluxo de controle são tratadas nos procs e lambdas é a principal diferença entre elas


7

Existem apenas duas diferenças principais.

  • Primeiro, a lambdaverifica o número de argumentos passados, enquanto a procnão. Isso significa que a lambdagerará um erro se você passar o número errado de argumentos, enquanto a procignorará argumentos inesperados e os atribuirá nila qualquer um que estiver faltando.
  • Segundo, quando lambdaretorna, ele passa o controle de volta ao método de chamada; quando procretorna, o faz imediatamente, sem voltar ao método de chamada.

Para ver como isso funciona, dê uma olhada no código abaixo. Nosso primeiro método chama a proc; a segunda chama a lambda.

def batman_ironman_proc
  victor = Proc.new { return "Batman will win!" }
  victor.call
  "Iron Man will win!"
end

puts batman_ironman_proc # prints "Batman will win!"

def batman_ironman_lambda
  victor = lambda { return "Batman will win!" }
  victor.call
  "Iron Man will win!"
end

puts batman_ironman_lambda # prints "Iron Man will win!"

Veja como procdiz "Batman vai vencer!", Isso ocorre porque ele retorna imediatamente, sem voltar ao método batman_ironman_proc.

lambdaNo entanto, nosso método retorna ao método após ser chamado, portanto, o método retorna o último código que avalia: "O Homem de Ferro vencerá!"


5

# Proc Exemplos

p = Proc.new { |x| puts x*2 }
[1,2,3].each(&p)              # The '&' tells ruby to turn the proc into a block 

proc = Proc.new { puts "Hello World" }
proc.call

# Lambda Examples

lam = lambda { |x| puts x*2 }
[1,2,3].each(&lam)

lam = lambda { puts "Hello World" }
lam.call           

Diferenças entre Procs e Lambdas

Antes de abordar as diferenças entre procs e lambdas, é importante mencionar que ambos são objetos Proc.

proc = Proc.new { puts "Hello world" }
lam = lambda { puts "Hello World" }

proc.class # returns 'Proc'
lam.class  # returns 'Proc'

No entanto, lambdas são um 'sabor' diferente de procs. Essa pequena diferença é mostrada ao retornar os objetos.

proc   # returns '#<Proc:0x007f96b1032d30@(irb):75>'
lam    # returns '<Proc:0x007f96b1b41938@(irb):76 (lambda)>'

1. Lambdas verifica o número de argumentos, enquanto procs não

lam = lambda { |x| puts x }    # creates a lambda that takes 1 argument
lam.call(2)                    # prints out 2
lam.call                       # ArgumentError: wrong number of arguments (0 for 1)
lam.call(1,2,3)                # ArgumentError: wrong number of arguments (3 for 1)

Por outro lado, os procs não se importam se recebem o número errado de argumentos.

proc = Proc.new { |x| puts x } # creates a proc that takes 1 argument
proc.call(2)                   # prints out 2
proc.call                      # returns nil
proc.call(1,2,3)               # prints out 1 and forgets about the extra arguments

2. Lambdas e procs tratam a palavra-chave 'return' de maneira diferente

'return' dentro de um lambda aciona o código fora do código lambda

def lambda_test
  lam = lambda { return }
  lam.call
  puts "Hello world"
end

lambda_test                 # calling lambda_test prints 'Hello World'

'return' dentro de um proc aciona o código fora do método em que o proc está sendo executado

def proc_test
  proc = Proc.new { return }
  proc.call
  puts "Hello world"
end

proc_test                 # calling proc_test prints nothing

E para responder sua outra pergunta, qual usar e quando? Vou seguir @jtbandes como ele mencionou

Portanto, para os usos mais rápidos, eles são os mesmos, mas se você desejar a verificação automática de argumentos estritos (que às vezes também pode ajudar na depuração), ou se precisar usar a instrução de retorno para retornar o valor do proc, use lambda.

Originalmente publicado aqui


1

De um modo geral, as lambdas são mais intuitivas que os procs porque são mais semelhantes aos métodos. Eles são bastante rígidos quanto à aridade e simplesmente saem quando você chama de retorno. Por esse motivo, muitos rubiistas usam lambdas como primeira opção, a menos que precisem dos recursos específicos de procs.

Procs: Objetos de classe Proc. Como blocos, eles são avaliados no escopo em que são definidos. Lambdas: também objetos de classe, Procmas sutilmente diferentes dos procs regulares. São fechamentos como blocos e procs e, como tal, são avaliados no escopo em que são definidos.

Criando Proc

a = Proc.new { |x| x 2 }

Criando lambda

b = lambda { |x| x 2 }


a = proc { |x| x 2 }é o mesmo quea = Proc.new { |x| x 2 }
lacostenycoder

1

Aqui está outra maneira de entender isso.

Um bloco é um pedaço de código anexado à chamada para uma chamada de um método em um objeto. No exemplo abaixo, self é uma instância de uma classe anônima herdada de ActionView :: Base na estrutura do Rails (que inclui muitos módulos auxiliares). cartão é um método que chamamos de auto. Passamos um argumento para o método e sempre anexamos o bloco ao final da invocação do método:

self.card :contacts do |c|
  // a chunk of valid ruby code    
end

Ok, estamos passando um pedaço de código para um método. Mas como usamos esse bloco? Uma opção é converter o pedaço de código em um objeto. Ruby oferece três maneiras de converter um pedaço de código em um objeto

# lambda
> l = lambda { |a| a + 1 }
> l.call(1)
=> 2 

# Proc.new
> l2= Proc.new { |a| a + 1 }
> l2.call(1)
=> 2 

# & as the last method argument with a local variable name
def add(&block)
end

No método acima, o & converte o bloco passado para o método em um objeto e armazena esse objeto no bloco variável local. De fato, podemos mostrar que ele tem o mesmo comportamento que o lambda e o Proc.new:

def add(&block)
  block
end

l3 = add { |a| a + 1 }
l3.call(1)
=> 2

Isso é importante. Quando você passa um bloco para um método e o converte usando &, o objeto que ele cria usa Proc.new para fazer a conversão.

Observe que eu evitei o uso de "proc" como uma opção. Isso porque Ruby 1.8, é o mesmo que lambda e no Ruby 1.9, é o mesmo que Proc.new e em todas as versões do Ruby deve ser evitado.

Então você pergunta qual é a diferença entre lambda e Proc.new?

Primeiro, em termos de passagem de parâmetro, o lambda se comporta como uma chamada de método. Irá gerar uma exceção se você passar o número errado de argumentos. Por outro lado, o Proc.new se comporta como atribuição paralela. Todos os argumentos não utilizados são convertidos em zero:

> l = lambda {|a,b| puts "#{a} + #{b}" }
 => #<Proc:0x007fbffcb47e40@(irb):19 (lambda)> 
> l.call(1)
ArgumentError: wrong number of arguments (1 for 2)

> l2 = Proc.new {|a,b| puts "#{a} + #{b}" }
=> #<Proc:0x007fbffcb261a0@(irb):21> 
> l2.call(1)
1 + 

Segundo, lambda e Proc.new tratam a palavra-chave return de maneira diferente. Quando você faz um retorno dentro do Proc.new, ele realmente retorna do método anexo, ou seja, do contexto circundante. Quando você retorna de um bloco lambda, ele apenas retorna do bloco, não do método envolvente. Basicamente, ele sai da chamada para o bloco e continua a execução com o restante do método anexo.

> def add(a,b)
  l = Proc.new { return a + b}
  l.call
  puts "now exiting method"
end
> add(1,1)
=> 2  # NOTICE it never prints the message "now exiting method"

> def add(a,b)
  l = lambda { return a + b }
  l.call
  puts "now exiting method"
end
> add(1,1)
=> now exiting method  # NOTICE this time it prints the message "now exiting method"

Então, por que essa diferença comportamental? O motivo é que, com o Proc.new, podemos usar iteradores dentro do contexto de métodos anexos e tirar conclusões lógicas. Veja este exemplo:

> def print(max)
  [1,2,3,4,5].each do |val|
    puts val
    return if val > max
  end
end
> print(3)
1
2
3
4

Esperamos que, quando invocarmos o retorno dentro do iterador, ele retornará do método envolvente. Lembre-se de que os blocos passados ​​para os iteradores são convertidos em objetos usando Proc.new e é por isso que quando usamos return, ele sai do método envolvente.

Você pode pensar em lambdas como métodos anônimos, eles isolam blocos individuais de código em um objeto que pode ser tratado como um método. Por fim, pense em um lambda se comportando como um método anômalo e Proc.novo se comportando como código embutido.


0

Um post útil sobre guias de rubi: blocos, procs e lambdas

Os procs retornam do método atual, enquanto os lambdas retornam do próprio lambda.

Os procs não se importam com o número correto de argumentos, enquanto os lambdas geram uma exceção.


-3

as diferenças entre proc e lambda é que proc é apenas uma cópia do código com argumentos substituídos por sua vez, enquanto lambda é uma função como em outros idiomas. (comportamento de retorno, verificações de argumentos)

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.