Here are my two cents. A function in the mathematical sense is a mapping from input values to an output value, and nothing more. It does not print "Hello world!", and so on. It does not even compute anything.
Many programming languages use the term function to denote a self-contained piece of code that may take parameters and may return a value. Another term is procedure. Object-oriented languages have methods, which are procedures integrated with objects: they are invoked with the object itself as an implicit argument, and have access to the object's internal member variables.
When invoked, a procedure can perform a number of operations like reading from / writing to variables (memory locations) or IO devices (files, keyboard, and so on).
The term pure has been introduced to denote programming language functions (procedures) whose only effect is to compute a function in the mathematical sense: given some input values, they compute a result and return it.
Looking at the example of the OP. As other answers have pointed out, since
AllUppercase() is invoked with the object itself as an implicit input, you can consider it a mapping from objects to strings. So
- For any given object,
AllUppercase() will always return the same string, and
AllUppercase() has no observable effect other than what described at point 1.
AllUppercase() only computes a function in the mathematical sense, i.e. it is a pure function in the programming language sense.
Regarding the idea of a mutable input, pure input. I think it is important to define precisely what an input is and what same input means. Suppose that the member
name in class
Example were mutable. So now you invoke
AllUppercase() and get one result. Then you mutate
name, invoke the method again and get a different result.
If you define same input as "same memory location" (same object reference), then the function produced two different results with the same input ==> not pure.
If you define same input through object equality (the result of the 'Equals" method in C#), then
AllUppercase() is still pure: when you invoke it on a mutated object, you are passing a different input so it is not a problem that it returns a different result.
Bottomline: to reason about purity when using mutable data you have to be precise about what you consider your inputs: addresses / variables or values contained in variables? This distinction is irrelevant when dealing with immutable data.