Ricerca…


Sintassi

  • Proc.new ( blocco )
  • lambda {| args | codice }
  • -> (arg1, arg2) {codice}
  • object.to_proc
  • {| single_arg | codice }
  • do | arg, (chiave, valore) | codice fine

Osservazioni

Fai attenzione alla precedenza degli operatori quando hai una linea con più metodi concatenati, come:

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

Invece di stampare qualcosa come abCDeFg , come ci si aspetterebbe, stampa qualcosa come #<Enumerator:0x00000000af42b28> - questo perché do ... end ha una precedenza inferiore rispetto ai metodi, il che significa che gsub vede solo l'argomento /./ e non l'argomento del blocco. Restituisce un enumeratore. Il blocco finisce per passare a puts , che lo ignora e mostra solo il risultato di gsub(/./) .

Per risolvere questo problema, gsub racchiudere la chiamata gsub tra parentesi o usare { ... } .

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"

Abbiamo copiato il metodo call_the_block dall'ultimo esempio. Qui, puoi vedere che un proc è fatto chiamando il metodo proc con un blocco. Puoi anche vedere che i blocchi, come i metodi, hanno ritorni impliciti, il che significa che anche procs (e lambda) fanno. Nella definizione di its_a , puoi vedere che i blocchi possono prendere argomenti splat e quelli normali; sono anche in grado di prendere argomenti predefiniti, ma non riesco a pensare a un modo per farlo. Infine, è possibile vedere che è possibile utilizzare più sintassi per chiamare un metodo - il metodo call o [] operatore.

lambda

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

Puoi anche usare -> per creare e .() Per chiamare 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

Qui puoi vedere che una lambda è quasi la stessa di una proc. Tuttavia, ci sono molti avvertimenti:

  • L'arbitrio degli argomenti di lambda sono applicati; passando il numero errato di argomenti a un lambda, si genera un ArgumentError . Possono ancora avere parametri predefiniti, parametri splat, ecc.

  • return dall'interno di un lambda ritorna dal lambda, mentre il return da un proc ritorna al di fuori del campo di applicazione:

    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"
    

Oggetti come argomenti di blocco ai metodi

Mettere una & ( & commerciale) davanti a un argomento lo passerà come il blocco del metodo. Gli oggetti saranno convertiti in un Proc utilizzando il metodo 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)

Questo è un modello comune in Ruby e molte classi standard lo forniscono.

Ad esempio, Symbol s implementa to_proc inviandosi all'argomento:

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

Questo abilita l'utile &:symbol idioma di &:symbol , comunemente usato con oggetti Enumerable :

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

blocchi

I blocchi sono blocchi di codice racchiusi tra parentesi graffe {} (di solito per blocchi a linea singola) o do..end (usati per blocchi a più linee).

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: le parentesi hanno una precedenza più alta di do..end

cedimento

I blocchi possono essere utilizzati all'interno di metodi e funzioni utilizzando la parola 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

Attenzione però se il yield viene chiamato senza blocco, genererà un LocalJumpError . A tal fine, ruby ​​fornisce un altro metodo chiamato block_given? questo ti permette di controllare se un blocco è stato passato prima di chiamare rendimento

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 può offrire argomenti anche al blocco

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

Mentre questo è un semplice esempio, la yield può essere molto utile per consentire l'accesso diretto a variabili di istanza o valutazioni nel contesto di un altro oggetto. Per esempio:

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>

Come puoi vedere usando yield in questo modo, il codice app.configuration.#method_name più leggibile rispetto a chiamare continuamente app.configuration.#method_name . Invece è possibile eseguire tutte le configurazioni all'interno del blocco mantenendo il codice contenuto.

variabili

Le variabili per i blocchi sono locali al blocco (simili alle variabili delle funzioni), muoiono quando il blocco viene eseguito.

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

I blocchi non possono essere salvati, muoiono una volta eseguiti. Per salvare i blocchi devi usare procs e lambdas .

Conversione in Proc

Gli oggetti che rispondono a to_proc possono essere convertiti in proc con l'operatore & (che consentirà anche che vengano passati come blocchi).

La classe Symbol definisce #to_proc così tenta di chiamare il metodo corrispondente sull'oggetto che riceve come parametro.

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

Gli oggetti metodo definiscono anche #to_proc .

output = method( :p )

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

Applicazione parziale e Currying

Tecnicamente, Ruby non ha funzioni, ma metodi. Tuttavia, un metodo Ruby si comporta quasi identicamente a funzioni in altre lingue:

def double(n)
  n * 2
end

Questo metodo / funzione normale prende un parametro n , lo raddoppia e restituisce il valore. Ora definiamo una funzione (o metodo) di ordine superiore:

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

Invece di restituire un numero, triple restituisce un metodo. Puoi testarlo usando Interactive Ruby 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)>

Se vuoi effettivamente ottenere il numero triplicato, devi chiamare (o "ridurre") il lambda:

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

O più concisamente:

triple(2).call

Currying e applicazioni parziali

Questo non è utile in termini di definizione di funzionalità di base, ma è utile se si desidera avere metodi / funzioni che non vengono immediatamente chiamati o ridotti. Ad esempio, supponiamo di voler definire metodi che aggiungono un numero per un numero specifico (ad esempio add_one(2) = 3 ). Se dovessi definire una tonnellata di questi potresti fare:

def add_one(n)
  n + 1
end 

def add_two(n)
  n + 2
end

Tuttavia, potresti anche fare questo:

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

Usando il calcolo lambda possiamo dire che l' add è (λa.(λb.(a+b))) . Il currying è un modo di applicare parzialmente l' add . Quindi add.curry.(1) , is (λa.(λb.(a+b)))(1) che può essere ridotto a (λb.(1+b)) . Applicazione parziale significa che abbiamo passato un argomento da add ma abbiamo lasciato l'altro argomento da fornire in seguito. L'output è un metodo specializzato.

Altri esempi utili di curry

Diciamo che abbiamo una formula generale davvero grande, che se specifichiamo determinati argomenti ad essa, possiamo ottenere formule specifiche da esso. Considera questa formula:

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

Questa formula è fatta per lavorare in tre dimensioni, ma diciamo che vogliamo solo questa formula per quanto riguarda y e z. Diciamo anche che per ignorare x, vogliamo impostare il suo valore su pi / 2. Facciamo prima la formula generale:

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

Ora, usiamo il curry per ottenere la nostra formula yz :

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

Quindi per chiamare il lambda memorizzato in f_yz :

f_xy.call(some_value_x, some_value_y)

Questo è piuttosto semplice, ma diciamo che vogliamo ottenere la formula per xz . Come possiamo impostare y su Math::PI/2 se non è l'ultimo argomento? Bene, è un po 'più complicato:

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

In questo caso, dobbiamo fornire segnaposto per il parametro che non stiamo pre-compilando. Per coerenza possiamo scrivere f_xy questo modo:

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

Ecco come funziona il calcolo lambda per 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))))

Ora diamo un'occhiata a 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))))

Per maggiori informazioni sul calcolo lambda, prova questo .



Modified text is an extract of the original Stack Overflow Documentation
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow