Composition

En fait en Caml, on fait simplement la composition

h = (compose f g) est la fonction x->f(g(x)).
Je me demandais comment faire ça simplement en C, je vois bien comment faire une fonction

int compose(f,g,x) int (*f)(); int (*g)(); int x; { return((*f)((*g)(x))); }
mais sauf à faire une structure un peu compliquée, je ne vois pas comment faire simplement une fonction h valant fog,
h=compose(f,g);
Sauf à faire une structure arborescente et une sytème d’évaluation adaptée, je ne crois pas que ça soit faisable, je me trompe?

C’est un mécanisme qui me fait vachement penser à de l’objet.

Tu déclare une classe qui possède deux données membres (tes pointeurs sur fonctions) et un fonction qui va simplement exécuter l’une après l’autre chacune de tes deux première fonction.

Ensuite à la déclaration de chaque objet tu lui donne tes deux fonctions et tu appelle ta fonction membre sur tes entiers/réelles/etc.

Donc je sais que c’est possible en C++ (avec de la généricité tu dois encore pouvoir améliorer la chose. Probablement en Objective-C.

Mais en C, mis à part avec un functor (que je connais très mal), je ne vois pas.

j’ai réflechi aussi à une directive préprocesseur, mais il faut aussi la variable dans la formule:

Il est possible de faire une structure:

structure fonction { int type; struct *fonction; int (*f)(); }

si g est une telle structure, si g.type=0, eval(g,x) renvoit x sinon eval(g,x) renvoit (g->f)(eval(g->fonction,x)), ça marche mais ça n’est pas terrible, c’est mon usine à gaz… Je ne pense pas qu’il y ait plus simple.

Salut,

Ce genre de soucis m’intéresse également et je me suis donc renseigné de mon côté. L’on m’a conseillé de stocker les pointeurs sur fonction dans une structure, j’en ai tiré ceci. C’est sans doute plus long mais l’utilisation me semble relativement simple.

[code]typedef struct s_comp
{
int (*f)();
int (*g)();
} t_comp;

void set_compose(t_comp *c, int (*f)(), int (*g)())
{
c->f = f;
c->g = g;
}

int do_compose(t_comp *c, int i)
{
return (c->f(c->g(i)));
}[/code]

Et un petit exemple d’utilisation pour la route :

[code]int f(int i)
{
return (++i);
}

int g(int i)
{
return (i + 5);
}

int main()
{
int i;
t_comp c;

i = 0;
set_compose(&c, &f, &g);
printf(“i=%d\nfog(i)=%d\n”, i, do_compose(&c, i));
return (0);
}[/code]

@Tycho Brahe> C’est marrant c’est la version non-objet de ce que je proposais :slightly_smiling:

En effet, la seule différence est dans la conclusion :stuck_out_tongue: Je devais être sacrément crevé pour oublier un post en quelques heures.

Voilà ce que donne un peu de programmation fonctionnel en C mais c’est du bricolage, recursif(estnul,fact_zero,moins_un,ex_fact,n) calcul n! uniquement à l’aide des 4 fonctions utile pour la récursion, eval(compose(moins_un,&Moins_un),15) calcule la composée de moins_un avec moins_un sur 15. Quand on voit la facilité avec laquelle ça s’écrit en Caml, on voit la différence entre les langages…

[code]#include <stdio.h>
#include <unistd.h>
#include <malloc.h>

/* structure d’une fonction */

struct fonction {
int type; /* si = 1, c’est f, sinon c’est f o suite */
struct fonction *suite;
int (*f)();
};

/* fonctions définissant factorielle */
int estnul(x)
int x;
{
return(x==0);
}

int fact_zero(x)
int x;
{
return(1);
}

int moins_un(x)
int x;
{
return(x-1);
}

int ex_fact(x,y)
int x;
int y;
{
return(x*y);
}

/* evaluation d’une fonction en x */
int eval(g,x)
struct fonction *g;
int x;
{
if (g->type) return((g->f)(x));
else return((g->f)(eval((g->suite),x)));
}

/* composition */
struct fonction *compose(f,g)
int (*f)();
struct fonction *g;
{
struct fonction *res;
res=(struct fonction *)malloc(sizeof(struct fonction));
res->type=0;
res->suite=g;
res->f=f;
return(res);
}

/* fabrication d’une fonction récursive avec les 4 fonctions */
int recursif(t,g,h,ex,x)
int (*t)();
int (*g)();
int (*h)();
int (*ex)();
int x;
{
if ((*t)(x)) return((*g)(x));
else return((*ex)(x,recursif(t,g,h,ex,(*h)(x))));
}

/* essai */
main(argc,argv)
int argc;
char **argv;
{
struct fonction Moins_un;
struct fonction Estnul;
struct fonction Fact_zero;

Moins_un.type=1;
Moins_un.f=moins_un;

Estnul.type=1;
Estnul.f=estnul;

Fact_zero.type=1;
Fact_zero.f=fact_zero;
printf("%d\n",eval(compose(moins_un,&Moins_un),15));
printf("%d\n",recursif(estnul,fact_zero,moins_un,ex_fact,10));
}
[/code]
reste plus qu’à dérécursifier en utilisant la structure, un peu pénible en fait…

C’est bien pour ça qu’il existe différent langages.

J’en profite pour vous montrer une technique que j’ai appris cette semaine pour calculer un factoriel n! quand le n est connu à la compilation (je vous donne directement le lien) : Métaprogrammation avec des patrons

Je trouve ça vachement intéressant, même si je ne suis pas sur que beaucoup de langages permettent ce genre de technique.

finalement, c’est quand même tellement mieux, le perl:

[code]#!/usr/bin/perl

sub compose {
my ($f,$g,@reste)=@;
my $O = sub { return &$f(&$g(@
)); };
return @reste ?
compose($O, @reste) :
$O;
}

$presente = sub { return "le resultat est ".shift; };
$ident = sub { return shift; };
$carre = sub { my $arg = shift; return $arg*$arg; };

$f = compose($ident,$ident);
$g = compose($presente,$carre,$carre);

print “f(10) : “.&$f(10).”\n”;
print “g(10) : “.&$g(10).”\n”;[/code][quote]roc@ROC:~$ ./test.pl
f(10) : 10
g(10) : le resultat est 10000[/quote]

et il y a aussi des templates: perldoc.perl.org/perlref.html#Function-Templates

L’une des idées de la metaprogrammation c’est que le calcul est fait à la compilation du coup l’exécution du programme est plus rapide.

Mouais. De nos jours, la diffèrence compilé/pcode/interprèté n’est pas forcément super sensible.
Et parfois on préfère un programme lisible moins rapide qu’un code obscur compilé.

J’ajouterais que je ne vois pas à quoi peut servir une fonction de composition, si ce n’est pas pour composer des fonctions fournies (et interprètées) à l’exécution. Sinon, tu codes directement ta fonction composée, tu ne t’amuse pas à l’objetiser.

Et j’ajouterais: tu codes comment en C la composition d’une fonction qui caste n’importe quoi en chaine avec une fonction numérique ?

La composition est une notion mathématiques. On considère des nombre en fonction des programmes tu prendras des entiers ou des nombres à virgules flottante. Son utilité peut se ressentir quand tu veut faire un programme qui réalise un calcule mathématiques et que pour des raison de lisibilité ( :mrgreen: ) tu veut garder la sémantique initiale. Ou alors si à l’écriture du programme tu ne connais pas les deux fonctions de ta composition.

Le fait que la différence entre code compilé vers du langage machine, du byte code ou juste à temps ne se fait pas sentir dans des petits programmes, n’indique pas que tu n’es pas le résultat d’un calcul très lourd dont tu te sers un grand nombre de fois dans le programme, trop complexe ou trop long à calculer à la main et que le passage par des variables globales pose des problèmes dans une optique multitâche bridant encore d’avantage les performances globales du programme. En tout cas j’avais montré ça juste pour le fun parce que c’est rigolo de voir une méthode très différentes de ce que l’on trouve habituellement.

En C++ tu peut rendre ta composition générique à fin de spécialiser les traitement en fonction du type de variable traité. En C tu te réécris ta fcomposition pour qu’elle puisse gérer les NTCTS.

Je ne crois pas que le Caml soit objet donc tu devrais parler d’une “fonctionalisation”.

Dis moi matt, tu ne serais pas un sectaire du C et du perl à tout hasard ?

[quote=“MisterFreez”]Dis moi matt, tu ne serais pas un sectaire du C et du perl à tout hasard ?[/quote] Non non, je bouffe à tous les rateliers. Ceux là et bien d’autres.
Mais c’est vrai qu’en ce moment je mange essentiellement du perl, couplé avec du code C génèré avec du XML (XSLT) qui exploite une lib particulière en C pur ( grep.be/articles/gnodbify ), mais c’est fini de débuguer cette couche là et ce n’est pas moi qui en ai produit le code, c’est mon collègue Wouter. Je suis arrivé pour produire les interfaces WEB en CGI, parceque malgré sa compètence, se prendre la tête à génèrer du HTML/Javascript était vraiment au dessus de ses forces (et je le comprend, c’est la mort le HTML et le dom).

[quote=“mattotop”]Mouais. De nos jours, la diffèrence compilé/pcode/interprèté n’est pas forcément super sensible.
Et parfois on préfère un programme lisible moins rapide qu’un code obscur compilé.

J’ajouterais que je ne vois pas à quoi peut servir une fonction de composition, si ce n’est pas pour composer des fonctions fournies (et interprètées) à l’exécution. Sinon, tu codes directement ta fonction composée, tu ne t’amuse pas à l’objetiser.

Et j’ajouterais: tu codes comment en C la composition d’une fonction qui caste n’importe quoi en chaine avec une fonction numérique ?[/quote]

La composition peut avoir plusieurs intérêts en elle même (l’objection sur la fin n’a aucune possibilité d’arriver dans un langage fortement typé), mais c’est surtout pour illustrer que dans un langage fonctionnel, les fonctiosn sont identiques aux objets:

compose n’importe quel type de fonctions. Un programme peut évoluer. C’est encore plus simple en Lisp mais le langage n’est pas typé.

Pourquoi ? La généricité le permet, non ?

Non:

#let compose f g = function x -> f (g x);; compose : ('a -> 'b) -> ('c -> 'a) -> 'c -> 'b = <fun> #
compose ne compose que des fonctions à un argument: g envoyant du type c sur du typea, puis f envoyant du typea vers du type `b et produit une fonction 'c -> 'b:

[code]
#let add3 = add_int 3;;
add3 : int -> int =
#add3 4;;

  • : int = 7
    #compose add3;;
  • : (’_a -> int) -> '_a -> int =
    #let concat s = s^“rajout”;;
    concat : string -> string =
    #concat “matt”;;
  • : string = “mattrajout”
    #compose add3 concat;;
    Toplevel input:

compose add3 concat;;
^^^^^^
This expression has type string -> string,
but is used with type string -> int.
#[/code]
Voilà ce que ça donne…

C’est donc possible avec de la généricité.

[code]template<typename A, typename R>
R fonction1(A var){
// premier traitement
}

template<typename A, typename R>
R fonction2(A var){
// second traitement
}[/code]
Avec le type de retour du premier en type, d’argument du second.
A la compilation, il y a vérification que tes types soient valides et utilisés correctement.