17

Coming from a OOP background (Java), I'm learning Scala on my own. While I can readily see the advantages of using immutable objects individually, I'm having a hard time seeing how one can design a whole application like that. I'll give an example:

Say I have objects that represents "materials" and their properties (I'm designing a game, so I actually really have that problem), like water and ice. I would have a "manager" that owns all such materials instances. One property would be the freezing and melting point, and what the material freezes or melts to.

[EDIT] All material instances are "singleton", kind of like a Java Enum.

I want "water" to say it freezes to "ice" at 0C, and "ice" to say it melts to "water" at 1C. But if water and ice are immutable, they cannot get a reference to each other as constructor parameters, because one of them has to be created first, and that one could not get a reference to the not-yet-existing other as constructor parameter. I could solve this by giving them both a reference to the manager so that they can query it to find the other material instance they need every time they are being asked for their freezing/melting properties, but then I get the same problem between the manager and the materials, that they need a reference to each other, but it can only be provided in the constructor for one of them, so either the manager or the material cannot be immutable.

Is their just no way around this problem, or do I need to use "functional" programming techniques, or some other pattern to solve it?

5
  • 2
    to me, the way you state, there's no water neither ice. There's just h2o material
    – gnat
    Commented Sep 12, 2011 at 18:50
  • 1
    I know this would make more "scientific sense", but in a game it's easier to model it as two different material with "fixed" properties, rather than one material with "variable" properties depending on the context. Commented Sep 12, 2011 at 20:46
  • Singleton is a dumb idea.
    – DeadMG
    Commented Sep 13, 2011 at 17:08
  • @DeadMG Well, OK. They are not real Java Singletons. I just mean that there is no point to create more than one instance of each, since they are immutable and would be equal to each other. In fact, I won't have any real static instances. My "root" objects are OSGi services. Commented Sep 13, 2011 at 19:06
  • The answer to this other question seem to confirm my suspicion that things get complicated really quickly with immutables: programmers.stackexchange.com/questions/68058/… Commented Sep 13, 2011 at 19:17

4 Answers 4

2

The solution is to cheat a little bit. Specifically:

  • Create A, but leave its reference to B uninitialized (as B doesn't exist yet).

  • Create B, and have it point to A.

  • Update A to point to B. Do not update either A or B after this.

This can either be done explicitly (example in C++):

struct List {
    int n;
    List *next;

    List(int n, List *next)
        : n(n), next(next);
};

// Return a list containing [0,1,0,1,...].
List *binary(void)
{
    List *a = new List(0, NULL);
    List *b = new List(1, a);
    a->next = b; // Evil, but necessary.
    return a;
}

or implicitly (example in Haskell):

binary :: [Int]
binary = a where
    a = 0 : b
    b = 1 : a

The Haskell example uses lazy evaluation to achieve the illusion of mutually dependent immutable values. The values start out as:

a = 0 : <thunk>
b = 1 : a

a and b are both valid head-normal forms independently. Each cons can be constructed without needing the final value of the other variable. When the thunk is evaluated, it will then point to the same data b points to.

Thus, if you want two immutable values to point to each other, you either have to update the first after constructing the second, or use a higher-level mechanism to do the same.


In your particular example, I might express it in Haskell as:

data Material = Water {temperature :: Double}
              | Ice   {temperature :: Double}

setTemperature :: Double -> Material -> Material
setTemperature newTemp (Water _) | newTemp <= 0.0 = Ice newTemp
                                 | otherwise      = Water newTemp
setTemperature newTemp (Ice _)   | newTemp >= 1.0 = Water newTemp
                                 | otherwise      = Ice newTemp

However, I'm side-stepping the issue. I'd imagine that in an object-oriented approach, where a setTemperature method is attached to the result of each Material constructor, you would have to have the constructors point to each other. If the constructors are treated as immutable values, you can use the approach outlined above.

1
  • (assuming I understood Haskell's syntax) I think my current solution is actually very similar, but I was wondering if it was the "right one", or if something better exists. First I create an "handle" (reference) for every (not-yet-created) object, then create all objects, giving them the handles they need, and finally initializes the handle to the objects. The objects are themselves immutable, but not the handles. Commented Sep 12, 2011 at 20:43
6

In your example, you're applying a transformation to an object so I would use something like an ApplyTransform() method that returns a BlockBase rather than trying to change the current object.

For example, to change an IceBlock to a WaterBlock by applying some heat, I would call something like

BlockBase currentBlock = new IceBlock();
currentBlock = currentBlock.ApplyTemperature(1); 
// currentBlock is now a WaterBlock 

and the IceBlock.ApplyTemperature() method would look something like this:

public class IceBlock() : BlockBase
{
    public BlockBase ApplyTemperature(int temp)
    {
        return (temp > 0 ? new WaterBlock((BlockBase)this) : this);
    }
}
6
  • This is a good answer, but unfortunately only because I failed to mention that my "materials", indeed my "blocks", are all singleton, so new WaterBlock() just isn't an option. That is the main benefit of immutable, you can reuse them infinitely. Instead of having 500,000 blocks in ram, I have 500,000 references to 100 blocks. Much cheaper! Commented Sep 12, 2011 at 20:35
  • So what about returning BlockList.WaterBlock instead of creating a new block?
    – Rachel
    Commented Sep 13, 2011 at 11:26
  • Yes, this is what I do, but how do I get blocklist? Obviously, the blocks have to be created before the list of blocks, and therefore, if the block is really immutable, it cannot receive the list of blocks as parameter. So where doe it get the list from? My general point is, by making the code more convoluted, you solve the chicken-and-egg problem at one level, just to get it again at the next. Basically, I see no way of creating a whole application based on immutability. It seems only applicable to the "small objects", but not to containers. Commented Sep 13, 2011 at 16:42
  • @Sebastien I'm thinking that BlockList is just a static class that is responsible for the single instances of each block, so you don't need to create an instance of BlockList (I'm used to C#)
    – Rachel
    Commented Sep 13, 2011 at 16:48
  • @Sebastien: If you use Singeltons, you pay the price.
    – DeadMG
    Commented Sep 13, 2011 at 17:09
6

Another way of breaking the cycle is to separate the concerns of material and transmutation, in some made up language:

water = new Block("water");
ice = new Block("ice");

transitions = new Transitions([
    new transitions.temperature.Below(0.0, water, ice),
    new transitions.temperature.Above(0.0, ice, water),
]);
1
  • Huh, it was hard for me to read this one initially, but I think it's essentially the same approach I advocated. Commented Sep 13, 2011 at 21:26
1

If you're going to use a functional language, and you want to realize the benefits of immutability, then you should approach the problem with that in mind. You're attempting to define an object type "ice" or "water" that can support a range of temperatures - in order to support immutability, you'd then need to create a new object every time the temperature changes, which is wasteful. So try to make the concepts of block-type and temperature more independent. I don't know Scala (it's on my to-learn list :-) ), but borrowing from Joey Adams Answer in Haskell, I suggest something like:

data Material = Water | Ice

blockForTemperature :: Double -> Material
blockForTemperature x = 
  if x < 0 then Ice else Water

or maybe:

transitionForTemperature :: Material -> Double -> Material
transitionForTemperature oldMaterial newTemp = 
  case (oldMaterial, newTemp) of
    (Ice, _) | newTemp > 0 -> Water
    (Water, _) | newTemp <= 0 -> Ice

(Note: I haven't tried to run this, and my Haskell's a little rusty.) Now, the transition logic is separated from the material type, so it doesn't waste as much memory, and (in my opinion) it's quite a bit more functionally oriented.

1
  • I'm not actually trying to use "functional language" because I just don't get it! The only thing I normally retain from any non-trivial functional programming example is: "Damn, I which I was more clever!" It's just beyond me how this can make sense to anyone. From my students days, I remember that Prolog (logic-based), Occam (everything-runs-in-parallel-by-default) and even assembler made sense, but Lisp was just crazy stuff. But I do get you point of moving the code that cause a "state change" outside the "state". Commented Sep 13, 2011 at 19:42

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.