Can a pure function return a character?

It might be philosophical, but I thought this would be the right place to ask.

Suppose I have a function that creates a list of ids. These IDs are only used internally, so ES2015 can be used here Symbol()

.

My problem is that, technically, when you ask for Symbol, I would assume that the JS runtime generates a unique identifier (random number? Memory address? Unsure) that would require global state access to prevent conflicts. The reason I'm not sure is because of this word "technically". I'm not sure (again, from a philosophical point of view) if this should be enough to break the mathematical abstraction that the API represents.

tl; dr: here's an example -

function sentinelToSymbol(x) {
  if (x === -1) return Symbol();
  return x;
}

      

Is this feature clean?

+1


source to share


2 answers


Not really, no, but it doesn't really matter.

It (foo) => Symbol(foo)

looks clean on the surface . Although the runtime can perform some operations with side effects, you will never see them, even if you call them Symbol()

at the same time with the same parameters. However, a call Symbol

with the same arguments will never return the same value, which is one of the main criteria (# 2, below).

From MDN page :

Note that the ("foo") character does not coerce the string "foo" into a character. Each time it creates a new symbol:

Symbol("foo") === Symbol("foo"); // false

      

When looking only at side effects, (foo) => Symbol(foo)

is clean (over run time).

However, a pure function must meet higher criteria. From Wikipedia :

Purely functional functions (or expressions) have no side effects (memory or I / O). This means that pure functions have several useful properties, many of which can be used to optimize your code:

  • If the result of a pure expression is not used, it can be removed without affecting other expressions.
  • If a pure function is called with arguments that cause no side effects, the result is constant relative to that argument list (sometimes called referential transparency), i.e. if the pure function is called again with the same arguments, the same result will be returned (this could allow cache optimizations such as memoization).
  • If there is no data dependency between two pure expressions, their order can be reversed or they can be executed in parallel and they cannot interfere with each other (in other words, evaluating any pure expression is thread safe).
  • If the entire language is free of side effects, any evaluation strategy can be used; this gives the compiler the freedom to reorder or combine the evaluation of expressions in the program (for example, using deforestation).


You could argue that the foreword to this list excludes everything in JavaScript, since any operation can lead to memory allocation, internal structure updates, etc. In the strictest possible interpretation, JS is never clean. It's not very interesting or useful, so ...

This function meets criteria # 1. Regardless of the result, (foo) => Symbol(foo)

and is (foo) => ()

identical to any outside observer.

Criteria # 2 gives us more problems. Given bar = (foo) => Symbol(foo)

, bar('xyz') !== bar('xyz')

therefore Symbol

does not meet this requirement at all. You are guaranteed to get a unique instance on every call Symbol

.

Moving on, criterion # 3 is not a problem. You can call Symbol

from different threads without conflict (in parallel), it doesn't matter in what order they are called.

Finally, criteria # 4 is more of a note than a direct requirement and is easy to meet (JS runtimes move around as they go).

Thus:

  • strictly speaking, nothing in JS can be clean.
  • Symbol()

    is definitely not clean, so the example is not.
  • If all you care about is side effects and not memoization, the example fits those criteria.
+3


source


Yes, this function is unclean sentinelToSymbol(-1) !== sentinelToSymbol(-1)

. We would expect equality here for a pure function.

However, if we use the concept of referential transparency in a language with object identifiers, we can relax our definition a bit. Do you think function x() { return []; }

he is clean? Obviously, x() !== x()

but still the function always returns an empty array regardless of input, just like a constant function. So we must here define the equality of values ​​in our language. The operator ===

may not be the most appropriate here (just consider NaN

). Are arrays equal to each other if they contain the same elements? Probably yes, if they haven't mutated somewhere.



So, you will have to answer the same question for your symbols. The symbols are immutable, which makes this part easy. Now we could consider them equal in their value [[Description]] (or .toString()

), so it sentinelToSymbol

will be pure by that definition.

But most languages ​​have functions that allow you to break referential transparency - for example, see How to print a memory address in a list in Haskell . In JavaScript, this will be used ===

for objects with equal probability. And this will use the symbols as properties as this verifies their identity. So if you don't use such operations (or at least don't observe from the outside) in your programs, you can claim the purity of your functions and use them to reuse your program.

0


source







All Articles