mardi 29 décembre 2009
Ruby, un langage para-fonctionnel ?
mardi 29 décembre 2009 - Science
Ruby est un très joli langage. Un des plus jolis que j'ai vus, Lisp compris. Très concis et bizarrement très lisible, deux adjectifs rarement réunis dans un langage de programmation.
Et pourtant il ne dispose pas de fonctions à proprement parler. On parle bien de méthodes, de blocks, de lambdas, de procs mais jamais de fonctions. Et c'est bien sa force : au lieu de nous servir un seul type de code paramétrable, Ruby nous en propose 4.
On connait déjà les lambda du lambda-calcul, en Ruby un lambda est construit à partir d'un block :
add = lambda { |x,y| x + y }
add(1,2)
=> 3
Ce qui m'amène à parler des blocks.
En Ruby un block n'est pas utilisable seul, il faut plutôt le voir comme un bout de code utilisable ailleurs, pour construire un lambda ou autre chose, donc qui peut intervenir au moment de la "compilation". Du code parsé mais pas compilé utilisable pour construire d'autres éléments du programme, cela fait fortement penser aux macros et à la meta-programmation. Ça commence à me plaire !
Ensuite Ruby propose quelque chose qui ressemble aux lambdas mais qui diffère sensiblement et qu'on appelle Proc.
add = Proc.new { |x,y| x + y }
La différence est que Proc ajuste ses arguments en remplaçant ceux qui manquent par nil et en enlevant ceux en trop, et qu'elle ne compte pas comme un appel de fonction sur la pile : return dans une Proc fait sortir de la fonction qui l'appelle !
Finalement il y a les méthodes. Ruby est un langage très orienté objet car la plupart du code se retrouvera en fait dans des méthodes appartenant à un objet, lui-même appartenant à une classe, qui est elle-aussi un objet. (Comme les objets du Common Lisp sauf que les méthodes ne se spécialisent que sur un objet, qu'il n'y a qu'une méta-classe, et... bref ça fait moins le café.) Une méthode est donc rattachée à un objet, mais c'est loin d'être tout.
En plus de ses arguments, on peut passer un block (et un seul) à une méthode. La méthode peut alors l'appeler comme une fonction normale avec yield.
def sort_args(x, y)
x, y = y, x if y < x
yield(x, y)
end
sort_args(4, 2) { |a, b| b / a }
=> 2
Ce qui est marrant c'est qu'on peut nommer ce block et le convertir en un objet Proc utilisable ailleurs dans le programme :
def set_hook(arg, &block) @hook = block end