Naming functions that 'operate on' vs 'copy and return'

Started by
12 comments, last by MaulingMonkey 12 years, 3 months ago
Very good points, thank you.

What are you defining 'literal objects' as?
I generally feel like unless the function holds onto the reference beyond the life of the function, it's pretty-clear cut and wont cause any side-effects.

I don't really get the distinction you are making. How is:
ReplaceAll(str); //Changes 'str'.
Any different from:
str.replace(...); //Changes 'str'.


You can make mutating behavior by combining non-mutating behavior and assignment; you cannot make non-mutating behavior from a mutating function. This gives you more flexibility as things evolve.

I'm not sure I understand this. I can make a non-mutating function from a mutating function.
//Implementing DoWork_Copy() off of a DoWork_Mutating().
std::string DoWork_Copy(std::string original)
{
std::string copy = original;
DoWork_Mutating(copy);
return copy;
}


Versus:
//Implementing DoWork_Mutating() off of a DoWork_Copy().
void DoWork_Mutating(std::string &original)
{
original = DoWork_Copy(original);
}

Perhaps I'm misunderstanding your terminology?

I haven't ever worked in multithreading code before, though I'm sure I will in the future. How is the first chunk of code harder to parallelize than the second chunk of code?
Advertisement

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.
No, that makes perfect sense, thank you for explaining it. I really appreciate the time you took to help me understand it.
While it's hard to generalize, where possible I generally trend towards making in-place operations verbs (e.g. "Normalize") and return-a-copy operations nouns (e.g. "Normal") or adjectives (e.g. "Normalized") and, depending on the language, possibly properties if they lack any inputs.

So a possibility would be "ReplaceAll" vs "WithAllReplaced". Of course, you can't enforce this on external libraries... and .NET's Replace for example returns a copy since strings are immutable there.

This topic is closed to new replies.

Advertisement