Venir à Ruby après un autre langage

Au premier coup d’œil, Ruby vous rappellera certainement les autres langages que vous avez déjà pu utiliser. Rien de plus normal : une bonne partie de la syntaxe se rapproche de Perl, de Python et de Java (entre autres), de sorte que si vous venez d’un de ces langages, apprendre Ruby devrait être plutôt facile.

Le présent document se partage en deux grandes sections. En premier lieu, une revue rapide de ce à quoi vous pouvez vous attendre en passant d’un langage donné à Ruby. Dans un deuxième temps, un examen plus approfondi des fonctionnalités propres de Ruby, illustré de quelques comparaisons avec d’autres langages.

À quoi s’attendre : passer d’un langage X à Ruby

Les fonctionnalités importantes et autres astuces

Voici un aperçu et des conseils concernant les fonctionnalités majeures de Ruby que vous allez rencontrer au cours de votre apprentissage.

Itération

Représentant en général une nouveauté technique pour celui qui découvre Ruby, les blocs et les itérateurs demandent en général un petit temps d’adaptation. Au lieu de construire une boucle sur un index (comme en C, C++ ou java < 1.5) ou sur une liste (comme en Perl, avec for (@a) {...}, ou en Python, avec for i in Liste: ...), vous écrirez souvent en Ruby quelque chose comme :

une_liste.each do |item_courant|
  # Nous sommes dans le bloc.
  # Travaillons avec l'item_courant de la liste...
end

Pour plus d’informations sur each et ses méthodes apparentées (collect, find@n @inject, sort, etc.), voyez ri Enumerable dans un terminal, par exemple (affinez ensuite avec ri Enumerable#méthode).

Des valeurs, partout

Ruby ne fait pas de différence entre une expression et une déclaration. Tout ce qui existe possède intrinsèquement une valeur, même si cette valeur est nil (qui modélise l’absence de valeur). De ce fait, ce qui suit est possible :

x = 10
y = 11
z = if x < y
      true
    else
      false
    end
z # => true

Les symboles ne sont pas des chaînes allégées

Beaucoup de débutants en Ruby se débattent longtemps avec la nature exacte des symboles, et la pertinence de leur utilisation.

La meilleure façon de décrire les symboles seraient de dire qu’ils sont des identités. Un symbole porte essentiellement sur le qui, et non sur le ce que—il ne s’agit pas de déterminer la nature de ce qui est, mais bien de savoir ce qui est. Le test suivant dans IRB illustre la notion :

irb(main):001:0> :george.object_id == :george.object_id
=> true
irb(main):002:0> "george".object_id == "george".object_id
=> false
irb(main):003:0>

La méthode object_id retourne l’identité d’un objet donné. Si deux objets ont la même identité, alors il s’agit d’un seul et même objet (même allocation mémoire).

Comme vous pouvez le voir, une fois un symbole défini, tout symbole de mêmes caractères (même nom) fait référence au même objet en mémoire. Pour deux symboles donnés représentant les mêmes caractères, l’object_id est unique.

Voyez maintenant le résultat obtenu sur la chaîne de caractères “george”. L’object_id ne correspond pas. Cela signifie qu’il y a bien deux objets différents, qui se ressemblent, mais qui n’ont pas la même identité. À chaque fois qu’une chaîne de caractère est utilisée, Ruby réalise une nouvelle allocation en mémoire.

Si vous doutez quant à l’utilisation d’un symbole ou d’une chaîne, estimez ce qui est dans votre cas le plus important : l’identité de l’objet (par exemple, la clé d’un hash) ou son contenu (par exemple, « george »).

Tout est un objet

En Ruby, l’expression « orienté objet » n’est pas une hyperbole. Jusqu’aux classes et aux entiers, tout ce qui est manipulable est un objet à part entière—objet acceptant les manipulations usuelles du type :

# Voici un bout de code équivalent à :
# class MaClass
#   attr_accessor :var_instance
# end
MaClass = Class.new do
  attr_accessor :var_instance
end

Des constantes variables

Ce qu’on appele habituellement constante ne l’est pas vraiment, en Ruby. La modification d’une constante est possible : elle produit un avertissement, mais ne stopppe pas l’exécution du programme. Ce qui n’es pas un encouragement en soi à redéfinir les constantes, cela dit.

Conventions de nommages

Ruby intègre (et impose) des conventions de nommage sémantiques. Si un identifiant commence par une majuscule, il s’agit d’une constante. S’il débute par un signe dollar ($), c’est une variable globale. S’il débute par un @, il s’agit d’une variable d’instance. S’il commence par @@, c’est une variable de classe.

Les noms de méthodes peuvent débuter par une majuscule—mais cela peut conduire à des confusions, comme dans l’exemple qui suit :

Constante = 10
def Constante
  11
end

Constante vaut 10, mais Constante() vaut 11.

Paramètres « mot-clés »

Depuis Ruby 2.0, à l’instar de Python, il est possible de définir une méthode avec des paramètres « mot-clés » :

def deliver(from: "A", to: nil, via: "mail")
  "Sending from #{from} to #{to} via #{via}."
end

deliver(to: "B")
# => "Sending from A to B via mail."
deliver(via: "Pony Express", from: "B", to: "A")
# => "Sending from B to A via Pony Express."

La vérité, toujours la vérité

En Ruby, tout ce qui n’est pas nil ou false est considéré comme vrai (true). En C, Python et dans bien d’autres langages, le 0 et d’autres valeurs (telles les listes vides) sont considérées fausses. Voyez par exemple le bout de code suivant, écrit en Python :

# en Python
if 0:
  print("0 est vrai/true")
else:
  print("0 est faux/false")

Ce qui affichera « 0 est faux/false. » L’équivalent en Ruby, maintenant :

# en Ruby
if 0
  puts "0 est vrai/true"
else
  puts "0 est faux/false"
end

Cette fois, vous lirez « 0 est vrai/true. »

Les modifications d’accès sont actives jusqu’à preuve du contraire

Considérez le bout de code suivant :

class MyClass
  private
  def a_method; true; end
  def another_method; false; end
end

Vous pourriez vous attendre à ce que another_method soit publique. Ce n’est pas le cas. Le mot-clé private est effectif jusqu’à la fin de la portée actuelle (ici, la classe MyClass), ou jusqu’à ce qu’un autre mot-clé change la donne. Par défaut, les méthodes sont publiques :

class MyClass
  # Méthode implicitement publique
  def a_method; true; end

  private

  # Cette méthode est privée
  def another_method; false; end
end

public, private et protected sont des méthodes à part entière, elles peuvent prendre des paramètres. Si vous passez un symbole à l’une d’elle, la visibilité de cette méthode est modifiée.

Accès aux méthodes

En Java, public signifie qu’une méthode est accessible par tout un chacun. protected signifie que les instances de la classe, les instances de classes filles et les instances de classes du même paquet peuvent y accéder, à l’exclusion de tout autre domaine. private signifie que personne à part les instances de la classe ne peut accéder à la méthode.

Ruby se démarque un peu ici. public signifie la même chose, mais private signifie que les méthodes sont accessibles uniquement si elles peuvent être appelées sans destinataire explicite. En fait, seul self est autorisé. protected est à part : une méthode protégée peut être appelée depuis une instance de classe ou de classe fille, mais également avec une autre instance comme destinataire.

Un exemple, repris de la FAQ Ruby:

class Test
  # publique par défaut
  def identifier
    99
  end

  def ==(other)
    identifier == other.identifier
  end
end

t1 = Test.new  # => #<Test:0x34ab50>
t2 = Test.new  # => #<Test:0x342784>
t1 == t2       # => true

# passer `identifier' en protected fonctionne toujours :
# la référence à other est autorisée

class Test
  protected :identifier
end

t1 == t2  # => true

# par contre, si `identifier' est private...

class Test
  private :identifier
end

t1 == t2
# NoMethodError: private method `identifier' called for #<Test:0x342784>

Les classes restent ouvertes

Ouvertes à la modification, à tout moment. Vous pouvez y faire des ajouts, les modifier durant l’exécution. Y compris les classes standards, telles que Integer, voire Object, la classe parente de toute autre. Par exemple, l’application Ruby on Rails défini nombre de méthodes pour traiter le temps, au sein de Integer. Voyez ceci :

class Integer
  def hours
    self * 3600 # nombre de secondes dans une heure
  end
  alias hour hours
end

# 14 heures après le 1er janvier à 00h00
Time.mktime(2006, 01, 01) + 14.hours # => Sun Jan 01 14:00:00

Indices sémantiques sur les méthodes

Un nom de méthode en Ruby peut se terminer par un point d’interrogation ou d’exclamation. Le premier signe de ponctuation sera utilisé pour les méthodes qui donnent une réponse à une question (par exemple, Array#empty?, qui retourne true si le destinataire est vide). Les méthodes potentiellement « dangereuses » (parce qu’elles modifient self ou les paramètres, comme exit!) sont signalées par le second signe.

Mais ce n’est pas obligatoire, et parfois le choix a été de ne rien indiquer du tout. Ainsi, Array#replace modifie sur place le contenu d’un tableau avec le contenu d’un autre tableau.

Les méthodes singletons

Une méthode singleton est une méthode liée à un objet. Elle n’est disponible que pour l’objet défini.

class Car
  def inspect
    "Cheap car"
  end
end

porsche = Car.new
porsche.inspect # => Cheap car
def porsche.inspect
  "Expensive car"
end

porsche.inspect # => Expensive car

# Les autres objets ne sont pas affectés
other_car = Car.new
other_car.inspect # => Cheap car

Gestion des méthodes manquantes

Si un message ne correspond pas à une méthode explicitement définie, Ruby n’abandonne pas la partie et passe la main à la méthode nommée method_missing. Elle l’informe du nom de la méthode introuvable et des éventuels paramètres joints au message. Par défaut, method_missing lève une exception du type NameError, mais vous pouvez la redéfinir pour mieux l’intégrer au contexte de votre application—de nombreuses bibliothèques exploitent cette possibilité. Voici un exemple :

# id est le nom de la méthode appelée, la syntaxe * renvoie
# tous les paramètres dans un tableau nommé « arguments »
def method_missing(id, *arguments)
  puts "La méthode #{id} a été appelée, mais elle n'existe pas. " +
       "Voici les paramètres de l'appel : #{arguments.join(", ")}"
end

__ :a, :b, 10
# => La méthode __ a été appelée, mais elle n'existe pas. Voici les paramètres de l'appel :
# arguments: a, b, 10

Le code ci-dessus ne fait qu’afficher les détails de l’appel, mais vous êtes libres de manipuler tout ça à votre guise.

Envoi de message et non appel de fonction

Ce qui est communément appelé « appel de méthode » est bel et bien un message envoyé à un autre objet—voyez plutôt :

# Ceci...
1 + 2
# est équivalent à...
1.+(2)
# qui est la même chose que :
1.send "+", 2

Les blocs aussi sont des objets—bien qu’ils ne le sachent pas (encore)

Les blocs (closures) sont massivement utilisés dans la bibliothèque standard. Pour appeler un bloc, vous pouvez utiliser yield, ou bien le transformer en Proc en rajoutant un paramètre spécial à la liste d’arguments, comme ceci :

def bloc(&le_bloc)
  # Ici, dedans, le_bloc est le bloc passé à la méthode
  le_bloc # retourne le bloc
end
addition = bloc { |a, b| a + b }
# addition est maintenant un objet du genre Proc
addition.class # => Proc

Cela signifie que vous pouvez créer des blocs en-dehors du contexte des appels de méthode, en utilisant Proc.new avec un bloc ou en appelant une méthode lambda.

De la même façon, les méthodes sont également des objets bien réels :

method(:puts).call "puts est un objet !"
# => puts est un objet !

Opérer sur les opérateurs

La plupart des opérateurs ne sont là que pour faciliter la vie du programmeur (et gèrent aussi les règles de priorité mathématique). Vous pouvez, par exemple, redéfinir la méthode + de la classe Integer:

class Integer
  # Possible, mais pas recommandé...
  def +(other)
    self - other
  end
end

Pas besoin des operator+ comme en C++, etc.

Vous pouvez aussi créer des accès du type tableau en définissant les méthodes [] et []=. Pour définir les signes + et -, comme pour +1 et -2, vous définirez les méthodes +@ et -@, respectivement.

Les opérateurs ci-dessous ne sont pas des méthodes, et ne peuvent pas être modifiés :

=, .., ..., not, &&, and, ||, or, ::

Par ailleurs, +=, *=, etc. ne sont que des raccourcis pour var = var + autre_var, var = var * autre_var, etc. et ne peuvent être redéfinis.

En savoir (encore) plus

Enthousiaste ? Direction notre section Documentation.