Hein?
/me aime beaucoup Ruby (entre autre). Mais voilà, aucun langage de programmation n'est parfait, comme aucun outil n'est universel. Pour une tâche donnée, certains outils sont (parfait|bien|difficile|impossible).
Ce qui me chicane, c'est les mauvaises argumentations.
Malheureusement, on ne peut poster de commentaires sur ce blog, donc je fais un petit billet :)
FUNCTION CALLS WITHOUT PARENTHESES
foo = bar
Is bar a variable? Is bar a method? There’s no way to know for sure.
Ben si. s'il existe une variable bar, alors c'est la variable qui est utilisée, sinon c'est la méthode (et si aucun des deux n'existe, ça raise NameError).
Le fait de pouvoir appeler des méthodes sans parenthèse est très pratiques. Souvent ça améliore la lisibilité. Par exemple, grâce à ça, il est possible d'implémenter en Ruby des DSL (Domain Specific Langage) d'une lisibilité et facilité déconcertante. Voici un exemple piqué dans le projet libastag, où l'on décrit une chorégraphique à exécuter (on peut bouger les oreilles et changer la couleurs de leds) :
Choregraphy.new do at time 0 set all leds to green move left ear forward from degrees 10 end
si on était obligé de mettre des parenthèses, ça donnerait tout de suite moins cosmétique :
Choregraphy.new do at(time(0)) set(all(leds(to(green)))) move(left(ear(forward(from(degrees(10)))))) end
(ici le programmeur LISP sourit)
On a après un exemple en C et en PHP :
/* C */ foo = bar(); /* function */ foo = bar; /* variable */
// PHP $foo = bar(); // function $foo = $bar; // variable
Dommage, il faudrait aller un poil plus loin. continuons :
// Java String str = "Hello world"; int[] ary = {12}; str.length() // function ary.length // variable
Oui, cher lecteur, en Java il faut appeler length() pour avoir la taille d'un objet String, length pour avoir la taille un tableau, et pour les autres objets, ça dépend de l'implémentation :)
Personnelement, je pense que tous les dev Java se posent cette question avant d'écrire length : "String, array, ou autre chose ?". En tout cas ça m'est arrivé plusieurs fois de me tromper, et pas une sans maudire cette API.
Ruby interdit l'accès aux variable d'instances. On utilise des méthodes public pour "simuler" l'accès et la modification de variables, mais cela reste des méthodes. On le fait parfois en Java également à grand coups de pioche setMyvar(arg) et getMyvar(). alors que Ruby donne du sucre syntaxique (omettre les parenthèse, permettre les méthodes avec =).
NO EXPLICIT RETURN
retourner implicitement la dernière évaluation d'une méthode est un concept magnifique de la programmation fonctionelle. Rien n'empêche en ruby d'écrire return, cela laisse justement la liberté au programmeur de choisir son paradigme de programmation.
First of all, in some cases you have to explicitly return anyway (e.g. at the beginning of a function)
def f ary return "pas glop" if ary.empty? # ... end
FUNCTIONS AND LAMBDAS AND PROCS AND BLOCKS
Confusingly, Procs created using proc or lambda behave differently from those created by Proc.new, but nobody really seems to understand those subtle differences.
(Ruby 1.9) Un objet Proc déclaré avec lambda, puis appelé avec un nombre d'argument plus petit/grand qu'attendu lève une exception ArgumentError (comme une méthode déclarée avec def), alors qu'avec Proc.new ou proc ou encore en bloc anonyme cette exception n'est pas levée (si vous apellez avec trop peu d'argument, il est fort probable qu'un autre exception soit levée, car les arguments manquant seront remplaçé par nil).
Proc#lambda? permet de savoir si le bloc se comporte comme s'il avait été déclaré avec lambda ou proc.
Proc.new { |i| i }.call # => nil Proc.new {}.call(2) # => nil lambda { |i| i }.call # ArgumentError: wrong number of arguments (0 for 1) lambda {}.call(2) # ArgumentError: wrong number of arguments (1 for 0)
Cela permet de choisir entre un objet Proc "strict" (qui réagit comme une Method) ou permissive. Tout ça est très bien documenté :
- "Changes in Ruby 1.9"
- rdoc de Kernel.lambda
- "Programming Ruby" (pp 343-345)
- "Ruby cookbook" (Recipe 7.1. Creating and Invoking a Block)
Instead of passing a block to a function, you pass it a lambda. This also makes it possible to pass multiple blocks to a single function, something that is not currently possible with Ruby.
Il est possible de donner un bloc de code, un objet Proc (qu'il soit construit avec proc, lambda ou Proc.new) ou une méthode définie avec def en paramètre d'une méthode. Il est également possible de donner plusieurs objets Proc à un méthode (ce qui est équivalent à donner deux bloc de code).
def justcall yield end def hellodef puts "Hello world" end helloproc = Proc.new { puts "Hello world" } hellolambda = lambda { puts "Hello world" } justcall &helloproc # "Hello world" justcall &hellolambda # "Hello world" justcall &(method :hellodef) # Hello world def doboth first, second first.call second.call end doboth lambda { puts "FIRST" }, lambda { puts "SECOND" }
TOO MANY SYNONYMS
- Enumerable#collect / Enumerable#map
- Enumerable#find / Enumerable#detect
- proc / lambda
- Array#length / Array#size
- ...
All these pairs do exactly (or almost exactly) the same. Why does Ruby have all these synonyms?
Parfois, le almost fait toute la différence. Retourner un Array vide ou nil est différent, et il arrive qu'on aie besoin/envie d'un comportement.
La nature dynamique de Ruby nous permet de changer les class et les méthode déjà définie. Si je redéfini Enumerable#find (comme le fait Rails), le comportement "standard" est toujours disponible avec Enumerable#detect.
Les autres langage choisissent une syntax pour une méthode (length(), size(), len(arg)) et t'obligent à retenir leur choix arbitraire. Ruby te laisse le choix d'utiliser celui que tu préfère.
INJECT? EH?
Ruby’s inject method is basically a reduce or fold function with a different name. Why pick a new name for a function that usually goes a different name?
Enumerable#reduce est un alias de Enumerable#inject (depuis Ruby 1.9) voir ici
So what should we do now?
Rien n'est pas parfait, mais avant de critiquer il faut être sûr de quoi on parle.
"A bon entendeur"