Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

typescript

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` 
  ) 
}
like image 855
Wong Jia Hau Avatar asked May 13 '20 08:05

Wong Jia Hau


People also ask

Why does my model keep saying somehow this variable is used?

I would hypothesize that the "somehow this variable is used" part may be because the parameter is used outside of your model's forward pass, which DDP is not aware of. Is this the case in your model? Either way, disabling find_unused_parameters=True should fix this as the parameter does end up getting used (in a way DDP is not aware of).

Can we measure a variable with just one question?

Sometimes, although we can measure a variable with just a single question, it may be preferable to use two or more items, because this improves reliability. Yes, if we compare the estimation done after asking with the ignorance on hand if we do not ask. But there exist seceral issues on how accurate our estimation will be... a whole science.

Can I Mark a variable ready multiple times in DDP?

For example, if you use multiple `checkpoint` functions to wrap the same part of your model, it would result in the same set of parameters been used by different reentrant backward passes multiple times, and hence marking a variable ready multiple times. DDP does not support such use cases yet.

What causes runtimeerror-expected to mark a variable ready only once?

Now I the error is a bit more informative: RuntimeError: Expected to mark a variable ready only once. This error is caused by one of the following reasons: 1) Use of a module parameter outside the `forward` function.


2 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;
}
like image 85
T.J. Crowder Avatar answered Nov 15 '22 08:11

T.J. Crowder


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

like image 41
ford04 Avatar answered Nov 15 '22 08:11

ford04