Buscar..


Sintaxis

  • Proc.nuevo ( bloque )
  • lambda {| args | código}
  • -> (arg1, arg2) {código}
  • object.to_proc
  • {| single_arg | código}
  • do | arg, (clave, valor) | código final

Observaciones

Tenga cuidado con la prioridad del operador cuando tenga una línea con múltiples métodos encadenados, como:

str = "abcdefg"
puts str.gsub(/./) do |match|
  rand(2).zero? ? match.upcase : match.downcase
end

En lugar de imprimir algo como abCDeFg , como es de esperar, imprime algo como #<Enumerator:0x00000000af42b28> - esto es porque do ... end tiene menor precedencia que los métodos, lo que significa que gsub solo ve el argumento /./ , y no el argumento del bloque. Devuelve un enumerador. El bloque termina pasado a las puts , lo que lo ignora y solo muestra el resultado de gsub(/./) .

Para solucionar este problema, envuelva la llamada gsub entre paréntesis o use { ... } lugar.

Proc

def call_the_block(&calling); calling.call; end

its_a = proc do |*args|
  puts "It's a..." unless args.empty?
  "beautiful day"
end

puts its_a       #=> "beautiful day"
puts its_a.call  #=> "beautiful day"
puts its_a[1, 2] #=> "It's a..." "beautiful day"

Hemos copiado el método call_the_block del último ejemplo. Aquí, puede ver que un proceso se realiza llamando al método proc con un bloque. También puede ver que los bloques, como los métodos, tienen retornos implícitos, lo que significa que procs (y lambdas) también lo tienen. En la definición de its_a , puede ver que los bloques pueden tomar argumentos splat así como los normales; también son capaces de tomar argumentos predeterminados, pero no se me ocurre una forma de trabajar en eso. Por último, puede ver que es posible usar varias sintaxis para llamar a un método, ya sea el método de call o el [] operador.

Lambdas

# lambda using the arrow syntax
hello_world = -> { 'Hello World!' }
hello_world[]
# 'Hello World!'

# lambda using the arrow syntax accepting 1 argument
hello_world = ->(name) { "Hello #{name}!" }
hello_world['Sven']
# "Hello Sven!"

the_thing = lambda do |magic, ohai, dere|
  puts "magic! #{magic}"
  puts "ohai #{dere}"
  puts "#{ohai} means hello"
end

the_thing.call(1, 2, 3)
# magic! 1
# ohai 3
# 2 means hello

the_thing.call(1, 2)
# ArgumentError: wrong number of arguments (2 for 3)

the_thing[1, 2, 3, 4]
# ArgumentError: wrong number of arguments (4 for 3)

También puede usar -> para crear y .() Para llamar a lambda

the_thing = ->(magic, ohai, dere) {
  puts "magic! #{magic}"
  puts "ohai #{dere}"
  puts "#{ohai} means hello"
}

the_thing.(1, 2, 3)
# => magic! 1
# => ohai 3
# => 2 means hello

Aquí puedes ver que un lambda es casi lo mismo que un proc. Sin embargo, hay varias advertencias:

  • La aridad de los argumentos de un lambda se hace cumplir; pasar el número incorrecto de argumentos a un lambda, generará un ArgumentError . Todavía pueden tener parámetros predeterminados, parámetros splat, etc.

  • return ing desde una lambda devuelve desde la lambda, mientras return ing desde una proc devuelve fuera del alcance adjunto:

    def try_proc
      x = Proc.new {
        return # Return from try_proc
      }
      x.call
      puts "After x.call" # this line is never reached
    end
    
    def try_lambda
      y = -> {
        return # return from y
      }
      y.call
      puts "After y.call" # this line is not skipped
    end
    
    try_proc # No output
    try_lambda # Outputs "After y.call"
    

Objetos como argumentos de bloque a métodos.

Poner un & (ampersand) delante de un argumento lo pasará como el bloque del método. Los objetos se convertirán en un Proc utilizando el método to_proc .

class Greeter
  def to_proc
    Proc.new do |item|
      puts "Hello, #{item}"
    end
  end
end

greet = Greeter.new

%w(world life).each(&greet)

Este es un patrón común en Ruby y muchas clases estándar lo proporcionan.

Por ejemplo, Symbol s implementa to_proc enviándose ellos mismos al argumento:

# Example implementation
class Symbol
  def to_proc
    Proc.new do |receiver|
      receiver.send self
    end
  end
end

Esto habilita el lenguaje útil &:symbol , comúnmente utilizado con objetos Enumerable :

letter_counts = %w(just some words).map(&:length)  # [4, 4, 5]

Bloques

Los bloques son fragmentos de código encerrados entre llaves {} (generalmente para bloques de una sola línea) o do..end (utilizados para bloques de varias líneas).

5.times { puts "Hello world" } # recommended style for single line blocks

5.times do
    print "Hello "
    puts "world"
end   # recommended style for multi-line blocks

5.times {
    print "hello "
    puts "world" } # does not throw an error but is not recommended

Nota: los frenos tienen mayor prioridad que do..end

Flexible

Los bloques se pueden usar dentro de los métodos y funciones usando la palabra yield :

def block_caller
    puts "some code"
    yield
    puts "other code"
end
block_caller { puts "My own block" } # the block is passed as an argument to the method.
#some code
#My own block
#other code

Tenga cuidado, si se llama a yield sin un bloque, se generará un LocalJumpError . Para este propósito ruby ​​proporciona otro método llamado block_given? esto le permite verificar si se pasó un bloqueo antes de llamar a rendimiento

def block_caller
  puts "some code" 
  if block_given? 
    yield
  else
    puts "default"
  end
  puts "other code"
end
block_caller 
# some code
# default
# other code
block_caller { puts "not defaulted"}
# some code
# not defaulted
# other code

yield puede ofrecer argumentos al bloque también.

def yield_n(n)
  p = yield n if block_given?
  p || n 
end
yield_n(12) {|n| n + 7 } 
#=> 19 
yield_n(4) 
#=> 4

Si bien este es un ejemplo simple, el yield puede ser muy útil para permitir el acceso directo a las variables de la instancia o evaluaciones dentro del contexto de otro objeto. Por ejemplo:

class Application
  def configuration
    @configuration ||= Configuration.new
    block_given? ? yield(@configuration) : @configuration
  end
end
class Configuration; end

app = Application.new 
app.configuration do |config| 
  puts config.class.name
end
# Configuration
#=> nil 
app.configuration
#=> #<Configuration:0x2bf1d30>

Como puede ver, usar el yield de esta manera hace que el código sea más legible que llamar continuamente a app.configuration.#method_name . En su lugar, puede realizar toda la configuración dentro del bloque manteniendo el código contenido.

Variables

Las variables para bloques son locales al bloque (similares a las variables de funciones), mueren cuando se ejecuta el bloque.

my_variable = 8
3.times do |x|
    my_variable = x 
    puts my_variable
end
puts my_variable
#=> 0
# 1
# 2
# 8

Los bloques no se pueden salvar, mueren una vez ejecutados. Para guardar bloques necesitas usar procs y lambdas .

Convertir a Proc

Los objetos que responden a to_proc se pueden convertir a procs con el operador & (lo que también permitirá que se pasen como bloques).

El símbolo de la clase define #to_proc por lo que intenta llamar al método correspondiente en el objeto que recibe como parámetro.

p [ 'rabbit', 'grass' ].map( &:upcase ) # => ["RABBIT", "GRASS"]

Los objetos de método también definen #to_proc .

output = method( :p )

[ 'rabbit', 'grass' ].map( &output ) # => "rabbit\ngrass"

Aplicación parcial y curry

Técnicamente, Ruby no tiene funciones, sino métodos. Sin embargo, un método de Ruby se comporta de manera casi idéntica a las funciones en otro idioma:

def double(n)
  n * 2
end

Este método / función normal toma un parámetro n , lo duplica y devuelve el valor. Ahora definamos una función de orden superior (o método):

def triple(n)
  lambda {3 * n}
end

En lugar de devolver un número, el triple devuelve un método. Puedes probarlo usando el Ruby Interactive Shell :

$ irb --simple-prompt
>> def double(n)
>>   n * 2
>> end
=> :double
>> def triple(n)
>>   lambda {3 * n}
>> end
=> :triple
>> double(2)
=> 4
>> triple(2)
=> #<Proc:0x007fd07f07bdc0@(irb):7 (lambda)>

Si realmente desea obtener el número triplicado, debe llamar (o "reducir") la lambda:

triple_two = triple(2)
triple_two.call # => 6

O más concisamente:

triple(2).call

Aplicaciones al curry y parciales.

Esto no es útil en términos de definir una funcionalidad muy básica, pero es útil si desea tener métodos / funciones que no sean llamados o reducidos instantáneamente. Por ejemplo, supongamos que desea definir métodos que agreguen un número por un número específico (por ejemplo, add_one(2) = 3 ). Si tuvieras que definir una tonelada de estos, podrías hacer:

def add_one(n)
  n + 1
end 

def add_two(n)
  n + 2
end

Sin embargo, también podría hacer esto:

add = -> (a, b) { a + b }
add_one = add.curry.(1)
add_two = add.curry.(2)

Usando el cálculo lambda podemos decir que add es (λa.(λb.(a+b))) . Currying es una forma de aplicar parcialmente add . Entonces add.curry.(1) , es (λa.(λb.(a+b)))(1) que puede reducirse a (λb.(1+b)) . La aplicación parcial significa que pasamos un argumento para add pero dejamos el otro argumento para proporcionarlo más adelante. La salida es un método especializado.

Ejemplos más útiles de curry

Digamos que tenemos una fórmula general realmente grande, que si le especificamos ciertos argumentos, podemos obtener fórmulas específicas de ella. Considera esta fórmula:

f(x, y, z) = sin(x\*y)*sin(y\*z)*sin(z\*x)

Esta fórmula está hecha para trabajar en tres dimensiones, pero digamos que solo queremos esta fórmula con respecto a y y z. Digamos también que para ignorar x, queremos establecer su valor en pi / 2. Primero hagamos la fórmula general:

f = ->(x, y, z) {Math.sin(x*y) * Math.sin(y*z) * Math.sin(z*x)}

Ahora, usemos el curry para obtener nuestra fórmula yz :

f_yz = f.curry.(Math::PI/2)

Luego para llamar a la lambda almacenada en f_yz :

f_xy.call(some_value_x, some_value_y)

Esto es bastante simple, pero digamos que queremos obtener la fórmula para xz . ¿Cómo podemos configurar y en Math::PI/2 si no es el último argumento? Bueno, es un poco más complicado:

f_xz = -> (x,z) {f.curry.(x, Math::PI/2, z)}

En este caso, debemos proporcionar marcadores de posición para el parámetro que no estamos rellenando previamente. Por coherencia podríamos escribir f_xy así:

f_xy = -> (x,y) {f.curry.(x, y, Math::PI/2)}

Así es como funciona el cálculo lambda para f_yz :

f = (λx.(λy.(λz.(sin(x*y) * sin(y*z) * sin(z*x))))
f_yz = (λx.(λy.(λz.(sin(x*y) * sin(y*z) * sin(z*x)))) (π/2) # Reduce => 
f_yz = (λy.(λz.(sin((π/2)*y) * sin(y*z) * sin(z*(π/2))))

Ahora veamos f_xz

f = (λx.(λy.(λz.(sin(x*y) * sin(y*z) * sin(z*x))))
f_xz = (λx.(λy.(λz.(sin(x*y) * sin(y*z) * sin(z*x)))) (λt.t) (π/2)  # Reduce =>
f_xz = (λt.(λz.(sin(t*(π/2)) * sin((π/2)*z) * sin(z*t))))

Para más información sobre el cálculo lambda intente esto .



Modified text is an extract of the original Stack Overflow Documentation
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow