0

Given an instance v of a class Vector, say v = new Vector(3, 5, 7), is it somehow possible to use the syntax v(k) to call a specific method of the class Vector on v with argument(s) k?

To provide some context, I'd like to be able to use v(k) to call the method getElem(k) on v, which retrieves the k-th vector element of v. For example, v(2) would return 7. In other words, v(k) would act as an alias or shorthand for v.getElem(k).

Of course it would be possible to write a (custom) pre-processor to achieve the above, I just wondered whether there is a built-in way to realise it.

This question was inspired by the syntax of the C++ library Eigen, which allows one to get/set matrix elements in a similar way. It would be lovely to have something like this in JavaScript.

A bit of code to accompany the class mentioned above —

class Vector {
    constructor(...vecElems) {
        this.vecElems = vecElems;
    }

    getElem(k) {
        return this.vecElems[k];
    }

    dot(v) {
        return this.vecElems.reduce((aV, cV, cI) => aV + cV * v.vecElems[cI], 0);
    }
}

const v = new Vector(3, 5, 7);
const w = new Vector(4, 6, 8);
console.log(v.getElem(2), v.dot(w));
theonlygusti
  • 11,032
  • 11
  • 64
  • 119
Ailurus
  • 731
  • 1
  • 10
  • 23
  • So you want `v(k)` to return `this.vecElements[k]`? – 0stone0 Feb 02 '23 at 14:13
  • Fundamentally you need `v` to be a _function_ first and foremost, with properties and methods as required. I don't think you can do that with class syntax, though. – jonrsharpe Feb 02 '23 at 14:18
  • Well, that part is possible by putting `return (i) => this.vecElems[i];` into the constructor, but then you’d lose the other functionality. Why do you want _this_ syntax in particular? You’re just making it unnecessarily complicated. – Sebastian Simon Feb 02 '23 at 14:21
  • @0stone0 Indeed. – Ailurus Feb 02 '23 at 14:32
  • @SebastianSimon I'm used to similar syntax from working with `Eigen`, and became curious whether this would be possible in JS. If so, it would be easy to generalise this to a multi-dimensional setting, i.e. working with matrix/tensor classes. – Ailurus Feb 02 '23 at 14:36

3 Answers3

1

You can make the syntax v[i] return v.getElem(i) too, by subclassing Array itself:

class Vector extends Array {
  constructor(...vecElems) {
    super(...vecElems);
  }

  getElem(k) {
    return this[k];
  }

  dot(v) {
    return this.reduce((aV, cV, cI) => aV + cV * v[cI], 0);
  }
}

const v = new Vector(3, 5, 7);
const w = new Vector(4, 6, 8);
console.log(v.getElem(0), v.getElem(1));
console.log(v[0], v[1]);
console.log(v.dot(w));
theonlygusti
  • 11,032
  • 11
  • 64
  • 119
1

Seems what you want is an object-generator (i.e. class) that returns functions that also have you custom properties.

Here's a way you can create such a "class":

function Vector(...vecElems) {
  let members = {
    vecElems,
    getElem: function (k) {
      return this.vecElems[k];
    },
    dot: function (v) {
      return this.vecElems.reduce((aV, cV, cI) => aV + cV * v.vecElems[cI], 0);
    }
  };

  return Object.assign(members.getElem.bind(members), members);
}

const v = new Vector(3, 5, 7);
const w = new Vector(4, 6, 8);
console.log(v.getElem(0), v.getElem(1));
console.log(v.dot(w));
console.log(v(0), v(1));

This will be more performant than the ES6-class-syntax with Proxy approach, because member access via Proxy suffers its indirection.

theonlygusti
  • 11,032
  • 11
  • 64
  • 119
1

Finally was pointed out a fairly elegant injection-safe way to do it in ES6+ class syntax with a Proxy:

class Vector extends Function {
    constructor(...vecElems) {
      super();
      this.vecElems = vecElems;
      return new Proxy(this, {
        apply(target, _, args) {
          return target.getElem(...args);
        }
      });
    }

  getElem(k) {
    return this.vecElems[k];
  }

  dot(v) {
    return this.vecElems.reduce((aV, cV, cI) => aV + cV * v.vecElems[cI], 0);
  }
}

const v = new Vector(3, 5, 7);
const w = new Vector(4, 6, 8);
console.log(v.getElem(2), v.dot(w));
console.log(v(2), v.dot(w));

This uses Proxy's handler.apply to wrap a callable object, which is in this case the class instance itself because it extends Function. It has to extend Function for the x(...) call syntax to be valid.

theonlygusti
  • 11,032
  • 11
  • 64
  • 119
  • Great! Thanks for all the effort (including rephrasing the original question), much appreciated. – Ailurus Feb 08 '23 at 08:30