Skip to content

Instantly share code, notes, and snippets.

@fidelisrafael
Created October 1, 2013 12:49
Show Gist options
  • Save fidelisrafael/6777929 to your computer and use it in GitHub Desktop.
Save fidelisrafael/6777929 to your computer and use it in GitHub Desktop.
Operador `&` unário e binário em Ruby

O operador "&" em Ruby

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.

O & binário

No Ruby 1.9.3 existem 3 usos para o operador &.

Bitwise AND

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"

Interseção de conjunto

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]

Boleano AND

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>

Definições customizadas

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

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.

Se o objeto é um block,converte o block em um simple proc.

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]]"

Se o objeto é um Proc, ele converte o objeto preservando o status de lambda?

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."

Se o objeto não é um proc, primeiro chama #to_proc no objeto e converte ele em um block

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.

Refêrencias/Créditos

A Blog About Code
Tradução

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment