Issu d'un background OOP (Java), j'apprends Scala par moi-même. Bien que je puisse facilement voir les avantages d'utiliser des objets immuables individuellement, j'ai du mal à voir comment on peut concevoir une application entière comme ça. Je vais donner un exemple:
Disons que j'ai des objets qui représentent des "matériaux" et leurs propriétés (je conçois un jeu, donc j'ai vraiment ce problème), comme l'eau et la glace. J'aurais un "gestionnaire" qui possède toutes ces instances de matériaux. Une propriété serait le point de congélation et de fusion, et ce à quoi le matériau gèle ou fond.
[EDIT] Toutes les instances matérielles sont "singleton", un peu comme une énumération Java.
Je veux que "l'eau" dise qu'elle gèle en "glace" à 0 ° C, et "glace" pour dire qu'elle fond en "eau" à 1 ° C. Mais si l'eau et la glace sont immuables, elles ne peuvent pas obtenir une référence l'une à l'autre en tant que paramètres de constructeur, car l'un d'entre eux doit être créé en premier, et que l'on ne peut pas obtenir de référence à l'autre en tant que paramètre de constructeur qui n'existe pas encore. Je pourrais résoudre ce problème en leur donnant à la fois une référence au gestionnaire afin qu'ils puissent l'interroger pour trouver l'autre instance de matériau dont ils ont besoin à chaque fois qu'on leur demande leurs propriétés de congélation / fusion, mais je reçois le même problème entre le gestionnaire et les matériaux, qu'ils ont besoin d'une référence les uns aux autres, mais il ne peut être fourni dans le constructeur que pour l'un d'eux, de sorte que le gestionnaire ou le matériel ne peut pas être immuable.
Est-ce qu'ils n'ont aucun moyen de contourner ce problème, ou dois-je utiliser des techniques de programmation "fonctionnelles", ou un autre modèle pour le résoudre?
la source
h2o
matérielRéponses:
La solution est de tricher un peu. Plus précisément:
Créez A, mais laissez sa référence à B non initialisée (car B n'existe pas encore).
Créez B et faites-le pointer sur A.
Mettez à jour A pour pointer vers B. Ne mettez pas à jour A ou B après cela.
Cela peut être fait explicitement (exemple en C ++):
ou implicitement (exemple dans Haskell):
L'exemple Haskell utilise une évaluation paresseuse pour obtenir l'illusion de valeurs immuables mutuellement dépendantes. Les valeurs commencent par:
a
etb
sont tous deux des formes valides normales à la tête indépendamment. Chaque inconvénient peut être construit sans avoir besoin de la valeur finale de l'autre variable. Lorsque le thunk est évalué, il pointera alors vers les mêmes donnéesb
points de vers.Ainsi, si vous souhaitez que deux valeurs immuables pointent l'une vers l'autre, vous devez soit mettre à jour la première après avoir construit la seconde, soit utiliser un mécanisme de niveau supérieur pour faire de même.
Dans votre exemple particulier, je pourrais l'exprimer dans Haskell comme suit:
Cependant, je contourne la question. J'imagine que dans une approche orientée objet, où une
setTemperature
méthode est attachée au résultat de chaqueMaterial
constructeur, il faudrait que les constructeurs pointent les uns vers les autres. Si les constructeurs sont traités comme des valeurs immuables, vous pouvez utiliser l'approche décrite ci-dessus.la source
Dans votre exemple, vous appliquez une transformation à un objet, donc j'utiliserais quelque chose comme une
ApplyTransform()
méthode qui renvoie unBlockBase
plutôt que d'essayer de changer l'objet actuel.Par exemple, pour changer un IceBlock en un WaterBlock en appliquant un peu de chaleur, j'appellerais quelque chose comme
et la
IceBlock.ApplyTemperature()
méthode ressemblerait à ceci:la source
BlockList.WaterBlock
au lieu de créer un nouveau bloc?BlockList
c'est juste unestatic
classe qui est responsable des instances uniques de chaque bloc, donc vous n'avez pas besoin de créer une instance deBlockList
(j'ai l'habitude de C #)Une autre façon de briser le cycle est de séparer les préoccupations du matériel et de la transmutation, dans un langage inventé:
la source
Si vous allez utiliser un langage fonctionnel et que vous souhaitez réaliser les avantages de l'immuabilité, vous devez aborder le problème en gardant cela à l'esprit. Vous essayez de définir un type d'objet "glace" ou "eau" qui peut supporter une plage de températures - afin de supporter l'immuabilité, vous devez ensuite créer un nouvel objet à chaque changement de température, ce qui est un gaspillage. Essayez donc de rendre les concepts de type de bloc et de température plus indépendants. Je ne connais pas Scala (c'est sur ma liste à apprendre :-)), mais en empruntant à Joey Adams Answer in Haskell , je suggère quelque chose comme:
ou peut-être:
(Remarque: je n'ai pas essayé de faire ça, et mon Haskell est un peu rouillé.) Maintenant, la logique de transition est séparée du type de matériau, donc ça ne gaspille pas autant de mémoire, et (à mon avis) c'est assez un peu plus fonctionnellement orienté.
la source