Usage de l'ampersand dans une méthode

21 juin 2025

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 à un Symbol#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   

voir bonnes pratiques

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 #withok 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))  

Romain GEORGES

Open Source evangelist & Ruby enthousiast