Eu vou assumir que você já é familiarizado com o operador de duplos &
comerciais em Ruby - O operador logico AND
. Este post irá
se focar e mtodos os usos do operador único &
. &
pode ser um pouco confuso porque ele tem significado diferente dependendo do contexto
que é usado. Na verdade, ambas as operações unárias(&object) e o binárias (object & object) tem significados diferentes em Ruby. Para entender
eles vão dar uma olhada nos usos do operador &
no core do Ruby.
No Ruby 1.9.3 existem 3 usos para o operador &
.
O bit a bit AND é o binário bit a bit equivalente do boleano "AND"
Então o binário 101 & 100 = 100
and o binário 101 & 001 = 001
. &
é definido como um operador bit a bit AND para Bignum
, Fixnum
, and Process::Status
e simplesmente converte o numério inteiro numa representação binário e executa um "bit a bit AND" nessa representacão.
Process::Status
converte o status do numero do processo em um Fixnum
e usa ele para executar a operação.
irb(main):001:0> 14 & 13
=> 12
O resultado desta operação pode ser facilmente visualizado convertando os números para suas representações binárias.
irb(main):001:0> "#{14.to_s(2)} & #{13.to_s(2)} = #{12.to_s(2)}"
=> "1110 & 1101 = 1100"
O uso mais simples do operador binário &
provavelmente é na classe Array
. &
é um operador de interseção, que significa que o resultado é uma coleções dos elements em comum em ambas as arrays.
irb(main):001:0> [1,2,3] & [1,2,5,6]
=> [1, 2]
Nas classes FalseClass
, NilClass
e TruClass
o operador binário &
é o equivalente ao boleando AND
. Tenha em mente que
ele não faz seu trabalho como o operador &&
, porque ele é somente definido nessas três classes.
irb(main):001:0> false & true
=> false
irb(main):002:0> nil & true
=> false
irb(main):003:0> true & Object.new
=> true
irb(main):004:0> Object.new & true
=> NoMethodError: undefined method `&' for #<Object:0x007f9e7ac96420>
Quando o operador binário &
é invocado(Primeiro & Segundo
) o Ruby executa a definição do primeiro objeto(First#&
). Você pode
escrever seu próprio metodo do operador &
facilmente. Quando eu faço, eu tento manter os dois primeiros usos do core do Ruby em mente - bit-a-bit AND e interseção de conjunto. Eu não gosto de usar o operador logico AND
porque ele já é coberto pelo operador &&
e é somente
definido em true
, false
e nil
, que são classes especiais.
Abaixo, um exemplo customizado de como funciona uma interseção de conjunto.
DNA = Struct.new(:chain) do
def triples
chain.split(//).each_slice(3).to_a
end
def &(dna)
(triples & dna.triples).map{ |triple| triple.join }
end
end
irb(main):001:0> DNA.new('AGGTTACCA') & DNA.new('TTAAGGCCC')
=> ["AGG", "TTA"]
O unário &
é um pouco mais complexo. Ele é quase equivalente ao chamar #to_proc
em um objeto, mas não exatamente. Para entender isso vamos
ver algumas coisas antes. Em Ruby você tem dois tipos de códigos de blocos, Blocks
and Procs
. Os dois são extremamente relacionados mas entre eles existem algumas diferenças importantes.
Você pode definir e referenciar Procs
e atribuir eles a variáveis. Blocks
estão sempre relacionados a uma chamada de método e não podem
ser definidos fora daquele contexto. A maneira como você distingui eles é que Procs
são sempre procedidos por Proc.new
, proc
, lambda
or ->()
quando eles são definidos.
# A block that is passed to the each function on the [1,2,3] Array.
[1,2,3].each do |x|
puts x
end
# A proc assigned to a variable.
k = Proc.new{ |x| puts x }
Todos os métodos tem um e apenas um argumento Block
implicito, você usando ele ou não. Você pode acessar ele chamando yield
no corpo
do método.
def two
yield(2)
end
irb(main):001:0> two{ |x| x*2 }
=> 4
Blocks
são menos inuteis foras de chamadas de funções, por exemplo, você não pode definir um :
irb(main):001:0> { |x| x*2 }
=> SyntaxError: syntax error, unexpected '|', expecting '}'
Mas você pode definir e referenciar Procs
.
irb(main):001:0> Proc.new{ |x| x*2 }
=> #<Proc:0x007f9e7ab766a8@(pry):30>
Procs
são dividos em duas categorias. Procs que são lambda?
, lambda procs
e Procs
que não são simples procs.
Lambdas são definidos usando lambda
ou ->()
e Procs
são definidos simplesmente usando Proc.new
or proc
.
irb(main):001:0> lambda{ |x| x*2 }
=> #<Proc:0x007f9e8a8c3f40@(pry):34 (lambda)>
irb(main):002:0> ->(x){ x*2 }
=> #<Proc:0x007f9e7a896ed8@(pry):35 (lambda)>
irb(main):003:0> Proc.new{ |x| x*2 }
=> #<Proc:0x007f9e7a8f95d8@(pry):36>
irb(main):004:0> proc{ |x| x*2 }
=> #<Proc:0x007f9e7a950e28@(pry):37>
Eu não vou entrar em detalhes sobre lambdas e simples procs, mas eles são duas coisas diferentes e é importante saber que eles existem.
O primeiro é que lambdas são stric arguments checkers, como métodos, eles podem lançar uma exception ArgumentError
. Procs
simples irão
somente ignorar argumentos incorretos, extras ou um menor numero de combinações. O segundo é que lambdas
atuam como metodos gravando seu
status de retorno. Eles podem retornar valores como métodos. Quando você tenta retornar um valor de um simple proc
você acaba com um erro do tipo LocalJumpError
Agora, de volta para nosso operador unário &
. Ambos Blocks
e Procs
são úteis, e é conveniente saber intercalar entre eles.
'&objeto' é avaliado da seguinte forma :
1 - Se objeto
é um block
, ele converte o block em um simple proc
.
2 - Se o objeto
é um Proc
, ele converte o objecto em um bloco preservando o status de lambda?
do objeto.
3 - Se o objeto não é um Proc
, ele primeiro chama #to_proc
no objeto e então converte ele em um block
.
Vamos examinar cada um desses passos separadamente.
O exemplo mais simples disso é quando nos desejamos ter acesso ao bloco que passamos para um método, em vez de somente chamar yield
.
Para fazer isso nos precisamos converter o block em um proc
.
def describe(&block)
"The block that was passed has parameters: #{block.parameters}"
end
irb(main):001:0> describe{ |a,b| }
=> "The block that was passed has parameters: [[:opt, :a], [:opt, :b]]"
irb(main):002:0> describe do |*args|
irb(main):003:0> end
=> "The block that was passed has parameters: [[:rest, :args]]"
Este é um caso extremamente útil do operador &
. Por exemplo, nos sabemos que o método Array#map
recebe um bloco, mas vamos imaginar
que temos um proc e que nos desejamos reutilizar-lo em multiplas chamadas de call
.
irb(main):001:0> multiply = lambda { |x| x*2 }
irb(main):002:0> [1,2,3].map(&multiply)
=> [2, 4, 6]
irb(main):003:0> [4,5,6].map(&multiply)
=> [8, 10, 12]
Lembre-se que o operador também preserva o status de lambda?
do bloco original. Isto é tipo puro porque significa que são capazes de passar
lambdas
(não simples procs) como blocos.
Isso significa que podemos obrigar argumentos estritos checando em nossos blocks e nos podemos capturar seu valor de returno usando a
a keyword return
. A única exceção para essa preservação são métodos, que são sempre lambdas independente de como eles são definidos.
def describe(&block)
"Calling lambda? on the block results in #{block.lambda?}."
end
irb(main):001:0> describe(&lambda{})
=> "Calling lambda? on the block results in true."
irb(main):002:0> describe(&proc{})
=> "Calling lambda? on the block results in false."
class Container
define_method(:m, &proc{})
end
irb(main):001:0> describe(&Container.new.method(:m).to_proc)
=> "Calling lambda? on the block results in true."
Aqui é aonde a mágica realmente acontece porque ela permite a passagem de objetos para as funções no lugar de blocks muitos simples.
Provavelmente, o caso mais comum disso é chamando Array#map
com um symbol
.
irb(main):001:0> ["1", "2", "3"].map(&:to_i)
=> [1, 2, 3]
Isto funciona porque chamando Symbol#to_proc
retorna um proc que responde para o método do simbolo. Então o simbolo :to_i
é primeiro
convertido para um Proc
e então em um block. Este é o tipo de legal porque podemos criar nossos proprios métodos #to_proc
.
class Display
def self.to_proc
lambda{ |x| puts(x) }
end
end
class FancyDisplay
def self.to_proc
lambda{ |x| puts("** #{x} **") }
end
end
irb(main):001:0> greetings = ["Hi", "Hello", "Welcome"]
irb(main):002:0> greetings.map(&Display)
Hi
Hello
Welcome
=> [nil, nil, nil]
irb(main):003:0> greetings.map(&FancyDisplay)
** Hi **
** Hello **
** Welcome **
=> [nil, nil, nil]
p = ->() { :to_i }
["1", "2", "3"].map {|x| x.send(p.call())}
=> [1, 2, 3]
Eu espero que este poste tenha esclarecido o operador binário e unário &
em Ruby. É um operador engraçado. Na forma binário ele pode
nos providenciar um atalho para efetuar operações como bitwise AND e interseção de conjuntos . Na forma unária ele pode nos providenciar
algumas funcionalidades poderosas para converter blocks e procs.