2015-02-10 5 views
15

Ho notato che molti esempi relativi a Ruby Proc hanno il seguente simbolo &.Scopo di & (commerciale) in Ruby per proc e metodi di chiamata

# Ruby Example 
shout = Proc.new { puts 'Yolo!' } 

def shout_n_times(n, &callback) 
    n.times do 
    callback.call 
    end 
end 

shout_n_times(3, &shout) 
# prints 'Yolo!' 3 times 

La mia domanda è che cosa è lo scopo funzionale dietro il simbolo &? Sembra che se ho scritto lo stesso codice esatto, senza &, funziona come previsto:

# Same code as previous without & 
shout = Proc.new { puts 'Yolo!' } 

def shout_n_times(n, callback) 
    n.times do 
    callback.call 
    end 
end 

shout_n_times(3, shout) 
# prints 'Yolo!' 3 times 
+1

Non inserire uno spazio prima parentesi! :) –

+0

Scusa, la mia abitudine proviene dal mondo di JavaScript :) La correggerò nel mio esempio. – wmock

+0

http://ablogaboutcode.com/2012/01/04/the-ampersand-operator-in-ruby/ – user2864740

risposta

29

This article fornisce una buona panoramica delle differenze.

Per riepilogare l'articolo, Ruby consente blocchi impliciti ed espliciti. Inoltre, Ruby ha block, proc e lambda.

Quando si chiama

def foo(block) 
end 

block è solo un semplice argomento del metodo. L'argomento è riferito nella variabile block e il modo in cui l'interazione con esso dipende dal tipo di oggetto che si passa.

def foo(one, block, two) 
    p one 
    p block.call 
    p two 
end 

foo(1, 2, 3) 
1 
NoMethodError: undefined method `call' for 2:Fixnum 
    from (irb):3:in `foo' 
    from (irb):6 
    from /Users/weppos/.rvm/rubies/ruby-2.1.5/bin/irb:11:in `<main>' 

foo(1, Proc.new { 1 + 1 }, 3) 
1 
2 
3 

Ma quando si utilizza la e commerciale & nella definizione del metodo, il blocco assume un significato diverso. Sei esplicitamente che definisce un metodo per accettare un blocco. E verranno applicate altre regole (come non più di un blocco per metodo).

def foo(one, two, &block) 
    p one 
    p block.call 
    p two 
end 

Innanzitutto, essendo un blocco, la firma del metodo ora accetta "due parametri e un blocco", non "tre parametri".

foo(1, 2, Proc.new { "from the proc" }) 
ArgumentError: wrong number of arguments (3 for 2) 
    from (irb):7:in `foo' 
    from (irb):12 
    from /Users/weppos/.rvm/rubies/ruby-2.1.5/bin/irb:11:in `<main>' 

Ciò significa, è necessario forzare il terzo argomento di essere un blocco passando il parametro con la e commerciale.

foo(1, 2, &Proc.new { "from the proc" }) 
1 
"from the proc" 
2 

Tuttavia, questa è una sintassi molto rara.In rubino, metodi con i blocchi sono generalmente chiamati usando {}

foo(1, 2) { "from the block" } 
1 
"from the block" 
2 

o do end.

foo(1, 2) do 
    "from the block" 
end 
1 
"from the block" 
2 

Torniamo alla definizione del metodo. Ho già detto che il codice seguente è una dichiarazione di blocco esplicita .

def foo(one, two, &block) 
    block.call 
end 

I metodi possono accettare implicitamente un blocco. I blocchi impliciti sono chiamati con yield.

def foo(one, two) 
    p yield 
end 

foo(1, 2) { "from the block" } 

È possibile controllare il blocco è passato utilizzando block_given?

def foo(one, two) 
    if block_given? 
    p yield 
    else 
    p "No block given" 
    end 
end 

foo(1, 2) { "from the block" } 
=> "from the block" 

foo(1, 2) 
=> "No block given" 

Queste caratteristiche dei blocchi legati non sarebbero disponibili se si dichiara il "blocco" come un semplice argomento (quindi senza commerciale), a causa sarebbe solo un argomento metodo anonimo.

+0

Grazie per la risposta completa e l'esempio - mi ha davvero aiutato a capirlo meglio! – wmock

+0

Ho trovato l'articolo collegato frustrante e sgradevole da leggere. Grazie per la fantastica sostituzione. – Hovis

3

Beh, quando si ha un blocco, se si applica & prima del blocco, diventa Proc oggetto e viceversa .

_unary &_: ha qualcosa a che fare con la conversione di cose da e verso i blocchi. Se non prendi nient'altro da questo, ricorda che quando vedi un unario "&" in Ruby, stai cercando di trasformare qualcosa in un blocco, o di fare un blocco in qualcosa.

Nel vostro primo esempio, in questa linea shout_n_times(3, &shout), si sta convertendo l'oggetto a cui fa riferimento Procshoot variabile a un block. e quindi nell'elenco dei parametri del metodo, si sta convertendo nuovamente in un oggetto Proc.

Nel secondo esempio funziona, perché si passa direttamente un oggetto Proc come argomento del metodo e quindi si chiama #call.

+0

Solo così sono chiaro, stai dicendo che se ho un oggetto Proc e lo aggiungo a &, il risultato può essere usato come un blocco di codice per i metodi? E d'altra parte, se ho un blocco di codice e poi lo aggiungo a &, posso usare il risultato come proc? Grazie! – wmock

+2

Ci sono solo due posti in cui è possibile utilizzare il prefisso unario 'operatore &': liste di argomenti e liste di parametri. In una lista di parametri, significa "converti il ​​blocco passato a un' Proc' e legalo al nome ". In una lista di argomenti, significa "converti il' Proc' in un blocco come se fosse passato come un blocco letterale ", con l'ulteriore vantaggio che se l'oggetto che viene passato non è un' Proc', Ruby prima chiamerà ' to_proc' su di esso per costringerlo a un 'Proc', che rende possibili trucchetti come' Symbol # to_proc'. –

2

La differenza è che nel primo esempio:

# Ruby Example 
shout = Proc.new { puts 'Yolo!' } 

def shout_n_times(n, &callback) 
    n.times do 
    callback.call 
    end 
end 

shout_n_times(3, &shout) 

... la sintassi chiamata di metodo consente di riscrivere la definizione metodo come questo:

shout = Proc.new { puts 'Yolo!' } 

def shout_n_times(n) 
    n.times do 
    yield 
    end 
end 

shout_n_times(3, &shout) 

--output:-- 
Yolo! 
Yolo! 
Yolo! 

Queste due affermazioni:

shout = Proc.new { puts 'Yolo!' } 
... 
shout_n_times(3, &shout) 

... sono equivalenti a:

shout_n_times(3) do 
    puts 'Yolo!' 
end 

e la scrittura di rendimento() all'interno della definizione del metodo di shout_n_times() chiama il blocco specificato dopo la chiamata al metodo:

method call +--start of block specified after the method call 
     |   |  
     V   V 
shout_n_times(3) do 
    puts 'Yolo!' 
end 
^ 
| 
+--end of block 

Vedete, un blocco è come un metodo e un blocco viene passato come argomento invisibile nella chiamata al metodo dopo la quale viene scritto il blocco. E all'interno della definizione del metodo, chiunque abbia scritto la definizione del metodo può eseguire il blocco con yield(). I blocchi di Ruby non sono altro che una sintassi speciale che ti permette di passare un metodo come argomento ad un altro metodo.

+0

Grazie per l'esempio illustrativo! Davvero utile! – wmock

6

Come ulteriore, mi ricordo di & come un segno di conversione tra block e Proc.

Per convertire un block per Proc

def foo(&p) 
    puts p.class 
end 

foo {} # => Proc 

Per convertire un Proc ad un block

def bar 
    yield "hello" 
end 
p = Proc.new {|a| puts a } 

bar &p # => hello