Usage de l'ampersand dans une méthode
Principe
En Ruby le préfixe &
sur un paramètre de méthode signifie qu'on passe la référence comme Proc
def my_method(&block)
block.call("hello")
end
puts my_method { |value| value.upcase }
la sortie :
HELLO
On fait une closure sur la méthode
Autre façon de voir les choses
Cela correspond à faire :
def my_method(&block)
block.call("hello")
end
puts my_method(&Proc.new { |value| value.upcase })
Compliquons un peu les choses
Je fais un monkey patch pour montrer que cela marche pour toutes méthodes de l'objet paramètre du call :
class String
def meth_wrapper_upcase
self.upcase
end
end
def my_method(&block)
block.call("hello")
end
puts my_method(&:meth_wrapper_upcase)
Ici le & s'applique au Symbol :meth_wrapper_upcase qui est transformé en Proc correspondant au code de la méthode de nom correspondant au Symbol
le&
est identique à unSymbol#to_proc
Remarque : pour bien comprendre il faut aller regarder Symbol#to_proc
la documentation dit :

donc une closure sur :my_method
du bloc issue de la transformation de la méthode :meth_wrapper_upcase
applicable sur le paramètre de closure.
En l’occurrence une String
Du coup
Toutes ses lignes sont équivalentes :
[1, 2, 3].map(&:to_s)
[1, 2, 3].map(&Proc.new { |number| number.to_s })
[1, 2, 3].map { |number| number.to_s }
En ruby on préfèrera le sucre syntaxique &:sym_meth
Oui mais y a un petit problème
Si on prend le code suivant
def convert(*args)
args.map { |item| item.to_s(2) }
end
ici on a un paramètre à la méthode to_s
pour faire de la base 2
on va essayer de faire plus court
def convert(*args)
args.map(&:to_s(2))
end
mais :to_s
reste un Symbol, or syntaxiquement :symbole(params)
n'est pas correct en Ruby donc :
/home/ruydiaz/labo/sandbox/test.rb:2: syntax error, unexpected '(', expecting ')' args.map(&:to_s(2))
^
/home/ruydiaz/labo/sandbox/test.rb:5: syntax error, unexpected end-of-input, expecting `end'
et la c'est le drame ....
Mais pas tout à fait
on va jouer avec le caller et faire du forwarding
On commence avec un petit monkey patch on fera mieux par la suite
class Symbol
def with(*args, &block)
->(caller, *rest) { caller.send(self, *rest, *args, &block) }
end
end
on va s'ajouter une réimplementation de Symbol
pour ajouter le forwarder #with
, ok le code pique un peu
En gros ça forward params et block vers le Symbol
Pour l'utiliser :
p [1,2,3,4,5].map(&:+.with(2))
qui renvoie :
[3, 4, 5, 6, 7]
On peut faire encore mieux
En ruby #call peut être abrévié #() , du coup le monkey patch devient :
class Symbol
def call(*args, &block)
->(caller, *rest) { caller.send(self, *rest, *args, &block) }
end
end
p [1,2,3,4,5].map(&:+.(1))
On peut faire plus propre
Les monkey patch c'est pas bien, mieux vaut faire un Refinement
module AmpWithArgs
refine Symbol do
def call(*args, &block)
->(caller, *rest) { caller.send(self, *rest, *args, &block) }
end
end
end
du coup l'usage devient, dans le scope qui va bien :
using AmpWithArgs
p [1,2,3,4,5].map(&:+.(1))