Je m'excuse de pas avoir publié cet article hier ( le lundi ) comme le veut la coutume :) J'étais dans l'embarras du choix du sujet à aborder cette semaine. Et vu que je suis en train de développer une API pour un domaine assez "spécifique" qui a son propre jargon j'ai (enfin) eu une idée : comment écrire un jargon en Ruby.
Dans le monde de l'informatique un jargon est dit DSL.
Les DSL et Ruby
Ruby fournit quelques fantastiques fonctionnalités natives pour la création Domain Specific Languages (DSL). Un Domain Specific Language est comme un langage de programmation écrit pour un domaine déterminé. C'est une façon d'exposer les fonctionnalités dans unformat simple et lisible pour les autres programmeurs (ou vous-même). Un des DSL les plus couramment utilisés dans le monde Ruby est Sinatra.
require 'rubygems'
require 'sinatra'
get '/hello' do
"Hello world."
end
Sinatra est un DSL pour créer des applications web. Sa syntaxe est construite sur la base des verbes HTTP tels que GET, POST, et PUT. En exposant les fonctionnalités de cette manière, le code est beaucoup plus lisible et beaucoup plus parlant surtout quand on sait comment fonctionne le web en général.
un peu de Yield
yield est un concept très important à comprendre pour réussir à créer un DSL Ruby. La fonctionnalité fournie par yield permet à un développeur de faire passer le contrôle temporairement pour permettre l'éxécution d'un autre bout de code. Si vous avez déjà utilisé le Array#map (ou Array#collect) qui est un exemple intéressant de l'utilisation de yield.
[1, 2, 3].map{|i| i + 1} # => [2, 3, 4]
class Array
def my_map
resultat = []
self.each do |truc|
resultat << yield(truc)
end
resultat
end
end
[1, 2, 3].my_map{|i| i + 1} # => [2, 3, 4]
yield arrête essentiellement l'évaluation(éxécution) de la méthode et évalue le bloc passé, l'appelant avec les arguments fournis dans la déclaration de yield lui-même. Donc, si j'avais une méthode qui simplement retournait ce qu'on lui passe en arguments, ça ressemblerait à ça :
def confesser( argument )
yield argument
end
confesser(" Ruby c'est magnifique !") do |arg|
puts arg
end
# Sortie : "Ruby c'es magnifique !"
Yield et les DSLsMaintenant, en utilisant yield, nous avons les bases pour créer un DSL simple. Je sais pas pour vous mais moi j'ai faim ! Donc nous allons créer un DSL pour décrire les recettes de cuisine. Nous voulons être en mesure de créer des recettes, d'ajouter des ingrédients ainsi que les étapes, et avoir un joli affichage de tout ça. Au boulot now :
class Recette
attr_accessor :nom, :ingredients, :instructions
def initialize(nom)
self.nom = nom
self.ingredients = []
self.instructions = []
end
def to_s
affichage = nom
affichage << "\n#{'=' * nom.size}\n\n"
affichage << "Ingredients: #{ingredients.join(', ')}\n\n"
instructions.each_with_index do |instruction, index|
affichage << "#{index + 1}) #{instruction}\n"
end
affichage
end
end
Now ajoutons une recette :
plat = Recette.new("Omelette") plat.ingredients << "Oeufs" plat.ingredients << "beurre" plat.ingredients << "fromage" plat.ingredients << "autres trucs " plat.instructions << "Beurre dans poêle" plat.instructions << "ouefs ..." plat.instructions << "fromage ..." plat.instructions << "la suite certainement ..."
Now faisons un "puts plat"! ça doit donner :
Plat ==== Ingredients: Oeufs, beurre, fromage, autres trucs 1) Beurre dans poêle 2) ouefs ... 3) fromage ... 4) la suite certainement ...
Bien que cela fonctionne, le code ne semble pas être élégant! Nous avons besoin d'un moyen de faire ressembler à ce qu'on peut voir sur une carte de recette. Rendons le code un peu plus beau. Pour commencer, nous allons réécrire le constructeur et yield :
def initialize(nom) self.nom = nom self.ingredients = [] self.instructions = [] yield self end
Next, nous devons ajouter quelques méthodes pour ajouter des ingrédients et des instructions à la recette :
def ingredient(nom, options = {}) ingredient = nom ingredient << " (#{options[:quantite]})" if options[:quantite] ingredients << ingredient end def instruction(texte, options = {}) etape = texte etape << " (#{options[:pendant]})" if options[:pendant] instructions << etape end
Cela nous permet de créer une recette d'une manière beaucoup plus naturelle :
nouilles_au_fromage = Recette.new("Nouilles au fromage") do |r|
r.ingredient "Eau", :quantite => "2 verres"
r.ingredient "Nouilles", :quantite => "1 verre"
r.ingredient "Fromage", :quantite => "3 cuillères"
r.step "L'eau sur le feu", :pendant => "5 minutes"
r.step "Ajouter les nouilles.", :pendant => "6 minutes"
r.step "Enlever l'eau."
r.step "Mélanger le fromage aux nouilles."
end
"puts nouilles_au_fromage" donnera :
Nouilles au fromage =================== Ingredients: au (2 verres), Nouilles (1 verre), Fromage (3 cuillères) 1) L'eau sur le feu. (5 minutes) 2) Ajouter les nouilles. (6 minutes) 3) Enlever l'eau. 4) Mélanger le fromage aux nouilles.
Et voilà ! le petit déjeuner est servi.
Nous verrons dans la suite comment rendre ce DSL plus joli et plus proche du langages des chefs cuisiniers.
Conclusion :
Nous avons appris à écrire un DSL en Ruby et nous n'avons utilisé qu'une seule méthode ( Yield ). Dans la suite de cet article nous utiliserons d'autres merveilles de Ruby.
Bonne journée
Aucun commentaire:
Enregistrer un commentaire