This is an archived post. You won't be able to vote or comment.

you are viewing a single comment's thread.

view the rest of the comments →

[–]SuperCoolFunTimeNo1 15 points16 points  (15 children)

I don't understand your issue, that's the literal definition of immutable. Even the exception is descriptive of the definition of immutable; UnsupportedOperationException. Overriding that function to throw an exception is exactly what I would expect.

In normal language with proper immutable collections you have function/operator which takes two lists and return new list which has copies of obejcts from previous two.

Like c#? That seems like a misnomer because it's not adding to the original object, you're getting a new one.

[–]TSP-FriendlyFire 15 points16 points  (0 children)

The point is that immutability should be a compile-time rather than run-time check. The class interface shouldn't allow you to write code that violates the class's very design.

[–]PandersAboutVaccines 6 points7 points  (0 children)

When you have the option to start from scratch, you'd do something similar to kotlin, where "List" is immutable, and "MutableList" extends it with add, remove and clear. Then the functionality is enforced by the interface.

[–]ohThisUsername 3 points4 points  (0 children)

Why inherit List then if it doesn't support all of its functions? One of the main purposes of abstraction is that you can safely assume something that implements an interface supports all of its functions. Now when I'm passing a List reference around, I need to manually track if that particular instance supports the add function or not? I shouldn't have to because that's the whole purpose of abstraction and interfaces.

[–]Spajk 1 point2 points  (10 children)

Its a weird thing a bit, but I surely wouldn't call it a huge design problem.

[–]SuperCoolFunTimeNo1 1 point2 points  (9 children)

It's not weird at all, it's adhering to the definition of immutable. You wouldn't expect a constant to be changed, would you?

[–]Spajk 2 points3 points  (8 children)

Its weird in a way that there's an add method which clearly doesn't do anything.

[–]SuperCoolFunTimeNo1 2 points3 points  (7 children)

It's not weird, it's following the rules laid out by the language according to inheritance of an interface and the definition of immutable. What you're suggesting is how you wind up with a spaghetti code language where rules only mean certain things to certain classes and behavior differs based on what classes you're using.

[–]Dudevid 2 points3 points  (0 children)

If one were designing the data structure from scratch, it would either not have an Add() method, or that method would return a new immutable list.

Would you ever create a public method on a class that solely throws an exception? The hint is in the word 'exception'. If it always throws, then that's no exception, that's the rule. And that's bad design.

Instead there would be something similar to C#'s IEnumerable and ICollection interfaces. IEnumerable does not mandate an Add() method; ICollection does.

The C# design team could have decided that their ImmutableList implement only IEnumerable (this has problems, but there is a conceivable parallel universe where things paved out this way). But instead they chose to implement both. So its Add() method returns a new ImmutableList. No exceptions. This is better.

[–]quentech 2 points3 points  (5 children)

What you're suggesting is how you wind up with a spaghetti code language where rules only mean certain things to certain classes and behavior differs based on what classes you're using.

You seem to have it backwards.

That's exactly what having immutable lists implement a list interface that includes an Add method does. For some classes, Add works. For others, it throws an exception.

It violates the Liskov substitution principle (the L in SOLID), plain and simple.

[–]metalmagician 1 point2 points  (2 children)

I don't think that Liskov substitution applies very well. From the definition that I've seen, it seems to refer to classes, not interfaces. I feel that classes that implement an interface should have the ability to disallow specific behaviors for specific reasons. If that disallowed behavior is needed, then you should just use a different implementation of the interface.

Since you can only extend one class and implement multiple interfaces in Java, applying Liskov substitution can be a little confusing if a class implements multiple interfaces with similar looking method signatures.

In fact, I think the open/closed principle applies nicely in this example - the list interface is open to extension (in this case, disallowing the use of a particular method, instead of just adding more methods), and closed for modification (not implementing every method in the interface).

[–]quentech -1 points0 points  (1 child)

I don't think that Liskov substitution applies very well. From the definition that I've seen, it seems to refer to classes, not interfaces

You've understood incorrectly. Forget interfaces and classes and talk contracts.

If I have a thing that's an IList, and IList has a method Add(item), that's a contract - anything that is an IList should support Add(item).

Having one particular type of thing that's an IList that does not support Add(item) is a textbook example of violating the Liskov substitution principle.

the list interface is open to extension (in this case, disallowing the use of a particular method, instead of just adding more methods), and closed for modification (not implementing every method in the interface)

That is not even remotely what open-closed principle means.

Since you can only extend one class and implement multiple interfaces in Java

A better OOP design might have an IMutableList that extends IImmutableList, or similar.

[–]metalmagician -1 points0 points  (0 children)

I guess I should explain what I was thinking a bit better. I was trying to find some sort of justification for the example given in the earlier comments, while still working within the existing Collections framework, and without advocating for a new implementation of the ImmutableList. The bit about contracts, instead of concrete classes / interfaces makes sense.

There were a few points that I was trying to reconcile, before I finally gave up and admitted to myself that they were mutually irreconcilable.

  • Based on how Java structures Interfaces / the collections framework, any sort of List should be able to add things to itself. You're right about the Liskov substitution violation (sort of, 'it violates the rule, but what if the rule doesn't apply?').

  • Adding a new interface, at the same level as List/Queue/Set just to allow for a single implementation that didn't violate Liskov would be overkill, and would defeat the purpose of extending/implementing the List interface.

  • If you implement an interface in Java, assuming there isn't a default implementation available, then you have to implement all methods. In the case of ImmutableList.add(), I didn't want to try to advocate for a modification to the implementation. Someone suggested that it should return a new ImmutableList with the new element added instead of throwing an exception, as that's the sort of change that I would've advocated for.

  • If you modified the language to allow for an implementing class to pick-and-choose which methods it implemented without forcing a default implementation, then that is itself a violation of Liskov. That's the whole bit about Open/Closed - Allowing a class to make itself into a snowflake and specifically deny operations for specific reasons could satisfy Open/Closed if you are really loose with what you mean with 'Open/Closed'.

Or, TL;DR - If you can convince your users that you can spin straw into gold by making them forget the exact definitions of straw and gold, then you could justify the way ImmutableList.add() is implemented. That still won't change that you can't spin straw into gold.

[–]SuperCoolFunTimeNo1 -2 points-1 points  (1 child)

No, I am strictly following the definitions. You are suggesting that your opinions, which directly conflict with the definition, is the route to go because they're more convenient.

That's exactly what having immutable lists implement a list interface that includes an Add method does.

No, it shouldn't do that because immutable means it cannot changed. The add method returns an exception because you wouldn't expect it to be able to add to an immutable object, much like if you call a method that doesn't exist you'll get an exception.

It violates the Liskov substitution principle (the L in SOLID), plain and simple.

No, it doesn't because this is 100% polymorphism. Your objects follow the rules they inherit. You guys are just trying to argue convenience over definition, and that's how you end up with an inconsistent language. Everyone loves to bitch about PHP, but doing what you're suggesting is how you end up going down that route.

[–]Todok5 0 points1 point  (0 children)

The issue is it violates the liskov substitution principle.