Les monads, c’est bien
Pour une bonne raison d’utiliser les monads, vous pouvez lire cet article où j’expliquais comment le fait d’avoir choisi un type monadique plutôt qu’un simple Maybe augmente automatiquement les possibilités du programme. Nous verrons la classe MonadPlus lors du prochain cours.
Articles connexes :

Je ne comprends pas l’invariant
return x >>= f == f xPour moi >>= doit retourner une monade, donc ce n’est pas
return x >>= f == return (f x)??
L’invariant donné est correct, n’oublie pas que « f » renvoie lui-même un monad.
Je suppose qu’il s’agirait d’une conversion implicite des int passés en paramètre en objets Integer, différents. D’où un résultat du genre 127 != 127. C’est bien ça ?
Chez moi, Eclipse refuse de le compiler… (Compilateur 1.6.0_03)
Bonjour Fred,
Chez moi, ça compile !
ols> java -version
java version « 1.6.0_10″
Java(TM) SE Runtime Environment (build 1.6.0_10-b33)
Java HotSpot(TM) Server VM (build 11.0-b15, mixed mode)
As-tu mis le code dans le ficher Piege.java ?
Olivier
Bonjour Olivier. Et merci pour cet exemple étonnant, JPR m’a dit qu’il le tenait de toi.
Bonjour,
Visiblement, l’autoboxing est apparu dans la version 5.0, or je tentais de compiler en compatibilité 1.4.
C’est ça, mais uniquement pour 128, pour 127 la comparaison renvoie un résultat vrai. C’est du à l’autoboxing des entiers hors du domaine 8 bits signés, qui créé de nouvelles instances alors que ceux tenant dans 8 bits signés utilisent toujours la même instance. Je trouve cela monstrueusement contraire au POLA (Principle of Least Astonishment).
Hmmm, je suis très rouillé en Java mais, en Java, tout n’est pas objet. int est un type scalaire (et donc on peut utiliser == pour comparer) et Integer est une classe (et on ne doit donc pas utiliser == pour comparer).
Remarque, je frime mais, avant de compiler avec gcj et d’exécuter, je n’avais pas détecté le piège :-)
Stéphane : le problème n’est pas tellement l’utilisation de == ici, mais le fait qu’on n’obtienne pas le même résultat pour 127 et 128. C’est assez surprenant, dans le sens où ça oblige à connaître les détails de la sémantique de l’autoboxing.
Voici un exemple du même genre que j’utilise en introduction d’un cours :
int j = 5; int i = 0; i = j << 32; println (i); (* Affiche 5. *) Integer a = new Integer (5); Integer b = new Integer (5); if (a == b) println ("1"); (* Test faux. *) a++; b++; if (a == b) println ("2"); (* Test vrai. *) a = 317; b = 317; if (a == b) println ("3"); (* Test faux. *)Ici, on voit que le hash-consing aussi (la réutilisation des instances) est difficilement prévisible …
En conclusion, bannir « == » ne semble pas une mauvaise idée :-).
/** * On veut comparer deux deux objets alors les opérateurs == ne sont * pas utilisable dans ce cas, car Integer n'est pas un type primitif (int). * On doit alors utiliser la méthode "equals" qui retourne un boolean si * les deux objets sont semblables. On peut surcharger cette méthode. * Pour en faire une soi-même, on doit comparer chacun des attributs */ class Compair { public static final String ILS_SONT_PAREIL = "Les deux OBJETs de type INTEGER sont équivalent"; public static final String ILS_SONT_PAS_PAREIL = "Les deux OBJETs ne sont pas équivalent"; public static void main(String[] args) { Integer i1 = new Integer(34); Integer i2 = new Integer(33); if(i1.equals(i2)) System.out.println(ILS_SONT_PAREIL); else System.out.println(ILS_SONT_PAS_PAREIL); } }rien avoir avec l’autoboxing mais tout avoir avec le cache que la classe Integer maintient pour les entiers de -128 a 127. Donc dans cet intervalle il n’existe au plus qu’une instance pour chaque entier et == retourne « true » quand on l’applique a deux Integers aillant la meme valeur entiere. En dehors de cet intervalle == retournera « false » si on l’applique a deux Integers aillant la meme valeur entiere. C’est pourquoi il faut utiliser equals() au lieu de ==. On peu aussi forcer le type pour comparer des entiers primitifs mais bon…
non:
private static boolean isSame(Integer i1, Integer i2) { return i1 == i2; }ok:
private static boolean isSame(Integer i1, Integer i2) { return (int) i1 == (int) i2; }mieux:
private static boolean isSame(Integer i1, Integer i2) { return i1.equals(i2); }Il y a le même cache en python, sauf que c’est de -5 à 256. Ça se voit avec l’opérateur « is », le comportement est exactement similaire à Java.
Même si c’est surprenant, il faut quand même passer par une très mauvaise pratique pour que ça apparaisse.
La class Integer est immutable et implémente le pattern flyweight.
Visiblement pas pour toutes les valeurs.
Je ne vois pas trop le rapport entre le problème pose et l’immutabilité des Integers et en et en depis de ce qu’on puisse lire ailleurs je ne suis pas vraiment sur que la classe Integer implémente la pattern flyweight que je comprends comme un mécanisme pour partager un état entre différents objets alors que dans le cas présent on a un cache d’objets ne partageant aucun état… mais bon toute pattern est sujette a interprétation et n’est pas grave dans le marbre.
Bonjour,
Cela n’a rien de surprenant, Java a sa facon de fonctionner c’est tout. Il y a plein de cas comme celui-ci. On ne peut pas taper sur un language sous pretexte qu’on ne le connais pas. Et lorsqu’on connais java, on n’est pas géné par ce type de cas. De toute facon, tester deux Integer avec == c’est se faire un croche patte tout seul.
Je veux bien prendre le projet: « Compilateur Scheme en Scheme générant du Factor »
Je suis intéressé par « Compilateur Factor en Factor générant du Scheme ».
Je veux bien « Implémenter les méthodes de passage de messages à distance d’Erlang en Factor »
Moi je prendrais bien le sujet sur le proxy.
Jean : je vais attendre qu’Antoine s’exprime avant de te l’affecter, car c’est lui qui a proposé le sujet, donc je suppose qu’il comptait le prendre. Antoine ?
Oui oui, bien sûr !!
En fait, le proxy je l’ai fait en C, et je voulais le faire en factor pour comparer et aller plus loin. Donc j’aimerai bien le prendre en effet. Désolé Jean…
Si t’es vraiment sur-motivé par ce sujet, je peux essayer de trouver autre chose, mais en principe j’aimerai bien faire ça.
Pas de souci Antoine !!
Je veux bien prendre alors l’implémentation des processus de supervision d’Erlang en Factor.
Je vais écrire un programme qui permet de contrôler par le port série le bras du club de robotique
I can’t read this because it’s not in ENGLISH!!!!!!!!
farmisen : le problème vient bien de l’autoboxing (comme le montre l’exemple de Yann). La valeur de (new Integer(127))==(new Integer(127)) est false, pas de surprise. Par contre, comme le montre l’exemple, l’autoboxing des int dans l’intervalle des bytes utilise un cache et donc il n’y a qu’une instance de Integer(127) (par exemple) utilisée par l’autoboxing. Par contre, toutes les instances que je crée à la main sont différentes. C’est expliqué en détail dans la spécification de Java.
Kaz : au contraire, c’est très surprenant et c’est l’exemple même de l’optimisation pourrie. Le compilateur fait dans mon dos des choses que je ne peux pas faire simplement, par exemple utiliser le cache des Integer. Voir que la sémantique de
Integer i = 127;
est différente de celle de
Integer i = 500;
est déjà perturbant, mais voir en plus que le premier code n’a pas non plus la même sémantique que
Integer i = new Integer(127);
c’est vraiment un choc.
Comme dit précédemment par Spoutnig, la classe Interger implémente le design pattern FlyWeight.
Les valeurs de -128 à 127 sont stockées en cache.
private static class IntegerCache {
private IntegerCache(){}
static final Integer cache[] = new Integer[-(-128) + 127 + 1];
static {
for(int i = 0; i = -128 && i <= 127) { // must cache
return IntegerCache.cache[i + offset];
}
return new Integer(i);
}
Styx: non, c’est faux, la classe Integer n’implémente pas le pattern FlyWeight. Si tu écris
System.out.println((new Integer(1))==(new Integer(1)));
ça affiche false, ce qui montre bien que deux instances différentes de la classe Integer sont crées.
C’est l’auto-boxing qui implémente le pattern FlyWeight.
Fabrice,
et si tu fais
System.out.println((Integer.valueOf(1))==(Integer.valueOf(1)));
ça affiche true … c’est valueOf() qui implémente le Flyweight pour les valeurs de -127 à 128 et l’autoboxing utilise implicitment le valueOf().
Si tu regarde le bytecode generé par ceci
public static void main(String[] args) throws Exception{
Integer i = 32;
}
Ca donne ceci: (On voit bien en instruction 2 l’appel static à valueOf généré par l’autoboxing)
// Method descriptor #15 ([Ljava/lang/String;)V
// Stack: 1, Locals: 2
public static void main(java.lang.String[] args) throws java.lang.Exception;
0 bipush 32
2 invokestatic java.lang.Integer.valueOf(int) : java.lang.Integer [20]
5 astore_1 [i]
6 return
Line numbers:
[pc: 0, line: 8]
[pc: 6, line: 9]
Local variable table:
[pc: 0, pc: 7] local: args index: 0 type: java.lang.String[]
[pc: 6, pc: 7] local: i index: 1 type: java.lang.Integer
}
Spoutnig
Spoutnig,
Ce que je voulais dire est que le pattern flyweight demande explicitement (en Java) de passer le constructeur en private car il n’y a pas de moyen (en Java toujours) de faire en sorte de new Bidule() te renvoie un objet déjà existant. La classe Integer implémente donc une version dégradée du pattern car le constructeur est public.
Le problème de cette version dégradée est que le comportement associé est pour le moins inattendu. Le fait que l’auto-boxing passe par une méthode factory alors qu’il existe un constructeur public n’est pas déductible simplement des principes généraux 1) de l’auto-boxing 2) de l’utilisation classique d’un constructeur. Il est vrai que la doc de Integer.valueOf précise qu’il vaut mieux l’utiliser à la place du constructeur, mais ce point n’est abordé ni dans la doc générale de la classe, ni dans la doc du constructeur. Et la cerise sur le gâteau : le pseudo flyweight est implémenté de façon sure pour l’intervalle -128..127 et de façon possible pour les autres valeurs. De ce fait, le comportement de l’auto-boxing dépend de la version de la JVM : merveilleux, non ?
Bonjour
Pour ce qui est du code en premier, oui il s’agit de l’Autoboxing :
si on teste new Integer(127) == new Integer(127) on aura faux tout le temps, pour la simple et bonne raison que le ‘==’ va comparer les références de ces objets !!!
Evidement, ils n’ont pas la même adresse en mémoire (référence).
Pour que ce code marche tu dois utiliser new Integer(127).equals(new Integer(127))
ou mieux encore :
new Integer(127).intValue() == new Integer(127).intValue()
Dans ce cas là la comparaison aura bien lieu sur le type ‘int’ et non pas sur la classe Integer.
C’est la raison pour laquelle je conseille pour tout type numérique, de faire passer en paramètre les types primitifs et non pas les types wrappés (Integer pour int).
// attention, null pointer exception
boolean compare(Integer a, Integer b) { return a.intValue() == b.intValue; }
// oui bien mieux encore, puisque le warning potential nullpointerexception pourra être levé
boolean compare(int a, int b) { return a == b; }
L’autoboxing n’a rien à voir avec de la mise en cache, c’est juste que lors de la compilation, il va s’occuper de faire appel à des wrappings/unwrapping
Integer n = new Integer(10);
int x = n; // sera transformé en int x = n.intValue()
int n = 0;
Integer x = n; // sera transformé en Integer x = new Integer(n);
Enfin, les types wrappés ont été introduits pour plusieurs raisons :
1 – la possibilité d’avoir des références nulles est parfois d’une importance capitale
2 – l’introduction de la norme IEEE Standard 754, adaptée à la base pour les nombres flottants qui dit:
=> la valuer « not a number » doit être possible
=> la définition des maximums et minimums de l’ensemble traité
=> l’infini positif et négatif.
J’espère que ces explications en éclaireront certains…
A oui !
Pour ce qui est de la phrase « Java-c’est-mal », en fait je dirai plutôt, c’est mal de ne pas lire les spécifications du langage et c’est encore plus mal de ne pas se poser la question de pourquoi un langage aussi puissant laisse faire ce genre de chose… ;)
Votre article démarre plustôt bien, en posant la question centrale
qui est celle du codage du programme.
Jetez donc un coup d’oeil de ce coté là:
http://www.fullpliant.org/doc/language/meta