dimanche 27 avril 2008

MetaTricks : Un petit DSL

Depuis quelques temps, j'avais envie de tester s'il était facile ou non d'implémenter un petit DSL (ou Domain Specific Language) en Ruby. Il se trouve que plutôt, oui. Le code collé chez Pastie :


Le langage que je vous propose est loin d'être Turing-complet : ce n'est pas le but. Dans sa forme actuelle, il gère les transferts entre les "comptes" de plusieurs personnes grâce à une syntaxe en anglais. Trois verbes disponibles : donner, recevoir et avoir. Les phrases sont formées de la façon suivante :

"Sujet" "Verbe" "Somme" $ [complément]

Avec pour complément facultatif :

"Cibleur" "Second compte"

Il est possible de commenter une ligne en la commençant par "--" (comme en Ada !). Un "cibleur" ('to' ou 'from') désigne un autre compte. Attention, pour des raisons de blocage mental de ma part à l'approche d'une Regexp, la somme d'argent doit être un entier, et non un flottant. Pas de centimes pour les vrais. Ah, oui, et pas de point à la fin des phrases. Il parait que ça sert à rien(.)

Un exemple ?

-- Marie is cool:
Marie gives 5 $ to Peter
Marie has 10 $
Marie receives 15 $ from Peter
Peter gives 10 $ to Simon because he previously borrowed him...
-- Marion is Marie's dark-twin...
Sobe receives 1 $ from Marion
-- But Sobe's a gangsta who gat connectionz so:
Sobe receives 100000 $ like this !


est valable, et donnera :

--------------------
-= Report =-
--------------------
Number of accounts: 5
Marie owns 25.0 $.
Peter lost 20.0 $.
Simon earned 10.0 $.
Sobe earned 100001.0 $.
Marion lost 1.0 $.

C'est loin d'être parfait, mais le corps de l'interprète fait à peine 200 lignes. Je suis juste un peu déçu de ne pas avoir davantage employé les outils de la métaprogrammation : je passe sans doute trop par de l'analyse de chaîne de caractère, opération en soit risquée.

Tout commentaire ou critique vivement apprécié(e).

mercredi 23 avril 2008

Ruby : Sécuriser la redéfinition de méthode

Etant un peu angoissé de nature, il y a un truc qui me tracasse en Ruby depuis quelques temps : l'absence de warning à la redéfinition de méthodes existantes. Un exemple ?




class Object
def self.new
puts "Oh dude !"
end
end

a = Array.new 46
#=> **Judgement Day**


Ceci devrait bien vous convaincre de l'utilité d'avertir le programmeur durant le développement. Rappelons aussi que Object.methods dénombre 77 méthodes pour cette racine de tout objet en Ruby...

Du coup, voilà un petit trick qui permet de sécuriser ce point, grâce à la méthode de classe method_added :


class Object
def self.method_added sym
if self.methods.include? sym.to_s then
print "WARNING: method #{sym}" + \\
"of class #{self} redefined\n"

end
end
end

class Integer
def clone
puts "I'm your dark twin !"
end
end
#=> WARNING: method clone of class Integer redefined

lundi 7 avril 2008

MetaTricks : Ciseaux pour enfants

Les 28 et 29 Mars derniers a eu lieu la MountainWest RubyConf 2008, une série de présentations sur la programmation en Ruby. Il y a beaucoup de conférences de Rubyistes et de Railistes à travers le monde (certains en sont d'ailleurs agacés...), mais celle-ci me semble avoir été particulièrement riche, avec des sujets très intéressants : DSL, cœur du langage, écosystème général, et... métaprogrammation.

Je n'ai pas la chance d'avoir été sur place mais, miracle du web oblige, les vidéos des différentes présentations sont disponibles (en bonne voire très bonne qualité, avec les slides en direct) à cette page. Merci Confreaks !

La présentation de Giles Bowkett est particulièrement réussie :


Giles Bowkett, dont j'ai régulièrement parlé ici (générateur de MIDI en Ruby, métaprogrammation...), y parle bien sûr de métaprog' et de génération "automatique" de code, le premier étant un outil pour le second.
Il est assez difficile de résumer ce type de présentation en quelques mots, et je vais donc lancer ça en vrac :

  • Archimède et mathématiques par les cercles en Grèce.
  • Les Rubyistes pratiquent des "langages exotiques" : Scala, Erlang, Haskell, Lisp, Io, Scheme, Smalltalk... mais ne parle pas Grec !
  • De Pi "The Circle" à Lambda "The Ultimate" en LISP.
  • 3 piliers improbables : Rubinius, Nodebox et "code == data".
  • Ruby2ruby
  • "Monkeypatch Monkeypatching is Metamonkeypatching"
  • Avantages économiques de la génération de code.
  • Pub pour "Code Generation in Action"...
  • De Java à Rails en passant par LISP (et Perl ! (et Smalltalk !)).
  • "Skilled programmers can write better programmers than they can hire."
Avec de l'humour absurde au milieu, du code en live et de la bonne humeur... Très agréable à regarder. Les deux seuls reproches que je pourrais faire sont d'une part qu'il passe un peu trop vite (à mon goût) de l'"historique" à la pratique, et d'autre part que les domaines d'applications proposés en exemple ne me paraissent pas assez ambitieux, surtout venant de lui ; G. Bowkett n'étant pas le dernier des n00bs : parmi ses petites réalisations persos on trouve autre chose que des applis en Rails : API pour le robot Lego, générateur de MIDI, programmation graphique, diverses gems... visitez son site ! (ou retrouvez les autres présentations ici.)

mercredi 2 avril 2008

Une classe pour une fonction

Une petite idée comme ça : plutôt que d'appeler directement une fonction, comme ici (loi des gaz parfaits) :

def compute_p v, n, t
r = 8.314
p = (n*r*t)/v
return p
end
puts "p = #{compute_p(1.0, 10.0, 273.5)}"


Pourquoi ne pas créer une classe, représentant cette fonction ? En effet, on peut alors définir un certain nombre d'options à l'exécution "internes" à notre fonction (ici, j'ai mis des exemples simples : print pour un affichage console, et clock pour retarder l'exécution d'un temps donné). On pourrait aussi sécuriser les arguments (non fait ici...) par rapport à des contraintes internes à la classe (ex : température > 0, autre ?) ou décider de remplacer certains arguments sous certaines conditions, éventuellement externes. Et bien d'autres choses, pour des problématiques concurrentielles... Des idées ?


class Compute_p_class

# Initializing the arguments as attributes
def initialize args
@v = args[:v]
@n = args[:n]
@t = args[:t]
@@r = 8.314
@opt = []
@opt_args = {}
end


# To add an option at execution
def add_option option, argopt = nil
@opt.push option
@opt_args[option] = argopt
end

# Wait for @opt_args[:clock] s before executing
def option_clock
t_beg = Time.now
t = t_beg
if @opt.include? :clock then
while t < t_beg + @opt_args[:clock]
t = Time.now
end
end
end

# Verbose-like
def option_print
if @opt.include? :print then
puts "Executing function Compute_p"
puts "with arguments"
puts "v = #{@v}"
puts "n = #{@n}"
puts "t = #{@t}"
end
end

# Execution after options
def execute
# Options management
option_print
option_clock
# Function core
p = (@n*@@r*@t)/@v
return p
end
end

values = {:v => 1.0, :n => 10.0, :t => 273.5}
calcul = Compute_p_class.new values
calcul.add_option :clock, 5.0
calcul.add_option :print

puts "p = #{calcul.execute}"