Qu'on mette let fa = f a ou #define fa(x) f(a,x), ça revient au même!
non non non...
Les fonctions curryfiées ont des avantages indéniables par rapport aux fonctions à deux variables.
Un exemple bateau :
Imaginons que L est une liste de listes, et qu'on veuille ajouter l'élément 1 en tête de tous les éléments de L.
Par exemple, [ [3] ; [] ; [8;9] ] => [ [1;3] ; [1] ; [1;8;9] ]
Grâce à la curryfication de la fonction
append, la solution tient en seulement :
map (append [1]) L;;
(enfin bon, dans mon exemple improvisé j'ai mis append pour des raisons de clarté mais j'utiliserais personellement plutôt
map (prefix :: 1) L;;)
Regarde bien une démonstration de mathématiques! Il y a plein d'endroits où on saute à une autre étape! Par exemple, à chaque fois que tu lis "de même", c'est un goto!
Non, c'est un Gosub.
Ou un appel de fonction, si tu veux.
Dans une démonstration mathématique, on ne trouve pas de « la démonstration se poursuit à la page 5 » et de « le fil de la démonstration retourne à la page 2 ».
Et si on veut un nombre 64 bits sur une machine 32 bits? Le C99 le permet (long long est garanti faire au moins 64 bits)!
module int64
C'est un détail d'implémentation, cela. L'opération mathématique à la base est la même. C'est juste la représentation interne qui est différente.
Non, l'opération n'est pas la même. Les propriétés algébriques de Z et de R sont fondamentalement différentes. Et les propriétés des pseudo-entiers et des pseudo-réels représentés sur machine sont encore plus fondamentalement différentes.
Ben, eux au moins, ils corrigent leurs bogues!
Parce qu'eux, ils ont des bogues.
Oui, mais avec des parenthèses! La notation classique d'une fonction est f(x,y), pas f x y!
ça ne change rien, met les parenthèses si tu veux.
Les parenthèses ne sont pas inutiles parce que leur absence nuit gravement à la lisibilité!
Leur absence complète nuit à la lisibilité.
mais leur abus nuit tout autant.
1. Merci de penser aux 68k. Ça existe encore!
Et effectivement Caml est bcp moins adapté que le C pour les petites machines. Il est fait pour les processeurs modernes (bien que ce serait un bien beau projet d'implémenter une version très light de cml sur une calculatrice, il y a un hpuser qui a vaguement cette idée en tête)
2. Quel que soit le processeur (même sur un MIPS), si tous les nombres sont codés sous la forme 2x+1, ça complique tous les calculs, d'où perte de temps et d'octets!
Bah, il faut calculer a+b-1 au lieu de a+b....
Si je parlais de Mips, c'est parce que sur un Mips on fait ça en une seule instruction :
add $sp,$sp, -1, par exemple.
Je ne connais pas assez le x86 pour savoir si ce genre de chose existe sur la dernière génération de microprocesseur, mais même si ce n'est pas le cas, je gage que les mécanismes de pipeline & cie font que ça ne nuit pas du tout aux performances.
Comme en Java. Et pourtant, le Java s'arrange pour ne pas perdre un bit à chaque entier pour ça!
Niveau performances, le Java est à la rue par rapport au caml et au C.
Question implémentation, caml se veut un langage très "pointu", comme le C.
Et évidemment, perdre un bit ne gêne absolument pas!
Ce n'est certes pas sale, mais ça encourage la paresse des programmeurs au dépens de la performance!
Certainement pas. Ca permet de se concentrer sur l'algorithme plutôt que sur des futilités.
2. On gaspille de la mémoire, parce que la mémoire n'est plus libérée dès qu'on n'en a plus besoin, mais quand la prochaine "garbage collection" arrive. Et ne me dis pas qu'on n'a pas besoin de libérer de la mémoire avant que la mémoire ne soit pleine, parce que ce n'est pas vrai sous n'importe quel environnement multitâches!
Hum....
Sais tu comment est gérée la mémoire dans un environnement multitache par le C?
Le gestionnaire de programme réserve un ou deux très gros blocs à l'OS. Ensuite, il y a un minimum de transactions avec l'OS, et c'est un sous-gestionnaire qui gère l'intérieur de ces gros blocs.
Quand on réserve de la mémoire dans le programme, le sous-bloc qui est alloué n'a pas d'existence du point de vue de l'OS.
De même, quand on libère un bloc, ça ne fait pas du tout augmenter la mémoire disponible pour les autres programmes!
(Tout celà étant bien sûr une simplification, le gestionnaire de mémoire ayant parfois à décider de réserver ou de rendre un gros bloc)
Pour caml, c'est pareil. Ca ne pose pas de problème.
3. Un exemple concret: GCC 3 (depuis la version 3.0) utilise une "garbage collection" pour le compilateur lui-même. La version précédente, GCC 2.95(.x), utilisait autre chose. Tout le monde se plaint de la lenteur de GCC 3, et tous les tests ont montré que le ralentissement le plus important était exactement quand la "garbage collection" a été introduite.
Je préfère ne pas faire de commentaire sur la façon dont gcc est programmé.
Ce n'est pas efficace du tout! Par exemple, tu ne peux plus calculer x+y directement, parce que tu te retrouves avec 2x+1+2y+1=2(x+y)+2. Tu es donc obligé de soustraire 1 à chaque fois. Donc une instruction de plus, donc perte de temps et de place. Et c'est pire pour les multiplications, divisions, ...
Cf réponse plus haut.
Sur un 68000 ce serait pénalisant, pas sur un Pc tout beau tout neuf.
Il suffit de voir les benchs, de toute façon.
Si, c'est gênant! On se retrouve avec une rangée de nombres divisée par 2, et tu appelles ça "pas gênant du tout"???
Non.
D'ailleurs, si on ne me l'avait pas dit, je ne m'en serais même pas rendu compte!
Pas moi, parce que l'opération mathématique à la base est la même!
(Z,+,.) et (R,+,.) sont des anneaux très, très, très différents.
Tes benchs montrent peut-être qu'il n'y a pas de différence de vitesse
En effet.
(ce qui est vrai si (et seulement si) on considère un processeur 32 bits et si on néglige les ralentissements dus aux calculs supplémentaires nécessités par le bit pris à part),
Non.
Et puis, le seul bench que tu m'as montré est un bench dont l'auteur lui-même dit qu'il n'est probablement pas fiable...
Aucun bench n'est fiable. Celui ci est donc honnête.
Ben, dans ce cas, c'est de l'émulé, donc là, ça sera indéniablement lent et gros!
On a si peu besoin de 32 et 64 bits, de toute façon, que ça n'a pas d'importance.
C'est effectivement plus gros, mais pas tellement plus lent.
Pense à la mémoire consommée!
Ce ne sont pas en tant que telles les données basiques des programmes qui peuvent saturer les >64Mo des ordinateurs récents. Ce sont les données graphiques, multimédias, etc...
Quant à l'alignement, ça dépend du processeur, mais il n'est presque jamais nécessaire d'aligner un objet à plus de sa taille. Donc un entier 16 bits peut très bien être sur une adresse multiple impair de 2, et un entier 8 bits peut très bien être sur une adresse impaire, sans que ça nuise à la performance.
En caml ils sont en adresse paire.
Et de toute façon, par conservatisme et souci de compatibilité, les données restent presque toujours alignées.
C'est lourd!
Au contraire.
Et on y gagne quoi?
La simplicité.
L'efficacité.
La beauté du langage.
Exemple?
bah, tout ce qui est mauvais type. Par exemple, quand on se trompe sur l'ordre des arguments d'une fonction compliquée. Le C, qui est tolérant, fait un transtypage transparent, alors que caml pointe l'erreur.
De plus, l'analyse des erreurs de type permet souvent de démasquer les erreurs dans le code.
Filtrage?
Un autre grand pilier de caml.
Le filtrage, c'est un peu comme une structure switch/case généralisée : on recherche un motif (
pattern matching) dans les données et on exécute du code différent selon le motif détecté.
Exemple basique :
let nombre_vers_lettres = function
0 -> "zéro"
| 1 -> "un"
| 2 -> "deux"
| 3 | 4 -> "trois ou quatre"
| _ -> "beaucoup";;
Des variables locales peuvent aussi être déclarées au cours d'un filtrage :
let nombre_vers_lettres = function
0 -> "nul"
| x -> "le nombre " ^ (string_of_int x);;
Bien sûr le filtrage peut porter sur des types de données aussi élaborés qu'on veut :
let truc x = match x with
( _ , [a::q, x] as w) -> .....
Le filtrage a une puissance assez fabuleuse quand il s'agit de manipuler des structures de données élaborées. Par exemple, si on définit un type arbre binaire sans étiquettes par :
type arbre = Feuille | Noeud of arbre*arbre
La fonction permettant de compter les feuilles d'un arbre s'écrit grâce au filtrage :
let rec nb_feuilles = function
Feuille -> 1
| Noeud(a,b) -> (nb_feuilles a)+(nb_feuilles b);;
Je ne crois pas que le C puisse rivaliser dans ce domaine...
Mais c'est un détail d'implémentation.
et de mathématiques!
D'ailleurs la différence d'implémentation est elle même issue de la différence mathématique...
Ce n'est pas dans Linux, ça, c'est la source du "Bourne shell" d'origine (pas du "Bourne again shell" (bash) du projet GNU qui est le plus utilisé maintenant). Il y a un paquet de macros pour transformer le C en un dialecte de l'ALGOL.
Je ne savais pas, je croyais que c'était dans le code linux.
Amusant