Is it possible to mark a variable such that it will only be used once?

Viewed 124

Consider the following code:

const a = initialValue()
const b = f(a)
const c = g(b)

I want to do something such that when a is being referenced the second time, it will be a compile error.

const d = h(a) // Compile error: `a` can be only referenced once

I need this to prevent faulty value derivation from variables that I'm not supposed to use anymore.

Thus, I'm wondering if this is possible in TypeScript (during compile-time)? If not are there any alternatives that I can consider?

Extra nodes: the main reason I need this feature is to breakdown a very long value derivation process to improve code readability.

Imagine the value derivation is something like this:

const finalValue = q(w(e(r(t(y(u(i(o(p(a(s(d(f(g(h(j(k)))))))))))))))))))

It's obviously necessary to break it down into several sections, that's why I need temporary variables that should be only use once within the same scope.

Edit: Added a realistic example

type Point = {x: number, y: number}
const distance = (a: Point, b: Point): number => {
  return Math.sqrt(Math.pow(a.x - b.x, 2) + Math.pow(a.y - b.y, 2))
}

Obviously the distance function above is not too readable, so I break it down by introducing temporary variable.

const distance = (a: Point, b: Point): number => {
  const xDistance = Math.pow(a.x - b.x, 2)
  const yDistance = Math.pow(a.y - b.y, 2)
  return Math.sqrt(xDistance + yDistance)
}

But then I have another problem, I don't these variables to be used more than once, in this case xDistance and yDistance. Referencing them more than once will introduce bug.

For example, referencing xDistance more than once introduces logical error which cannot be detected by type checking:

const distance = (a: Point, b: Point): number => {
  const xDistance = Math.pow(a.x - b.x, 2)
  const yDistance = Math.pow(a.y - b.y, 2)
  return Math.sqrt(
    xDistance + 
    xDistance // should be `yDistance` 
  ) 
}
4 Answers

I assume you have a reason for creating the variable; otherwise, Bart Hofland is on-point.

I suspect you can't (but then, just about every time I think that about TypeScript, I learn I'm wrong).

But there's a way (perhaps a hack) you can do it with object properties:

const v = {
    a_value: initialValue(),
    get a() {
        if (this.a_used) {
            throw new Error("a has already been used");
        }
        this.a_used = true;
        return this.a_value;
    }
};

// ...
const b = f(v.a); // Works
// ...
const d = h(v.a); // Fails: "a has already been used"

If you're going to do that more than once (which seems likely), you can use a helper function:

function oneTime(obj: object, name: string, value: any) {
    let used = false;
    Object.defineProperty(obj, name, {
        get() {
            if (used) {
                throw new Error(`${name} has already been used`);
            }
            used = true;
            return value;
        }
    });
    return obj;
}

// ...

const v = {};
oneTime(v, "a", initialValue());
const b = f(v.a); // Works
// ...
const d = h(v.a); // Fails: "a has already been used"

You can make it even more convenient to use if you build it in a proxy:

function oneTimer() {
    const used = new Set<string | Symbol>();
    return new Proxy({}, {
        get(target: object, name: string | Symbol) {
            if (used.has(name)) {
                throw new Error(`${String(name)} has already been used`);
            }
            used.add(name);
            return target[name];
        }
    });
}


const v = oneTimer();
v.a = initialValue();
v.b = f(v.a);
v.c = g(v.b);
v.d = h(v.a); // Error: a has already been used

Live Example:

function oneTimer() {
    const used = new Set/*<string | Symbol>*/();
    return new Proxy({}, {
        get(target/*: object*/, name/*: string | Symbol*/) {
            if (used.has(name)) {
                throw new Error(`${String(name)} has already been used`);
            }
            used.add(name);
            return target[name];
        }
    });
}


const v = oneTimer();
v.a = initialValue();
v.b = f(v.a);
v.c = g(v.b);
console.log(v.c);
v.d = h(v.a); // Error: a has already been used


















function initialValue() {
    return 42;
}
function f(n) {
    return n * 2;
}
function g(n) {
    return n / 2;
}
function h(n) {
    return n * 3;
}

If you don't want to reuse a variable, simply don't create it. Instead, use the expression that you use to initialize the variable where you use(d) the variable itself.

const b = f(initialValue())
const c = g(b)

A variable is traditionally used for storing values for reuse. If you don't want that, don't use them.

I would recommend some sort of pipe to compose functions in a readable manner:

increment(increment(multiply(2)(toLength("foo")))) // 8

becomes

flow(
  toLength,
  multiply(2),
  increment,
  increment
)("foo") // 8

This will prevent temporary variables, as @Bart Hofland said, while keeping code clean.

Playground sample
(this is the flow implementation from fp-ts library)


If you really (really) need something like a compile-time check for a "one-time" variable :
type UsedFlag = { __used__: symbol } // create a branded/nominal type 
type AssertNotUsed<T> = T extends UsedFlag ? "can be only referenced once" : T

const a = 42 // a has type 42
const b = f(a)
markAsUsed(a)
a // now type (42 & UsedFlag)
const d = h(a) // error: '42 & UsedFlag' is not assignable '"can be only referenced once"'.

function markAsUsed<T>(t: T): asserts t is T & UsedFlag {
    // ... nothing to do here (-:
}

function f(a: number) { }
function h<T>(a: AssertNotUsed<T>) { }

Playground sample

In addition to my earlier answer, I propose a possible solution to solve the a variable access issue for you.

In your answer, you gave this example:

const finalValue = q(w(e(r(t(y(u(i(o(p(a(s(d(f(g(h(j(k)))))))))))))))))))

Suppose the value of a(s(d(f(g(h(j(k))))))) should be calculated once and reused in functions q, w, e, r, t, y, u, i, o, and p.

Then you would get something like this:

const x = a(s(d(f(g(h(j(k)))))))
const finalValue = q(w(e(r(t(y(u(i(o(p(x))))))))))))

Now suppose you want to avoid that variable x is accessed again:

const x = a(s(d(f(g(h(j(k)))))))
const finalValue = q(w(e(r(t(y(u(i(o(p(x))))))))))))
const trySomething = x  // This should fail, because x is already accessed for calculating finalValue!

In that case, I would create a separate function, say calculate, to perform the complex calculation (bookmark 1):

function calculate(k) {
  const x = a(s(d(f(g(h(j(k)))))))
  return q(w(e(r(t(y(u(i(o(p(x))))))))))))
}

const finalValue = calculate(k)

As you can see here, variable x is now only accessible within function calculate for the purpose of the complex calculation.

However, there is no way to avoid calling the calculate function multiple times. Each time, variable x will be re-calculated as well. If you want to avoid that, you could use a variable to check if function calculate is already executed (bookmark 2):

let calculated = false;
function calculate(k) {
  if (calculated) {
    throw new Error('Do not call me again!')
  }

  calculated = true
  const x = a(s(d(f(g(h(j(k)))))))
  return q(w(e(r(t(y(u(i(o(p(x))))))))))))
}

const finalValue = calculate(k)
const finalValue2 = calculate(k) //Will throw error

But it might be possible to manipulate the calculated variable here. Using first-class functions (functions that return functions) and closures might help you in that case (bookmark 3):

function generate_calculate_function() {
  let calculated = false;

  return function(k) {
    if (calculated) {
      throw new Error('Do not call me again!')
    }

    calculated = true
    const x = a(s(d(f(g(h(j(k)))))))
    return q(w(e(r(t(y(u(i(o(p(x))))))))))))
  }
}

const calculate = generate_calculate_function();
const finalValue = calculate(k)
const finalValue2 = calculate(k) //Will throw error

The generated calculate function can now be called only once. And there is no way to manipulate the calculated variable from the outside.

However, there is now no restriction on generating a second calculate function and use that instead:

const calculate = generate_calculate_function();
const finalValue = calculate(k)

const calculate2 = generate_calculate_function();
const finalValue2 = calculate2(k) // Fine

So in the end, there will be no way to avoid calculating the value of variable x inside the calculate function multiple times.

But in my opinion, there should be no need to avoid multiple calculations at all. It should technically not be an issue to call function calculate multiple times. Or does the function have side effects and should your program thus be used only once and then never again? In that case, I would simply add some initial logic that somehow checks if your program has already been run in the past and only continue if that is not the case. Or better yet: I would try to separate the "pure" (repeatable) logic from the "unpure" (side-effects) logic and put any necessary run-once checks in that "unpure" logic, so that it will be skipped in subsequent calls. (This is an example of what I meant in my comment for you to consider rethinking your code structure/design.)

So I personally would just go with the code under bookmark 1 above. As I see it, the value of x is only used within the scope that needs it (the logic of function calculate) and it cannot be falsely used anywhere else.

NB: My solutions also use run-time checks. As far as I know, there is no way to let the compiler check if (and disallow that) a variable or function is being accessed multiple times.

Related