#903 Mutable and immutable lists

jodastephen Sat 9 Jan 2010

Thread 884 brought up the issue of whether methods on List (and Map) should always return a new list.

Brian: The Fantom convention I use is that you never expose a mutable list, mutation is done safely inside a class and only a ro or toImmutable copy is every exposed. So I think adding more mutating methods would be the wrong path to head down.

I wanted to present a problem based on how I work today in Java, and see what solutions get proposed. We use mutable lists everywhere, and the lack of mutable alteration methods would be a major downside.

Consider the travel domain, where you have a return trip (Edinburgh to Sydney and back) consisting of two journeys (Edinburgh to Sydney, and Sydney to Edinburgh). Each journey consists of two segments, which are airline pricing elements (Edinburgh to London, and London to Sydney). The Edinburgh to London segemnt is one Leg, the London to Sydney segment is two Legs (via Hong Kong), where a Leg is a flight.

Thus the JavaBean object model is (simplified and converted to Fantom):

class Trip {
  Journey[]
}
class Journey {
  Segment[]
}
class Segment {
  Leg[]
}
class Leg {
  Str fromAirport
  Str toAirport
}

Now, in our code we are always searching and filtering this structure, frequently changing data at the lower leg or segment level, rather than just the list of journeys. For example, filtering the list to exclude trips based on leg information, or similar. Trying to do this with only immutable filtering (or immutable lists) would be a nightmare. In case it isn't clear, the problem is the depth of the structure and the need to edit in place.

I'll leave this open for suggestions as to a Fantom alternative design. Right now, I find the suggestion of not having mutable edit methods on a list crazy, but I'm willing to be educated.

After all, are we taking of making the List.add method return a new list fo consistency?

KevinKelley Sun 10 Jan 2010

Couple points... first, you're already using one immutable type in that: Leg is holding immutable Str objects. Doesn't mean you can't change the value of fromAirport, just that no code anywhere can turn your "Edinburg" to "EdinItAin't".

So the nice thing about using only the immutable (idempotent) interfaces to List is, you get the same safety as you do with Strings: you know that if you hold a reference to a particular list, with known contents, it won't be changed out from under you. "Pure" functional languages take this idea all the way, and get some serious provably-correct power out of it.

OO languages generally don't go too far down that road; we tend to get safety though encapsulation. Exposing a modifiable handle to your class's data is kind of seriously, completely, horribly not a good idea. So I'm going to assume that's not really what you're doing. :-)

I use List to hold all kinds of stuff. But I never, not even to myself in code that I'm going to throw away tomorrow, never modify those lists from outside the class. When I do need to expose a list, I make an accessor function and pass out a .ro copy. But even that is kind of a code smell; if an object is nothing more than a holder of a list of things, there's no point in making a class out of it.

I don't think anybody's seriously suggesting doing away with modifiable lists in Fantom. But, having a subset of methods be "pure", "const", "idempotent", whatever you choose to call it, is important and valuable. And, while Fantom isn't a pure FP language, we're making great use of some things that come from FP (cf. closures, type inferencing).

One thing that comes from Haskell and the Monads thing is the idea that you can keep the benefits of "pure" functions, and still allow for mutable state and declarative-style programming, by splitting your Types into a const part, and a mutable part, and providing a conversion between the two. Then you get the provability/safety while you're using the const version of the type, and you get clear synchronization points where you have to convert to the mutable version.

Integrating that idea with an OO-style type hierarchy, is still an exercise left for the reader. :-)

jodastephen Sun 10 Jan 2010

first, you're already using one immutable type in that: Leg is holding immutable Str objects. Doesn't mean you can't change the value of fromAirport, just that no code anywhere can turn your "Edinburg" to "EdinItAin't".

Big difference. Having immutable value objects is great! Immutable lists would be a pain in the backside.

Exposing a modifiable handle to your class's data is kind of seriously, completely, horribly not a good idea. So I'm going to assume that's not really what you're doing. :-)

That is exactly what we do. We expose mutable lists from objects. And it works very well, thank you.

More significantly, I believe that most Java/C# developers code in the same way. That is what the whole JavaBean stuff is designed around. Taking mutable methods like sort/filter/add/remove away would seriously block those users from moving to Fantom.

BTW, this is is some ways a rehash of thread 311. If Fantom had a syntax for deep setting of immutable objects, then the entire structure above could be immutable. However, the difference here to the 311 debate is that this structure doesn't really need to be immutable. Basically, removing mutable sort/filter would be constraining the kinds of solutions that could be built in Fantom quite strongly.

My preferred solution is for Fantom to have both type of method on List and standard naming to determine retun-copy vs modify-in-place methods:

list.sort      // present tense, so modify-in-place
list.sorted    // past tense, so return-copy
list.filter    // present tense, so modify-in-place
list.filtered  // past tense, so return-copy

brian Sun 10 Jan 2010

Just to clarify: I am not against mutable lists or maps. I believe that for performance and convenience mutation is ok. However I personally do not believe that APIs shouldn't let mutable collections escape their encapsulation. I have designed all the Fantom APIs around this philosophy.

But if you want to design code that uses mutables lists, I don't think there is any real problem with that as long as you make the list fields settable. Looking at your code, maybe that is what you are already doing. Remember it isn't just about mutating an existing list, but using all the methods that can generate lists such as:

segment.legs = foo.map |f->Leg| { ... }

I make a distinction between a "mutator" and a "transformer". I don't have any inclination to change mutating methods like add, remove, insert, etc.

However most transform methods like findAll, map, etc return new lists. The only transform methods which are in-place are sort, sortr, and reverse. My proposal is to make them consistent with the rest of the transform methods.

jodastephen Sun 10 Jan 2010

However I personally do not believe that APIs shouldn't let mutable collections escape their encapsulation. I have designed all the Fantom APIs around this philosophy.

The problem is that the proposal (for sort and reverse, and similar) means that your philosophy is being forced on users of Fantom. I can't use your core list API in the way I want to, which is a standard pattern for JavaBeans, and very widely used.

(Because of Fantoms limited generics, there is no "design your own list class" solution here)

I don't think there is any real problem with that as long as you make the list fields settable.

Nope, the lists are readonly in my curent Java case. I could make them read-write, but that would still involve lots of extra line noise.

helium Sun 10 Jan 2010

I can always emulate

bar = foo.returnTransformedCopy()

as

bar = foo.dup().transformInPlace()

, but I can't emulate

foo.transformInPlace()

The closest I can get is

foo = foo.returnTransformedCopy()

which has characteristics similar to

foo = foo.dup().transformInPlace()

(Just stating facts not proposing anything.)

KevinKelley Sun 10 Jan 2010

There's some benefit to separating the mutable and the immutable interfaces to a type. One way that's been tried is to use interitance:

class ConstList {
  List sorted() { /*return a sorted copy*/ }
  List reversed() ...
}
class MutableList : ConstList {
  This sort() { /*sort in place*/ }
  ...

but that tends to not work out so well in practice, for reasons I can't remember offhand, relating to co- vs. contra-variance I think.

Fantom avoids those problems (whatever they are exactly) by conflating the mutable and immutable interfaces into the same List class. Which seems to work out pretty well, plus/minus some argument about whether the interface is too big, and questions about how come a favorite method has only a (mutable|immutable) version when I want the other.

I'm thinking maybe that mutable is like nullable. I'm wondering if there'd be a way to indicate mutability like we do nullability, and then allow mutable methods (maybe separated into a mixin) only on mutable objects.

That's just guessing and handwaving, though. I think that with Fantom as it is, all we can really do is compromise on the interfaces by giving both mutable and immutable versions where the use cases make it important.

I do like the ( sort /*in place*/ vs. sorted /*copy*/ ) pattern joda mentioned, by the way. But it tends to lead to explosion of interfaces, so I tend to just pick whichever one fits the uses most often, and go with it.

brian Sun 10 Jan 2010

Nope, the lists are readonly in my curent Java case. I could make them read-write, but that would still involve lots of extra line noise.

I think if you are going to have mutable lists, then you'd want mutable fields too. Then your problem is solved. I don't know how much Fantom you've written, but I found that a huge number of lists are constructed from things like map, slice, etc which will always require creating a new list.

Although we could always add some method like replace that just mutably replaced the entire list:

legs.replace(legs.sort)
legs.replace( foo.map |m->Leg| { toMap(m) })

brian Sun 10 Jan 2010

There's some benefit to separating the mutable and the immutable interfaces to a type. One way that's been tried is to use interitance:

It's always nice to try to model things like that with the type system or with multiple classes, but it seems to get real ugly. I think what we have now with a single class that serves multiple purposes may be not ideal, but it is pretty pragmatic.

tcolar Tue 12 Jan 2010

I think there are use for both.

Default should be immutable, I always prefer safer & simpler by default - less bugs, work on performance later.

It would be nice to have the mutable option too when performance is needed.

Maybe the way java does with collections/synchronized ??

Login or Signup to reply.