I am currently working on a collection implementation in JavaScript. I need a mutable and an immutable version of that. So what I thought of first was something like:
- Collection
- MutableCollection extends Collection
- ImmutableCollection extends Collection.
My main issue with this is that if I want to implement more specific collections like List or Set, they could still inherit from Collection while their Mutable/Immutable implementations could not inherit from MutableCollection / ImmutableCollection.
So this made me think whether having all three, Collection, MutableCollection and ImmutableCollection would actually bring any significant benefit.
An alternative approach which came to me would be to only have Collection and then have a Collection.prototype.makeImmutable or freeze there. I would then also introduce a Collection.requireMutableInstance( collectionInstance ) which can be used in functions explicitly requiring a mutable collection.
Here are my main concerns about putting everything into just Collection:
- The prototype definition is getting much bigger compared to defineing a MutableCollection prototype as well which would then hold the code of all the operations for alteration. I guess I could solve this by just defining a second file anyhow which would then just extend the basic Collection definition with those additional operations and the makeImmutable operation.
- Documentation of those function signatures that actually care about mutable vs. immutable and not just require the basic collection would look less pretty.
Take:
/**
* @param {MutableCollection} collection
/*
function messAroundWithSomeCollection( collection ) { /* ... */ }
vs.
/**
* @param {Collection} collection A collection not yet made immutable.
/*
function messAroundWithSomeCollection( collection ) { /* ... */ }
What would be the more natural way of doing this in JavaScript? Especially without having interfaces I can check against and considering the flatter chains of inheritance if I would also implement a Set and Map collection, the everything-in-one approach seems more intuitive to me.
1 Answer 1
The "class hierarchy" that you mention depends a lot on what you put in Collection. For an immutable list what you usually need is a way of iterating through values: length
, forEach
, map
, find
, etc.
So what has the ImmutableCollection
interface that is not already part of Collection
?
Another possible hierarchy could be:
Iterable
:length
,forEach
MutableCollection
: implementsIterable
, but adds mutatorsadd
,remove
About how to convert a mutable collection into immutable, use a decorator:
new ImmutableView(mutableCollection)
Where ImmutableView
implements Iterable
but doesn't expose any mutator.
That's the approach used by most collection APIs: Java, Scala, Dart. (Actually Java implementation is broken in the sense that Collection
already have mutators, so an Immutable collection is as collection that doesn't follow the substitution principle: add
/remove
throws exceptions so semantically doesn't implement the interface)
About the Collection.requireMutable
. Why do you want to do that? JavaScript is not statically typed, so what you want to do is to emulate some sort of type checking.
If that really bothers you, switch to a language like TypeScript or Dart.
If not, assume that you receive a mutable collection and don't do any additional check.
Everything will be fine as long you:
- Do unit tests. TDD is the best companion for big systems based on dynamic languages
- Follow the Liskov substitution principle. Even if JavaScript doesn't have static typechecking, is good exercise to think about it. A quick rule of thumb, if your code has some
if (x instanceof Y)
conditionals.. you are not following the substitution principle. It means that you are not taking advantage of polymorphism.
-
A ReadableFoo, a ReadOnlyFoo, and an ImmutableFoo should make different promises; all should promise that items may be read out; the second should additionally promise that it cannot be typecast or otherwise manipulated to yield a reference which could be used to mutate the collection; the third should promise that no reference that could manipulate the collection could possibly exist. All three promises have independent uses, and would have value as separate types. As an alternative to using
instanceof
tests, one could have virtual methodsAsMutable
,AsReadOnly
, andAsImmutable
,...supercat– supercat01/18/2014 18:10:56Commented Jan 18, 2014 at 18:10 -
...which would return either
this
, a wrapper, or a new object, as appropriate. It may also be helpful to haveAsNewImmutable
, although that wouldn't need to be virtual (since all types should implement it asreturn new MutableFoo(this);
).supercat– supercat01/18/2014 18:12:17Commented Jan 18, 2014 at 18:12
...in most cases, code should not really care about whether a given collection is mutable or not.
: while ideally true, the semantics of dealing with immutable vs. mutable objects/collections can vary greatly, so I don't suspect you could get away with that assertion.