I think this is an unfortunate design decision which should be reconsidered. Functional operations should have different names than side-effecting operations. In general, I think that while side-effecting operations are commonly verbs, functional operations should be nouns or prepositions.
Particularly in a language without static types, you want to be able to look at an unfamiliar piece of code and see pretty quickly what types it is using. The semantics of mutable and functional collections are similar enough that using the same names is going to be very confusing, particularly in code that uses both kinds of collection -- and such code will definitely exist.
It's important that the names convey the semantics of the operation. Java's 'BigInteger' is a good example of this being done wrong -- the addition operation is called 'add', for example, and I have read that some newbies call it without assigning the return value to something, expecting it to be a side-effecting operation. I think that if it were called 'plus', such an error would be much less likely. We're used to thinking of "a + b" as an expression that returns a value, rather than an object with state.
I understand that introducing new names has a cost: people have to learn them. But keeping the old names is going to drive users nuts. If you won't change the names altogether, at least append an "F" to them or something.
EDITED to add: if you want some ideas, check out FSet: http://www.ergy.com/FSet.html
I ultimately decided that the mental cost of remembering a new API would outweigh the potential for accidental return value mis-management.
It's hard to make a decision like this sans-data, so I had to make a gut call. I'm really interested to hear feedback of issues encountered in-practice due to this. Of course, if I'm wrong about this (and there's always a reasonable chance I am!) then I would seriously consider changing the method names in a future major version.
It is much easier to remember that "foo always does 'a'" and "bar always does 'b'" rather than "foo does 'a' for some things but does 'b' for others", it's why we create functions and objects with different behaviors instead of nesting lots of if statements.
New objects with new behaviors should use new language.
This second force is ubiquitous in natural language, but common also in programming language where operators like "+" and "" are re-used in different contexts without practical ambiguity, and with the benefit of transfer of knowledge through metaphor.
So I think your conclusion -- "New objects with new behaviors should use new language" -- is a bit too strong. On the other hand, in this particular case, the context is very close (array behavior) and the only difference is the immutablity, so confusion is a valid practical concern.
One trick I use in FSet that you might want to copy is default values for maps. This is particularly handy when the range type of the map is another collection: you can make the default value be the appropriate kind of empty collection, making it unnecessary for code that accesses the map to check for a null value. In Java, for example:
FMap<Foo, FSet<Bar>> m = new FHashMap.withDefault(FHashSet.emptyMap());
// now I can do:
for (Bar b : m.get(x)) ...
// without worrying about whether 'm' contains an entry for 'x'.
Immutable.js supports something similar but at the access site:
var m: Im.Map<string, Im.Set<string>> = Im.Map();
console.log(m.get('foo')); // maybe undefined
console.log(m.get('foo', Im.Set())); // never undefined
I'm less a fan of your argument that method names like "push" or "add" inherently imply that they mutate their caller. I see no reason for that to be the case, other than in the context of your first argument. I think that an "add" method that returns a new integer without mutating its caller accurately conveys the semantics of the operation just as much as an "add" method that mutates its caller.
Readability is impacted as well. Building a mutable copy from an immutable should be explicit. These calls should have a very prominent call signature that is easily read, not skimmed over.
Very cool library, but I can sense lots of confusion and debugging resulting from accidental misuse.
Guava's immutables are pretty solid and serve as a great reference.
Depending on other API designs these warnings could also drown in false positives because some functions BOTH mutate the object and return it and you very rarely store the return value somewhere else, it's just there "for convenience" when chaining calls, such as foobar.add(123).multiply(456).subtract(789). Worse offenders are those that just randomly return something for the sake of it, take memcpy/memmove/etc in C for example, it accepts destination as a parameter and it also returns the very same destination for no good reason, i have never found a reason to use that return value.
An api should be designed so that unused return values in the majority of cases can be considered an error, unless explicitly ignored by casting it to (void) or something, most cases of unused return values you find are just people that are too lazy to check return codes which is about as smart as wrapping every statement in try/catch with an empty catch block.
And how likely is that? It's great if you start with a purely functional project but in most projects you're starting with a existing codebase, and you don't always have the time to go back and change everything when you figure out a better way to do it.
So I don't think it would be that unusual for someone to want to use immutable objects alongside alongside "normal" mutable objects. But it would be harder to distinguish the two in code when the methods are all the same, particular when you don't have type information at hand.