In my answer I use .NET's or Java's
List<T>.Add( T ) as an example of an impure deterministic function.
I also use
Random.Next() as an example of an impure nondeterministic function.
I now think both examples are wrong.
Part of my uncertainty comes from how the
this parameter is syntactically hidden in most languages with classes and instance-methods. But at the same time, whether or not
this is hidden doesn't affect a function's behaviour: it's trivial to show that a
static method or free function taking an explicit
const me parameter is identical to an instance method with a hidden
const this parameter.
this is mutable, I'm unsure. Actually, in the general case of a free-function that accepts a reference to a mutable structure: are those mutations considered (impure) side-effects even if they aren't "hidden"?
List<T>.Add( T ) impure and deterministic? Or pure and non-deterministic?
List<T>.Add( T item ) and its static equivalent
static void Add( List<T> me, T item ) are considered identical, then it follows that destructing
me to separate parameters shouldn't make a difference either, so therefore the following 3 methods are identical, no?
void Add( T item )
static void Add( List<T>* me, T item )
static void Add( T** array, size_t* index, T item )
T*to allow for reallocating when resizing.
I also have contradictory arguments going-on in my head:
- "It's impure" because
- "It's pure" because
Adddoesn't mutate any hidden or private state: it mutates only its inputs, therefore it's pure.
If mutating objects passed by-reference (namely
T** array) is what matters, then what about if
static void Add instead returns a tuple of
static ( T* nextArr, size_t nextIndex ) Add( T* array, size_t* index, T item ) (thus shifting the responsibility of reassigning
nextArr onto the caller), but it still mutates
array's contents if the resize/reallocation wasn't necessary, but there's no hidden mutations going on, so I'm finding it very hard to say if it's now pure or not...
Similarly for determinism:
"It's deterministic" because identical input will always result in identical output: that is, if the inner structure's
int indexare considered input, then the end-result output is the same (i.e.
[out] index = [in] index + 1and
[out] array = [in] array OR [new] resizedArray).
"It's nondeterministic" because the caller has no way of knowing if the
.Addcall will fail due to the array resizing (allocate, copy, deallocate) operation will fail due to unpredictable underlying runtime state, e.g. memory fragmentation, even if the length and contents of the input array is identical to previous
In languages like C# and Java, functions generally do not have their own state, so API design horrors like
strtok can't happen again.
...except when they do: both C# and Java allow the creation of closures, which means that what looks like a stateless function-pointer/delegate type really has a bound hidden
this (👀) parameter pointing to the closure capture object, which in-turn probably also contains the original
this too - which gives me a headache because if
List<T>.Add( T ) is-or-is-not pure or impure and deterministic or non-deterministic because of its
this parameter, how is a closure any different?
Oh, and both C# and Java also have thread-local storage - which allows for oddly-behaved concurrent but not necessarily reentrant functions, but I'm happy to keep that out-of-scope for now.