Archives pour la catégorie Développement

La pente est raide mais la route est longue

Vu tout ce à quoi peut servir python, pas facile de trouver un «nouveau» langage. J’ai jeté mon dévolu sur rust, histoire de voir s’il se passait enfin quelque chose d’intéressant côté langages compilés, vu les échecs totaux de C++, D, go et consorts. Je fais un grand sac, hein, je peux rajouter C# et Java si on me chauffe un peu. Mais là, je me tiens bien, les oreilles couchées, bien droit sur ma chaise, la raie de côté.

Et que dire si ce n’est que, apprendre le rust, ça me permet de prendre des coups de pelle plein fer à un rythme assez soutenu. Et pas que à cause des points-virgules ! Ne me lancez pas sur les points-virgules. S’il-vous-plaît.

Mon projouet : une classe «vecteur à deux dimensions»

Pour résumer : un machin qui contient un x et un y, qu’on peut additionner ou modifier par addition, pour utiliser sur codingame. En Python, ça donnerait ça :

class V2:

  def __init__(self, x, y):
    self.x = x
    self.y = y

  def __add__(self, autre):
    return V2(self.x + autre.x, self.y + autre.y)

Ce n’est pas avec ça qu’on révolutionnera les Internets, mais bon, il faut bien commencer quelque part. Pour l’histoire, mon premier projouet était un conteneur générique à références croisées ; force est de constater que j’avais été un peu trop ambitieux. Je n’ai plus fait de rust pendant six mois.

La gifle : choisir un type de base

Alors, hardi petit, et on tapote en rust :

struct V2 {
  x: usize,
  y: usize,
}

Le connaisseur en gestion de configuration appréciera la virgule à la troisième ligne.

- Ha ! Pourquoi usize et pas u64 ?

- Disons, l’expérience professionnelle.

- Oui mais, des fois on va faire des calculs dans tous les sens, on va perdre de la précision partout, c’est trop horrible. Faudrait faire une classe patron, comme ça je dirai en fonction des usages si j’ai besoin d’entiers ou de flottants.

- Tout doux, Bijou ! Les années 2000 sont terminées. Bataille pas, colle des flottants partout, on verra plus tard.

- Oui mais…

- Oui mais rien du tout, tu fais ce que je dis, on est la même personne, je te rappelle.

- Bon.

Alors, je m’obéis, et je change :

#[derive(Debug)]
struct V2 {
  x: f64,
  y: f64,
}

Bon. J’ai déjà envie d’une douche, mais je sais que ce genre de choix est inhérent au type du langage. il faut choisir. L’annotation initiale (« [derive(Debug)]« ), c’est pour pouvoir afficher le contenu de mes objets histoire de déboguer à la console comme un cochon, hein. Pas d’inquiétude. Ou presque.

La béquille : surcharger un opérateur arithmétique

Surcharger les opérateurs arithmétiques, mais pourquoi faire ? En premier lieu : du code lisible. Alors, je me paluche la documentation, et finis par tomber sur les traits «std::ops». C’est ça qu’il me faut ! Je tape :

impl std::ops::Add for V2 {
  type Output = V2;
  fn add(self, autre: V2) -> V2 {
    V2 { x: self.x + autre.x, y: self.y + autre.y }
  }
}

Bon, ça a l’air facile comme ça, mais une lecture attentive nous montre que ça ne va pas marcher. Si par exemple, j’utilise tout ça dans une belle fonction principale :

fn main() {
  let v1 : V2{ x: 15.0, y: -3.0 };
  let v2 : V2{ x: -4.0, y: 27.0 };
  let v3 = v1 + v2;
}

Jusqu’ici, tout va bien, mais si  je rajoute juste avant la fin :

  println!("{:?}", v1);

Et bien paf ! ça ne compile pas, et pourquoi ? Parce que la propriété de v1 a été transférée à la fonction d’addition, donc n’est plus disponible. C’est logique, c’est le langage qui veut ça. Pour que ça, ça marche, j’aurais du écrire :

  let v3 = &v1 + &v2;

C’est bien ce que je veux : utiliser v1 sans le modifier, pour pouvoir faire mon calcul. Du coup, l’opérateur que je dois implémenter n’est pas une addition à base de valeurs, mais de références. Et donc, en m’inspirant de BigInt, je rajoute, attention les yeux :

impl<'a, 'b> std::ops::Add<&'b V2> for &'a V2 {
  type Output = V2;

  fn add(self, autre: &V2) -> V2 {
    V2::new(self.x + autre.x,
            self.y + autre.y)
  }
}

Ça a l’air compliqué comme ça (bouh !), mais ça ne fait que l’addition de deux références aux cycles de vie différents. J’ai donc deux opérations d’addition :

- une entre deux valeurs (qui seront donc définitivement perdues ensuite) ;

- une entre deux références.

Je pourrais aussi avoir besoin d’autres variantes pour les fois où je ferais des trucs plus compliqués, comme additionner trois vecteurs sur une seule ligne : une addition entre une valeur et une référence, et entre une référence et une valeur. Soit deux surcharges supplémentaires :

// &v1 + v2
impl<'a> std::ops::Add<V2> for &'a V2 {
  type Output = V2;

  fn add(self, autre: V2) -> V2 {
    V2 { x:self.x + autre.x,
         y: self.y + autre.y }
  }
}

// v1 + &v2
impl<'b> std::ops::Add<&'b V2> for V2 {
  type Output = V2;

  fn add(self, autre:&V2) -> V2 {
    V2 { x: self.x + autre.x,
         y: self.y + autre.y}
  }
}

Ouf ! Ça commence à faire des kilomètres de code pour pas grand chose… La robustesse du logiciel a un prix : le voici ! Sans compter qu’on a l’impression de retomber dans le C++, ambiance référence sur objet temporaire et compagnie… Il doit y avoir moyen de faire plus simple, parce que sinon, quand il faudra rajouter les autres opérations, je vais décéder ! J’ai cru voir tout un système de macros, on ira voir plus tard.

La variante : le poisson chinois

Je viens tout de même d’écrire quatre variantes pour un même opérateur, soit quatre sources de bogues assez subtiles, opérateur que j’ai favorisé sur une banale méthode pour écrire «du code lisible», et j’en viens à devoir écrire «v3 = &v1 + &v2» pour écrire du code correct et sûr. Comme une impression d’avoir laché la proie pour l’ombre, pas vous ? Mais il y a une autre option : le trait «std::marker::Copy». Qu’est-ce ? Si je suis près à faire une croix sur l’unicité des références aux instances, tout est plus simple. En dérivant ce trait, seule la première implémentation naïve de l’addition suffit.

Fort bien, mais qu’est-ce que le trait Copy ? De ce que j’en comprends, une directive de compilation (puisqu’il n’y a rien à implémenter de spécifique) pour dire que toutes les copies physiques d’une instance sont équivalentes. Est-ce que du coup le compilateur produit effectivement des copies quand j’écris «v3 = v1 + v2» ? Mystère. Je sens que ce simple trait est un sujet de thèse à part entière. Alors j’assume : je ne le dérive pas, et j’implémente mes quatre variantes de la même addition en attendant d’en savoir plus.

Un coude, une cloison nasale : deux raisons de saigner

Maintenant qu’on titube de douleur, on se dit : «Tiens, et si je mettais mes vecteurs dans des conteneurs ? Ça pourrait arriver, dans la vraie vie.» Ni une, ni une, je tapote :

let mut s = std::collections::HashSet::new();
s.insert(v3);
println!("{}", s.len());

Las ! Je reprends une mandale : comment voulez-vous distinguer deux instances si vous ne dites pas comment les hacher ? Et le trait «std::hash::Hash» alors ? Bon, je ne peux pas franchement donner tort au compilateur. Allons au plus vite : dérivons le trait standard :

#[derive(Hash)]

Le compilateur n’en veut pas : le type «f64» n’est pas hachable par défaut. J’imagine que c’est à cause de la norme IEEE-754 et de ses classes d’équivalence. J’ai donc le choix entre écrire comment hacher un «f64», mais ça risque d’être valable pour tout le programme, soit limiter ma façon de hacher du f64 aux bornes de mon vecteur. Je choisis cette dernière façon de pourrir le code, et j’implémente explicitement le trait std::hash::Hash plutôt que de le dériver :

impl std::hash::Hash for V2 {
  fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
    self.x.to_bits().hash(state);
    self.y.to_bits().hash(state);
  }
}

En passant par la représentation physique (avec «f64::to_bits», j’obtiens un entier parfaitement hachable ! C’est moche mais c’est isolé, alors ce n’est pas grave. Bon, ça passe presque : maintenant qu’on sait faire de la bouillie de vecteur, il faut aussi introduire une relation d’équivalence (le trait std::cmp::Eq). Ce n’est pas moi, hein, c’est le compilateur, n’oubliez pas. Histoire d’assurer que deux instances équivalentes aient même haché (cf. ). N’imaginez même pas dériver du trait Eq ! J’imagine que c’est pour les mêmes raisons que f64 n’est pas hachable. Par contre, on peut au moins dériver le trait std::cmp::PartialEq, ce qui est déjà pas mal. Bref, il ne reste qu’à rajouter :

#[derive(PartialEq)]
…
impl std::cmp::Eq for V2 {
}

Et là, enfin ! Ça compile, ça se lance, ça ne fait pas n’importe quoi ! Il serait temps me direz-vous. Je peux même insérer deux fois v3, ça passe… Enfin, deux références à v3, bien sûr.

[…à suivre…]


Python – comparaison lexicographique efficace

Vu que j’ai mis un peu de temps à trouver comment faire, je me dis que ça mérite d’être inscrit au fronton du mausolée des bonnes idées.

Soit une classe Python Cost définie comme suit :

class Cost(object):
   def __init__(self):
      self.write = 0
      self.exec = 0

Comment y adjoindre efficacement les opérateurs classiques de comparaison, c’est-à-dire en écrivant le moins de code possible (très important !) et en faisant en sorte qu’elle coûte le moins cher possible à l’exécution ? On prendra l’exemple d’une comparaison lexicographique classique, celle-là même qui fait que :

(1, 2) < (1, 3)
"abc" >= "aac"

Aparté pour technologue : on pourra utiliser cette même classe Cost pour évaluer les différentes solutions, on est en pleine auto-référence, c’est trop génial !

Les comparaisons en Python

La documentation de python (« pydoc \< » dans votre interpréteur de commande favori) nous apprend qu’il y a deux stratégies pour les opérateurs de comparaison :

  • les surcharger un par un. Il faudra donc écrire le comportement de <, >, <=, >=, == et !=, en espérant que ces six implémentations soient homogènes. Bonne chance. Le plus simple dans ce cas est d’en implémenter un seul, et de définir les autres plus par rapport aux attributs, mais par rapport à cette implémentation de référence. Par exemple, on peut définir les cinq autres opérateurs à partir de <:
a <= b : return not (b < a)
a >  b : return a < b
a >= b : return not (a < b)
a != b : return a < b or b < a
a == b : return not (a < b or b<a)
  • surcharger un opérateur unique faisant ce qu’il est communément appelée une trichotomie : il renvoit une valeur numérique entière qui est négative quand a est plus petit que b (typiquement, -1), nulle quand ils sont égaux, et positive sinon (tout aussi typiquement, 1). Il n’y a alors qu’une méthode à écrire, et l’interpréteur Python se charge de câbler les opérateurs usuelle sur cette implémentation de référence.

Il est bien entendu évident qu’on va utiliser la stratégie de l’opérateur unique, si on trouve un moyen d’écrire ce même opérateur efficacement.

Rédaction efficace de la trichotomie

Une première implémentation naïve de la trichotomie serait la suivante :

class Cost(object):
   ...
   def __cmp__(self, other):
      if self.write < other.write: return -1
      elif self.write > other.write: return 1
      else:
         if self.exec < other.exec: return -1
         elif self.exec > other.exec: return 1
         return 0

C’est un peu verbeux, et on s’y prend mal : pourquoi n’utiliserait-on pas aussi ce même opérateur cmp pour nos propres attributs ? Recommençons.

class Cost(object):
   ...
   def __cmp__(self, other):
      test_write = cmp(self.write, other.write)
      if test_write != 0: return test_write
      test_exec = cmp(self.exec, other.exec)
      return test_exec

C’est déjà plus concis (ce qui est arbitrairement impératif) Mais peut-on faire mieux ? Se passer des variables temporaires tout en garantissant que la comparaison de l’attribut ‘exec’ des deux instances ne soit effectuée que si nécessaire, donc quand les attributs ‘write’ sont égaux ?

Les opérateurs logiques de Python (and, or, etc.) ont l’aimable particularité d’être semis-stricts : l’expression à leur droite n’est évaluée que si l’expression à leur gauche ne permet pas, seule, de déterminer le résultat de l’opération. Pour e1 and e2, ça signifie que e2 n’est évalué que si e1 est vrai. Ils sont donc des candidats de choix pour nous aider à avoir une écriture encore plus concise.

class Cost(object):
   ...
   def __cmp__(self, other):
      return cmp(self.write, other.write) \
          or cmp(self.exec, other.exec)

Et c’est là que la magie de Python opère (ne soyons pas chiens, le comportement serait identique en C) : X or Y renvoit X si X est jugé vrai, Y sinon. Et c’est bien ce que l’on veut ici : le résultat de la première comparaison si elle est déterminante, celui de la seconde sinon. Encore une fois, c’est génial !

Conclusion

La méthode est scalable et peut-être étendue à peu de frais à un troisième, un quatrième attribut, voire plus.

Tags: , ,

Dériver d’un projet CVS pour en héberger sa propre version GIT

Chu-Chu Rocket! Voilà un jeu de puzzles qu’il est bien ! Encore faut-il pouvoir encore y jouer. À l’époque, c’était sur Game Boy Advance que ça se passait. Joie dans mon coeur, il en existe un émulateur : Visual Boy Advance, hébergé sur sourceforge.net, plus maintenu depuis 5 ans. Les binaires disponibles sont compatibles 32 bits, les sources eux font des raccourcis sur la taille des pointeurs… De toute évidence, si on veut pouvoir libérer les souris du joug des chats tyrans, il va falloir mettre les mains dans le cambouis. Mais comment reprendre la main sur ce projet ?

Aspirer une sauvegarde du dépôt CVS depuis Sourceforge.net

La FAQ de sourceforge.net est très claire là dessus, il n’y a aucun mystère ; une seule commande suffit :

rsync -av rsync://vba.cvs.sourceforge.net/cvsroot/vba/* .

Et nous voilà avec le beau répertoire VisualBoyAdvance qui contient toute la sauvegarde du projet original, format CVS.

Extraire du dépôt CVS local les informations nécessaires à l’initialisation du dépôt GIT

La documentation de la fondation Apache aide également :

cvs2git --blobfile=cvs2svn-tmp/git-blob.dat --dumpfile=cvs2svn-tmp/git-dump.dat --username=cvs2git VisualBoyAdvance

L’outil cvs2git étant une surcouche d’un autre outil, cvs2svn, je ne suis pas sûr qu’on ait une quelconque liberté pour la valeur à donner aux options blobfile et dumpfile. Dans tous les cas, on s’en moque un peu.

Monter un dépôt GIT de toute pièce

Toujours en lisant l’aide précédente :

git init --bare VisualBoyAdvance.git
cd VisualBoyAdvance.git
cat ../cvs2svn-tmp/git-*.dat | git fast-import

Et voilà ! Reste plus qu’à mettre les mains dans le cambouis, affaire à suivre (peut-être).

Tags: , , ,

Fedora – configurer Emacs pour la coloration syntaxique du OCaml

Chose amusante, je n’ai pas réussi à obtenir la coloration syntaxique du OCaml grâce à l’a-priori bien nommé paquetage « ocaml-emacs ». Peut-être n’est-ce tout simplement pas son rôle.

Par contre, le paquetage « emacs-tuareg » y réussi plus ou moins, avec quelques lacunes, mais c’est déjà mieux que rien. Pour que cela fonctionne, ne pas oublier d’ajouter à son .emacs favori :

;;; OCaml

(setq auto-mode-alist
 (append '(("\\.ml[iyl]?$" .  tuareg-mode ))
 auto-mode-alist))

(autoload 'tuareg-mode "tuareg.elc" "Mode for OCaml programs" t)

 

Tags: , ,

Apprentissage du OCaml

Comment apprendre un langage de programmation d’une manière générale ? Lire la documentation, c’est bien, mais rapidement, le besoin se fait sentir de pratiquer. Et pour ça, il faut avoir des sujets futiles mais variés pour créer des mini-programmes permettant de balayer les différents concepts. Pour apprendre Python, je m’étais inscrit à SPOJ, une sorte de concours de programmation permanent mettant à disposition des milliers de sujets et un robot qui évalue la qualité de votre programme-solution en le soumettant à des entrées qui ne vous sont pas fournies. L’intérêt est double : en plus d’apprendre un langage, cela vous pousse à prendre en compte les cas limites, ce que tout bon programmeur à tendance à oublier de prime abord.

Mais voilà, si le premier sujet s’apparente à un classique « Hello World! », le second nécessite l’implémentation d’un crible d’Ératosthène efficace, ce qui n’est pas franchement trivial. Donc, pour l’apprentissage progressif, on repassera.

J’étais tout à mon « Hello World! » en OCaml quand je me suis dit qu’il devait exister des sites dédiés à l’apprentissage d’OCaml. Et j’ai trouvé ça : « 99 problèmes en OCaml » (en anglais). C’est l’adaptation d’un classique du Lisp, une liste de problèmes à la difficulté croissante permettant de se familiariser avec les paradigmes des langages fonctionnels. De plus, on y trouve la correction de certains problèmes, et d’autres sont encore recensées ici.

Je vous encourage à lire le lien du dernier article sur les exceptions et leur inocuité dans 99% des cas, ça résume tout à fait ce que j’en pense.

Tags: