samedi 26 mai 2012

Utilisation de Proc#curry

Avant propos
On m'a souvent fait remarqué que je ne faisait rien dans et pour Funkywork alors j'ai décidé d'écrire un micro-article qui tire partit d'une petite astuce présentée précédemment.
Sachez tout d'abord que cet article n'a pas pour objectif de présenter un fait, "une vérité absolue", mais une manière que je considère comme élégante (et potentiellement amusante) de résoudre certains problèmes.

Contenu de l'article
Ce billet présentera une manière d'utiliser la méthode Curry de la classe Proc, car comme Pierre en avait parlé dans son article, on en voit rarement l'intérêt (cf : L'article en question ).
Cette manière n'est certainement pas la seule, ni même la meilleure, mais elle a au moins le mérite d'être originale.

Contextualisation
Pour un projet "divers", j'ai du manipuler des projectiles. Il existe plusieurs manière de déplacer un projectile, j'ai choisis d'utiliser une fonction.
L'équation d'une fonction linéaire est définie comme ceci : f(x) = ax + b (sans rentrer dans les détailles et les informations de précisions).
L'objectif de ma fonction et de trouver la valeur de "y" pour une fonction définie par 2 points.
Pour cela, rien de bien compliqué, il suffit de trouver "a" et "b" avec les données qu'on nous donne. Soit :

  • a = (source_y-arrivee_y)/(source_x-arrivee_x)
  • b = source_y - source_x*a
Ce calcul n'est évidemment pas très compliqué à réaliser (et à trouver), cependant, je voulais ne pas avoir a répéter mes données source_x/y et arrivee_x/y à chaque utilisation.
J'ai donc choisi de retourner une fonction anonyme.

Implémentation
Pour avoir une fonction facilement utilisable, j'ai décidé d'utiliser les fonctions d'ordres supérieurs, soit des fonctions qui peuvent prendre des fonctions en argument et/ou retourner des fonctions. 
Un exemple d'utilisation serait:
  1. line_proc = equation_line(1,2,2,21)
  2. (0..150).each do | x |
  3.         put "(#{x}, #{line_proc.(x)}"
  4. end
(Afficher toutes les coordonnées de 0 à 15 pour une fonction linéaire passant de (1,2) à (2, 21))

Comment procéder (en utilisant la Curryfication) ? L'idée de la fonction est donc de retourner un Proc/une Lambda qui appliquerait la fonction trouvée (en calculant "a" et "b") à une valeur d'entrée.

Un problème de portée?
Je ne suis pas un spécialiste du Ruby et certains gourous pourront me contredire, mais j'ai vraiment du mal à percevoir la portée des variables, principalement dans le cas des "blocks". 
Par exemple : 
  1. a, b, c = 1, 2, 3
  2. l  = lambda{|x|a + b + c + x}
Est-ce qu'on peut utiliser a, b et c comme des variables globales (dans le cas présent) ?
Même si cet exemple est admissible, je trouve (et ce n'est que mon avis), que ce n'est pas très "joli".

Une autre idée aurait été de calculer les valeurs "a" et "b" dans la fonction que je retourne, le soucis c'est qu'un calcul qui ne devrait être exécuté qu'une seule fois devra être, pour chaque "y" demandé, recalculé. 
C'est pourquoi, la conclusion qui s'est naturellement offerte a moi est : "Passons "a" et "b" en argument !".
Le problème c'est qu'en dehors de ma fonction, je ne connais pas "a" et "b". 
La curryfication nous permettra donc de retourner une lambda a qui nous avons déjà passé 2 arguments, en l'occurrence "a" et "b". Voici une petite implémentation.
  1. def line_equation(sx, sy, cx, cy)
  2.        a = (sy-cy)/(sx-cx)
  3.        b = (sy - sx*a)
  4.        fun = lambda{|ap, bp, x|ap*x + bp}
  5.       return fun.curry.(a, b)

Conclusion
Je suis parfaitement d'accord pour dire que l'utilité de cet article est fortement discutable, cependant, je trouvais ça amusant de présenter une utilisation concrète de cette méthode.
De plus, elle permet d'être très fiable quant aux règles de portées de variables (domaine Ô combien flou pour les êtres limités comme moi dans la programmation) et aussi parce que c'est rigolo d'avoir du curry dans son code.

Merci de votre lecture. 
Raho.

2 commentaires:

  1. hello
    je sais que maintenant on optimise plus ce genre d'algo, mais à mon époque neoproctique où chaque cylce comptait, on utilisait un autre algo qui n'utilise pas de variable réél, ou float, ou double. Mais des entiers, après tout un ecran de jeu est fait de pixel donc n'est pas linéaire
    Le principe est simple:
    Soient 2 points A(xa,ya) et B(xb, yb) et ton projectif P(xp,yp) quiu veux aller de A vers B
    Calcul de DeltaX = xb-xa
    Calcul de DeltaY = yb-ya
    xp = xa;yp=ya
    Si abs(DeltaX)>abs(DeltaY) alors
    counter=abs(DeltaX)
    for xp = xa+1;xp++;xp<ya
    counter-=abs(DeltaY)
    if counter<0
    counter+=abs(DeltaX)
    yp += DeltaY<0 | -1 : +1
    endif
    sinon..pareil en inversant y et x

    Bon voilà c'est un truc vite fait pour capter le principe. en gros des entiers et un compteur de pente :)

    RépondreSupprimer