0

I am creating a language which is compilable to Swift, Rust, and JavaScript (or at least trying). Rust and Swift both use pointers/references/dereferencing/etc., while JavaScript does not. So in a Rust-like language, you might do something like this:

fn update(x) {
  *x++
}

fn main() {
  let i = 0
  update(&i)
  log(i) #=> 1
}

In a JavaScript-like language, if you did this then it would fail:

function update(x) {
  x++
}

function main() {
  let i = 0
  update(i)
  log(i) #=> 0
}

Because the value is cloned as it is passed in (as we obviously know).

So what I am thinking about is doing this at first:

function update(scopeWithI) {
  scopeWithI.i++
}

function main() {
  let i = 0
  let scopeWithI = { i }
  update(scopeWithI)
  i = scopeWithI.i
  log(i) #=> 1
}

But that is a lot of extra processing going on, and kind of unnecessary it seems. Instead I might try compiling to this:

function update(scopeWithI) {
  scopeWithI.i++
}

function main() {
  let scope = {}
  scope.i = 0
  update(scope)
  log(scope.i) #=> 1
}

This would mean every nested scope you create, you would have to start manually creating/managing the scope chain. And actually that wouldn't work because update is hardcoded to i. So you might have to pass in what the variable name is you want.

function update(scope, ...names) {
  scope[names[0]]++
}

But then it's like:

function update(scope, ...names) {
  scope[names[0]]++
}

function main() {
  let scope = {}
  scope.i = 0
  if random() > 0.5
    let childScope = { scope }
    childScope.x = 0
    update(childScope, ['i'])
    update(childScope, ['x'])
    update(childScope, ['x'])
    log(childScope.x) #=> 2
  else
    update(childScope, ['i'])

  log(scope.i) #=> 1
}

So that seems like it might get us somewhere.

So then it's like, the generic solution is to have scope be the first parameter to a function.

function add(scope, name1, name2) {
  return scope[name1] + scope[name2]
}

Dereferencing means reading a value directly from the scope, while passing a reference (like &name in Rust or C), would mean passing the scope and the name.

Will something like this work? Or better put, what needs to be changed or added? Does it need to get any more complicated than this?

I would like to try and find a way to transform the pointer-oriented code into JavaScript (transpilation), without at first trying to figure out the seemingly much more complicated approach of not being so direct, and avoiding pointer simulation in JavaScript by redefining a lot of the methods. It seems that avoiding any pointer use in JavaScript would be way harder to figure out, so I am trying to see if a pointer sort of system would be possible to simulate in JavaScript.

To avoid pointer simulation, you would have to redefine methods.

update(x) {
  *x++
}

Would have to change the outer usage of the function everywhere. So this:

main() {
  let i = 0
  update(&i)
}

Would become:

main() {
  let i = 0
  i++ // inline the thing
}

For this simple case it's fine, but for a more complicated function it starts to seem like macros and might get complicated.

So instead of changing the outer usage, we make it so you have to pass the scope.

Another approach might be to have every variable be an object with a value, so it's more like:

update(x) {
  x.value++
}

main() {
  let i = { value: 0 }
  update(i)
}

So then I'm thinking to myself, how to handle references to references then?

update2(x) {
  update(&x)
}

update(x) {
  *x++
}

main() {
  let i = 0
  update2(&i)
}

In the system i described, that would be like:

update2(x) {
  // then what?
  let y = { value: x }
  update(y)
}

update(x) {
  // like this?
  x.value.value++
}

main() {
  let i = { value: 0 }
  update2(i)
}

So it seems this wouldn't really work.

3
  • You know your 6th code block is invalid syntax, and the interpreter will tell you that else didn't come right after an if. Did you miss the curly braces? Commented Feb 26, 2022 at 5:07
  • I think some of your text (title & body) conflates "pointers" with "call-by-reference". Although pointers in C are mirrored to objects in JS, this still is a different concept than call-by-reference. Commented Feb 26, 2022 at 9:56
  • Yeah I'm aware that JS is pass-by-value instead of by reference, but I was trying to see if you could sort of simulate it like my last example using objects. But I think your answer of reassigning the values on return might work too. I just wasn't sure it would make the translation extremely complicated or not. Commented Feb 26, 2022 at 10:12

3 Answers 3

2

Needless to say, but JavaScript does not have a general mechanism to pass arguments by reference.

There can be some confusion around the term "reference", as in JavaScript one can pass objects to functions -- which are references -- but this is a call-by-value mechanism. Call-by-reference really means that the parameter variable is an alias for the caller's variable, such that assigning to that alias is equivalent to assigning to the caller's variable. Except for some very particular situations (like the arguments exotic object in non-strict mode, or the export mechanism, or the var link with the window object, none of which helps you in your case in a best practice way), there is no such variable-aliasing mechanism in JavaScript.

Here is an example on how to "exploit" the effect of var in a browser context, in non-strict mode:

function modify(ref) {
    // Using the fact that global `var` variables are aliases
    //  for properties on the global object
    // (This is not considered good practice)
    globalThis[ref] = globalThis[ref] + 1;
}

var a = 1;
modify("a"); // We pass a reference to `a`
console.log(a);

In short, apart from some bad design in the old JavaScript language (in sloppy mode), it just is not possible in JavaScript in general. All your attempts that work, perform a mutation on a given object, by setting one of its properties. You cannot hope to have a function that assigns to a parameter variable, thereby modifying the caller's variable. It just is not possible -- by design. Note the important distinction between assignment and mutation.

If the caller's variable (not a property) needs to be assigned a new value (not just mutation, but really assignment), then that assignment must happen to that variable -- something a function cannot do for the caller.

So the way to perform such an assignment in JavaScript, is that you make the function return whatever the caller needs to reassign, and it remains the responsibility of the caller to perform that assignment:

function modify(value) {
    return 3;
}

let value = 1;
value = modify(value);
console.log(value);

When you have more than one variable that is involved, let the function return a "packed" object, which the caller can destructure back into its own variables:

function modify(a, b) {
    return [a + 1, b * 2];
}

let a = 1, b = 2;
[a, b] = modify(a, b);
console.log(a, b);

Sign up to request clarification or add additional context in comments.

3 Comments

So the general approach is, for every "reference" that gets passed in the pointer-based language, if it gets dereferenced and mutated, then in the transpiled language, return each of the dereferenced+mutated values along with any other return value, to update their values. Some examples here. Are there any cases where your approach really wouldn't make sense? Otherwise this might actually simplify a lot.
Because in JS-land, reassigning you are passing a new value, but in C/Rust-land, you are using an old pointer, so they seem slightly different and like there might be some edge cases to consider, but hopefully this approach will work.
I looked over the examples you referenced: indeed, that is how it would translate. (I added another "sloppy-mode" way it can work -- but don't use it). Just remember the principle. If the function makes an assignment to the parameter variable (not only mutation), then you need to apply this strategy to it.
2

JavaScript objects are passed "by reference":

const obj = {
  hello: "world"
};

console.log(obj.hello); // "world"
f(obj);
console.log(obj.hello); // "bob"

function f(s) {
  s.hello = "bob";
}

So if you want "references" like C or Rust or Swift put everything in objects. Consider

fn update(x) {
  x++
}

fn main() {
  let i = 0
  update(&i)
  log(i) #=> 1
}

In JavaScript it would be:

function update(obj) {
  obj.x++
}

const i = { x: 0 };
update(i);
console.log(i.x); // 1

Comments

0

Does this work or am I misunderstanding?

update2(x) {
  update(x)
}

update(x) {
  x.value++
}

main() {
  let i = { value: 0 }
  update2(i)
}

1 Comment

That would work, but I am trying to figure out if I have all the cases covered and/or if this would be a practical approach to doing this.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.