Vous n'êtes pas connecté. Connectez-vous ou enregistrez-vous

La curryfication et Ruby

Voir le sujet précédent Voir le sujet suivant Aller en bas  Message [Page 1 sur 1]

1default La curryfication et Ruby le Mer 30 Mai - 5:26

Grim


Membre
Membre
La curryfication en Ruby



Cet article portera sur un point très particulier de Ruby (et traitera d'ailleurs d'un sujet qui n'appartient pas du tout à ce qu'il est indispensable de savoir en Ruby).




Introduction

La curryfication est une opération typique de la programmation fonctionnelle.
Dans cet article, nous allons aborder cette opération au moyen du langage de programmation Ruby, qui entre nous, n'est pas vraiment un langage où ce genre de procédé est important. Il s'agit surtout de curiosité.

Cet article aura donc pour objectif de répondre à une certaine curiosité même si je doute que la curryfication prenne une place importante dans vos projets (du moins avec Ruby).




Définition & explication

Avant d'aborder l'aspect historique de la curryifcation, il est important de voir une chose. Dans un langage comme Haskell, toutes les fonctions ne prennent qu'un seul et unique argument/paramètre. Ceux qui ne programme pas en Haskell seront outrés ! Un seul argument c'est bien trop peu ! Ceux qui connaissent un petit peu Haskell (du moins de vue), diront que ces faux et ceux qui connaissent diront oui. En effet, en Haskell, les fonctions ne prennent qu'un seul argument et cet argument est une fonction qui prend elle même en unique argument le paramètre suivant et ainsi de suite.

Code:
 f(g(h(i(x))))

Dans la programmation fonctionnelle, généralement, utiliser un espace entre 2 choses consiste à y appliquer une fonction, on peut donc considérer un espace comme un genre d'opérateur.
La curryfication est une opération qui permet donc de créer des fonctions pures (qui retournes toujours la même valeur pour les mêmes arguments).

Un peu d'Histoire
Je conclurai cette introduction en parlant de Moses Schönfinkel, l'inventeur de la curryfication et auteur d'un article sur la logique combinatoire, lui ayant attribué ce nom en référence au mathématicien Haskell Curry (partiellement contemporain à Moses Schönfinkel), que certains d'entre vous connaissent pour avoir poser les bases de la programmation fonctionnelle, le paradoxe de Curry (très amusant paradoxe permettant d'arriver à n'importe quelle conclusion à partir d'une phrase auto-référentielle et de quelques règles logiques, par exemple:
"Si cette phrase est vraie, alors le monstre du Memphrémagog existe.") ainsi que la correspondance Curry-Howard, un pont entre la théorie de la démonstration et l'informatique théorique qui a joué un rôle très important dans la logique.




Application & exemple d'utilisations

Tout ceci peut paraitre fort inutile mais nous allons voir des exemples très pratique d'utilisation. Exemples qui prouvent que la curryfication peut être un moyen très élégant pour résoudre certains cas très spécifique.
Prenons par exemple, une fonction dont le rôle est de multiplier 2 entiers entre eux (je suis d'accord que cet exemple n'est pas très original et je m'en excuse). Voici une solution OCaML:

Code:
 let multiplier x y =
      x * y;;

Cette fonction est relativement simple et se contente de prendre 2 arguments et d'en retourner leur produit.
Cette petite fonction nous amène à une des utilisation les plus courante (pour ne pas dire la seule) de la curryfication, l'application partielle ! Posons le cas très simple où, possédant une fonction multiplier, j'ai envie d'écrire une fonction multiplierPar2. Rien de plus simple, il suffit de l'écrire, identique à la précédente, sauf quelle ne prend qu'un seul argument et qu'elle retourne x*2:

Code:
 let multiplier x  =
      x * 2;;

Cependant, ce ne serait pas très intéressant !
Comme il a été expliqué dans l'introduction, une fonction peut prendre en argument une autre fonction et ainsi de suite. Donc il est possible d'appliquer partiellement nos arguments ! Exemple avec multiplier:

Code:
let multiplier x y =
      x * y;;
let multiplierPar2 =
      multiplier 2;;

Je viens de faire une application partielle de mes argument. En effet "multiplier 2" va se contenter d'attendre les arguments manquants.
D'ailleurs, en testant "multiplierPar2 6" le résultat sera bel et bien 12.
Dans le cas des fonctions d'ordres supérieures (fonctions qui prennent d'autres fonctions en arguments et qui dans certains cas retournent d'autres fonctions), ce genre d'opérations est vraiment (en plus d'être élégant) puissant.
Avec un peu d'imagination, il serait envisageable de définir une fonction avec moins d'arguments que ce dont elle a besoin et d'utiliser la curryfication pour palier ce manque explicite.

Ce qu'il faut retenir
Tout ce petit blabla peut se résumer en une phrase... "En appellant une fonction avec trop peux de paramètres; on crée, en quelque sorte, des fonctions à la volée.".
Retenez aussi que la programmation fonctionnelle est plus adaptée que les autres paradigmes car beaucoup de langage fonctionnels admettent la curryfication nativement (comme Haskell).

La curryfication appliquée a Ruby

Appliquer des arguments de manière partielle à une fonction écrite en Ruby est envisageable sous plusieurs forme. La plus instinctive serait de jouer avec le passage d'argument sous forme de tableau:

Code:
def ma_methode(*arguments)

Ou encore en utilisant le passage d'arguments sous forme de Hash (Dictionnaire pour les Pythoneux)

Code:
def ma_methode(hash)
    argument1 = (hash[:argument1] || valeur_par_défaut)
    argument2 = (hash[:argument2] || valeur_par_défaut)
end

Rappelons que la forme x || y vérifie si le membre x et null et attribue si oui, x si non.
Ce genre de méthode est assez peu formelle et nous verrons qu'il existe une autre méthode.

La curryfication, c'est une fonction qui prend comme argument une autre fonction etc.
J'ai souvent répété cette phrase et elle devrait faire tiquer certaines personnes. En effet, quel objet/élément de Ruby est parfaitement adapté à cette situation? Oui, c'est la Lambda/Proc.
En effet, en lisant la description de la classe Proc on peut découvrir une méthode associée à Proc qui est curry, les Procs permettent donc nativement de gerer la curryfication. Exemple:




Comme on peut le voir, il est impossible d'appeler la fonction au moyen de call avec un seul argument (alors que notre lambda en requiert 2). Essayons avec notre fonction curry.



Cela fonctionne parfaitement, notre multiplier.curry retourne bien une nouvelle fonction qui attend un argument en plus. Essayons notre argumentation partielles en créant, sur base de multiplier, une fonction multiplier_par_2. (Curry se contente de retourner un Proc/Lambda avec comme nombre d'arguments les arguments manquants donc multiplier.curry.call(x) revient au même que multiplier.curry[x] voir même multiplier.curry.(x)). Voici la fonction multiplier_par_2 obtenue grâce à une curryfication de multiplier:



Et voila, voici donc un Proc curryfié. Dans le cadre des fonctions d'ordre supérieur, ça peut être très pratique. Cependant, je m'en sert rarement (mais je ne fais pas énormément de Ruby :) ).




Conclusion

Je dois avouer que son intérêt (pour Ruby) est assez limité mais je trouvais qu'il s'agissait tout de même d'un aspect amusant et intéressant à développer.
Lié à certaines fonctions récursives, ou autre traitements spécifiques, la curryfication peut être un apport à la programmation Ruby. J'espère avoir au moins eu l'occasion de vous faire découvrir (ou redécouvrir) quelque chose d'amusant.
Merci beaucoup d'avoir lu cet article !

Plus récemment (soit après la rédaction de cet article), Raho/Ohar/HoRa/Haro (lol ça en fait des pseudos) a écrit un petit complément:





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 :

Code:
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:

Code:
line_proc = equation_line(1,2,2,21)
(0..150).each do | x |
        put "(#{x}, #{line_proc.(x)}"
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 :

Code:
a, b, c = 1, 2, 3
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.

Code:
def line_equation(sx, sy, cx, cy)
      a = (sy-cy)/(sx-cx)
      b = (sy - sx*a)
      fun = lambda{|ap, bp, x|ap*x + bp}
      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.

______________
+20 points d'aide ajouté par Skaaz pour partage de Tutoriel.

Voir le profil de l'utilisateur

2default Re: La curryfication et Ruby le Mer 30 Mai - 7:13

Venox


Fondateur
Fondateur
C'est vrai que c'est pas très utile à mes yeux, et surtout peu de personnes connaissent le ruby de nos jours, bref l'intention y est merci à toi du partage :)

Voir le profil de l'utilisateur

3default Re: La curryfication et Ruby le Mer 30 Mai - 18:40

Boby


Membre Royal
Membre Royal
Merci du partage, même si je l'avoue je n'ai rien lue -_- le ruby ne m’intéresse pas plus que ça ^^"


***********Signature************
- * Kick * No rebelle please
Voir le profil de l'utilisateur

4default Re: La curryfication et Ruby le Ven 8 Juin - 10:05

chaipokoi


Membre Actif
Membre Actif
j'ai tout lu, mais en fait... j'ai rien compris et pourtant je code en ruby de temps en temps, en fait , ce n'est pas tant tes explications qui me dérange mais euh... à quoi sert la curryfication ?

Voir le profil de l'utilisateur

5default Re: La curryfication et Ruby le Sam 9 Juin - 18:09

XHTMLBoy


Gardien de la taverne
Gardien de la taverne
Sans être parfaitement objectif, je pense que le dernier exemple indique un exemple très clair de contournement de problème de portée de variables mal définie.

j'ai rien compris et pourtant je code en ruby de temps en temps
Je pense qu'il faut un certain niveau d'application et de concentration pour comprendre cet article.

Voir le profil de l'utilisateur

6default Re: La curryfication et Ruby Aujourd'hui à 7:43

Contenu sponsorisé


Voir le sujet précédent Voir le sujet suivant Revenir en haut  Message [Page 1 sur 1]

Permission de ce forum:
Vous ne pouvez pas répondre aux sujets dans ce forum