quinta-feira, 27 de janeiro de 2011

Um novo Object#tap

Implementação atual de tap:
class Object
  def tap
    yield(self)
    self
  end
end
Mas o Daniel aqui discorda. Minha proposta é:
class Object
  def tap
    yield(self)
  end

  def tee
    yield(self)
    self
  end
end
Object#tap
altera a method chain
Object#tee
não altera a method chain (atual Object#tap)
Para Enumerable, nós temos Enumerable#collect, que funciona exatamente igual ao meu tap.
[1,2,3].collect {|i| i + i} => [2,4,6]
Porém não temos isso para objetos:
1.tap {|i| i + i } => 2
Object#tap executa um bloco na method chain sem altera-la.
[1,2,3].collect {|i| i * i }.tap {|a| puts a.size }.inject {|sum, i| sum + i } => 14 (escreve 3 na STDOUT antes do retorno)
Porém se você quer justamente passar um bloco apenas para alterar a method chain? Não deveria ser justamente essa a função de Object#tap?
[1,2,3].collect {|i| i * i }.tap {|a| a + a }.inject {|sum, i| sum + i } => 28
A origem do atual Object#tap vem do comando tee. Portanto, nada mais justo que:
class Object
  alias tee tap
  def tap
    yield(self)
  end
end
É preciosismo e a discussão é meramente teórica, já que podemos implementar este novo Object#tap com outro nome e evitar o alias, o que exigiria um refactoring de todo código que utiliza Object#tap. Possíveis nomes para meu Object#tap: get, come, become, go e grow. Viajei muito?

3 comentários:

  1. Não viajou não, se funcionasse assim seria perfeito.


    (aliás, teu exemplo parece errado - 1.tap {|i| i + i } - isso retorna 1, e não 2)

    abraço!

    ResponderExcluir
  2. Então, se vc quer interceptar a method chain e alterar um resultado, você já pode conseguir esse resultado na lógica anterior, não precisando do tap.

    Segundo a descrição que tem no ruby doc e no api do rails, o tap e sua implementação é apenas um block pra vc conseguir interferir e ver o que está acontecendo no meio do method chain, e o resultado acaba sendo o proprio self, que é o o resultado anterior.

    Com a sua implementação do tap, vc consegue os mesmos resultados usando map ou collect. Voce manipula seu enumerable da mesma forma. Nao sei se precisa ter mais um metodo pra isso!

    PS: nao conhecia o tee. Ja foi deprecado? Nao tem em nenhuma api.

    @Rafael Souza: Se voce sobrescrever o método com a implementacao do Daniel, retorna 2.

    ResponderExcluir
  3. @rbernardelli mas #map não existe para objetos arbitrários, existe? Acho que é essa a ideia do Daniel. Boa ideia, btw.

    ResponderExcluir

Comenta ae, amiguinho!