What are you defining 'literal objects' as?
Sorry, poor wording. Things that are objects in the real world or problem domain. For example Dates and Strings are not objects. Mutating them is weird, and not immediately intuited (in my experience). Inventories or people are more objects with traits or children. Changing a trait on an object doesn't make a new object, but adding something to a value gives you a new value.
Perhaps I've been using C# for too long, but the distinction between
value and reference types is one thing that I think doesn't get as much credit as it should. Even in some things like strings that are reference types but immutable that distinction often comes if the type represents an object in real life as opposed to a thing or a concept.
// example of non-mutating function from a mutating one
This requires copy on assignment, and will end up in... 3 copies if I count correctly (one for the parameter into the function, one into the temp and then one in whatever catches the result). Again, used to working in languages where copy on assignment is uncommon (or mutation is highly frowned upon/unavailable).
How is the first chunk of code harder to parallelize than the second chunk of code?
Those examples don't cause issues. Assume though that you were doing your mutation in-line on the same variable. You have to be careful how that is implemented. If for example you were doing a ReplaceAll on a string, that string would be off limits if you did something like
for( char *i = str; i; ++i ){
if( *i == search ){
*i = replace;
}
}
Because another mutating function or something that gets the string's value might run any time during the middle of this loop and see any of the intermediary steps. You would have to essentially build the new buffer as a copy and then do the replacement; and even then you might undo another mutating effect that occurs at the same time. Or you could have to prevent access to the variable... which sucks.
With an immutable behavior there's no chance for an interruption because each pile of intermediate steps exists in its own call stack. There you still have to worry about the scenario where the value you thought you were working with changes before your operation is done. Those issues though are less catastrophic if you fail to address them, and 'compare and swap' operations are well known solutions to these problems. Plus, they allow the user of the classes to deal with concurrency where they are using them since what needs to be done depends a bit on usage.
I'm sure something there is not entirely clear... let me know and I'll see what I can do to clarify or further expound on things.