Is it possible to achieve dynamic scoping in JavaScript without resorting to eval?
Asked Answered
C

7

28

JavaScript has lexical scoping which means that non-local variables accessed from within a function are resolved to variables present in the parents' scope of that function when it was defined. This is in contrast to dynamic scoping in which non-local variables accessed from within a function are resolved to variables present in the calling scope of that function when it is called.

x=1
function g () { echo $x ; x=2 ; }
function f () { local x=3 ; g ; }
f # does this print 1, or 3?
echo $x # does this print 1, or 2?

The above program prints 1 and then 2 in a lexically scoped language, and it prints 3 and then 1 in a dynamically scoped language. Since JavaScript is lexically scoped it will print 1 and then 2 as demonstrated below:

var print = x => console.log(x);

var x = 1;

function g() {
    print(x);
    x = 2;
}

function f() {
    var x = 3;
    g();
}

f();           // prints 1

print(x);      // prints 2

Although JavaScript doesn't support dynamic scoping we can implement it using eval as follows:

var print = x => console.log(x);

var x = 1;

function g() {
    print(x);
    x = 2;
}

function f() {
    // create a new local copy of `g` bound to the current scope
    // explicitly assign it to a variable since functions can be unnamed
    // place this code in the beginning of the function - manual hoisting
    var g_ = eval("(" + String(g) + ")");
    var x = 3;
    g_();
}

f();                         // prints 3

print(x);                    // prints 1

I would like to know if there exists another possible way to achieve the same result without resorting to eval.

Edit: This is what I'm trying to implement without using eval:

var print = x => console.log(x);

function Class(clazz) {
    return function () {
        var constructor;
        var Constructor = eval("(" + String(clazz) + ")");
        Constructor.apply(this, arguments);
        constructor.apply(this, arguments);
    };
}

var Rectangle = new Class(function () {
    var width, height;

    constructor = function (w, h) {
        width = w;
        height = h;
    };

    this.area = function () {
        return width * height;
    };
});

var rectangle = new Rectangle(2, 3);
print(rectangle.area());

I know that it's not a very good example but the general idea is to use dynamic scoping to create closures. I think this pattern has a lot of potential.

Crossways answered 8/4, 2012 at 6:26 Comment(8)
Nicely written, interesting question. Though it asks for an objective answer, unless someone shows how it can be done, I suspect it will provoke a lot of debate and subjective answers of why it can't or shouldn't be done. What brought you to this question?Dorothydorp
@ChrisWesseling - I updated my question to show what made me post it. The above program is perfectly valid and works across all platforms. I believe it has a lot of potential to create class patterns and lots of other things. It's up to the general public to use it wisely. Perhaps one of the only valid reasons to use eval.Crossways
The reason I want to use dynamic scoping is so that I can inject private variables into the scope of a function. For example, in my above program I can also pass a variable called uber that points to the parent of the given class. This variable should be accessible to the class scope but it shouldn't be accessible to the public. Hence I can't just set it on the instance of the class and call it a day. Thus the roundabout workaround.Crossways
similar question: stackoverflow.com/questions/10031399Lewan
Will the below code work for you? var x=1; function g () { print(this.x); this.x=2; } function f () { var x=3 ; this.g(); } print(x);Sickler
@RajkumarMasaniayan - No. I'm afraid that your code is terribly convoluted. The function g can't access the local variables of f unless it's defined in f. That's the reason I did var g = eval(String(g)). In your case it's just like my first example above (the one without dynamic scoping). Even worse you're explicitly setting global variables by using this.Crossways
@Aadit - The following code does what you want but I am not sure if this is generic enough and handle all the edge cases. Am specifying it here, just in case. var test = function() { this.x=1; var g = function g () { console.log(this.x); this.x=2; } var f = function f () { this.x=3; g.call(this); } this.f = f; new f(); console.log(this.x); } new test();Sickler
@RajkumarMasaniayan - No it doesn't do what I want it to. What I want is to be able to modify private variables, not properties of this. I'm pretty sure using eval is the only way to do that. However like every programmer should I have learned to embrace the language instead of fighting it. So I don't need dynamic scoping anymore. I've found a better solution to my problem in functional programming, and I've used my newfound knowledge to create a very small and efficient JavaScript library for object oriented and functional programming - augment. =)Crossways
W
10

Attribute lookup falls through the prototype chain, which matches quite well to dynamic scopes. Just pass your own environment of dynamically-scoped variables to use around instead of using Javascript's lexical scoping.


// Polyfill for older browsers.  Newer ones already have Object.create.
if (!Object.create) {
  // You don't need to understand this, but
  Object.create = function(proto) {
    // this constructor does nothing,
    function cons() {}
    // and we assign it a prototype,
    cons.prototype = proto;
    // so that the new object has the given proto without any side-effects.
    return new cons();
  };
}

// Define a new class
function dyn() {}
// with a method which returns a copy-on-write clone of the object.
dyn.prototype.cow = function() {
  // An empty object is created with this object as its prototype.  Javascript
  // will follow the prototype chain to read an attribute, but set new values
  // on the new object.
  return Object.create(this);
}

// Given an environment, read x then write to it.
function g(env) {
  console.log(env.x);
  env.x = 2;
}
// Given an environment, write x then call f with a clone.
function f(env) {
  env.x = 3;
  g(env.cow());
}

// Create a new environment.
var env = new dyn();
// env -> {__proto__: dyn.prototype}
// Set a value in it.
env.x = 1;
// env -> {x: 1}  // Still has dyn.prototype, but it's long so I'll leave it out.

f(env.cow());
// f():
//   env -> {__proto__: {x: 1}}  // Called with env = caller's env.cow()
//   > env.x = 3
//   env -> {x: 3, __proto__: {x: 1}}  // New value is set in current object
//   g():
//     env -> {__proto__: {x: 3, __proto__: {x: 1}}}  // caller's env.cow()
//     env.x -> 3  // attribute lookup follows chain of prototypes
//     > env.x = 2
//     env -> {x: 2, __proto__: {x: 3, __proto__: {x: 1}}}

console.log(env.x);
// env -> {x: 1}  // still unchanged!
// env.x -> 1
Wes answered 8/4, 2012 at 7:8 Comment(7)
Your code is very confusing. Mind providing meaningful comments to explain what you are doing?Crossways
@AaditMShah Commented. And go read an Introduction to Object-Oriented JavaScript.Wes
Alright I finally understood your code. You are using a constructor called dyn to simulate a global scope. Every function has a formal parameter called env which is equivalent to the activation object of that function. This env object is supplied by the caller. For the function f we supply the global instance of dyn. For g we supply the copy-on-write instance of the global dyn pushing the first onto the prototype chain. It's an intuitive answer and scope lookup does resemble traversing a prototype chain. Plus one for finding an alternative instead of resorting to using eval. =)Crossways
Thank you for sharing that link with me too - although I didn't learn anything I didn't already know from it, it was a nice gesture. Appreciated.Crossways
@AaditMShah Glad to help. Actually, my first thought (using with to extend the scope) didn't work… which is all for the better, since it's a terrible feature of Javascript :)Wes
I'm accepting this answer since it directly answers my question. However I still prefer using eval rather than passing an activation object around. Too much bookkeeping. I guess this is one of those cases in which using eval is really not evil and the benefits of simulating dynamic scopes shadows any performance losses. If used wisely it won't have any negative impact on the rest of the code. Thank you for your help though. =)Crossways
this answer is barely readableVaulted
L
12

To add a note on this topic:

In JavaScript whenever you make use of:

  • function declaration statement or function definition expression then local variables will have Lexical Scoping.

  • Function constructor then local variables will refer to the global scope (top-level code)

  • this is the only built-in object in JavaScript that has a dynamic scoping and is set through the execution (or invocation) context.

So to answer to your question, In JS the this is already dynamically scoped feature of the language and you even don't need to emulate another one.

Larimore answered 10/5, 2013 at 15:48 Comment(1)
I'm grateful to know this. Thank you.Crossways
W
10

Attribute lookup falls through the prototype chain, which matches quite well to dynamic scopes. Just pass your own environment of dynamically-scoped variables to use around instead of using Javascript's lexical scoping.


// Polyfill for older browsers.  Newer ones already have Object.create.
if (!Object.create) {
  // You don't need to understand this, but
  Object.create = function(proto) {
    // this constructor does nothing,
    function cons() {}
    // and we assign it a prototype,
    cons.prototype = proto;
    // so that the new object has the given proto without any side-effects.
    return new cons();
  };
}

// Define a new class
function dyn() {}
// with a method which returns a copy-on-write clone of the object.
dyn.prototype.cow = function() {
  // An empty object is created with this object as its prototype.  Javascript
  // will follow the prototype chain to read an attribute, but set new values
  // on the new object.
  return Object.create(this);
}

// Given an environment, read x then write to it.
function g(env) {
  console.log(env.x);
  env.x = 2;
}
// Given an environment, write x then call f with a clone.
function f(env) {
  env.x = 3;
  g(env.cow());
}

// Create a new environment.
var env = new dyn();
// env -> {__proto__: dyn.prototype}
// Set a value in it.
env.x = 1;
// env -> {x: 1}  // Still has dyn.prototype, but it's long so I'll leave it out.

f(env.cow());
// f():
//   env -> {__proto__: {x: 1}}  // Called with env = caller's env.cow()
//   > env.x = 3
//   env -> {x: 3, __proto__: {x: 1}}  // New value is set in current object
//   g():
//     env -> {__proto__: {x: 3, __proto__: {x: 1}}}  // caller's env.cow()
//     env.x -> 3  // attribute lookup follows chain of prototypes
//     > env.x = 2
//     env -> {x: 2, __proto__: {x: 3, __proto__: {x: 1}}}

console.log(env.x);
// env -> {x: 1}  // still unchanged!
// env.x -> 1
Wes answered 8/4, 2012 at 7:8 Comment(7)
Your code is very confusing. Mind providing meaningful comments to explain what you are doing?Crossways
@AaditMShah Commented. And go read an Introduction to Object-Oriented JavaScript.Wes
Alright I finally understood your code. You are using a constructor called dyn to simulate a global scope. Every function has a formal parameter called env which is equivalent to the activation object of that function. This env object is supplied by the caller. For the function f we supply the global instance of dyn. For g we supply the copy-on-write instance of the global dyn pushing the first onto the prototype chain. It's an intuitive answer and scope lookup does resemble traversing a prototype chain. Plus one for finding an alternative instead of resorting to using eval. =)Crossways
Thank you for sharing that link with me too - although I didn't learn anything I didn't already know from it, it was a nice gesture. Appreciated.Crossways
@AaditMShah Glad to help. Actually, my first thought (using with to extend the scope) didn't work… which is all for the better, since it's a terrible feature of Javascript :)Wes
I'm accepting this answer since it directly answers my question. However I still prefer using eval rather than passing an activation object around. Too much bookkeeping. I guess this is one of those cases in which using eval is really not evil and the benefits of simulating dynamic scopes shadows any performance losses. If used wisely it won't have any negative impact on the rest of the code. Thank you for your help though. =)Crossways
this answer is barely readableVaulted
R
2

I don't think so.

That is not how the language works. You have to use something other than variables to refer to this state information. The most "natural" way being to use properties of this, I guess.

Reformatory answered 8/4, 2012 at 6:34 Comment(0)
D
2

In your case, instead of trying to use dynamic scoping to set the constructor, what if you used the return value?

function Class(clazz) {
    return function () {
        clazz.apply(this, arguments).apply(this, arguments);
    };
}

var Rectangle = new Class(function () {
    var width, height;

    this.area = function () {
        return width * height;
    };

    // Constructor
    return function (w, h) {
        width = w;
        height = h;
    };
});

var rectangle = new Rectangle(2, 3);
console.log(rectangle.area());
Diseuse answered 8/4, 2012 at 7:58 Comment(1)
I could do that but as I explained in my last comment on my question - using dynamic scoping allows me to inject as many variables as I want into the scope of a function. You can only return one value.Crossways
A
1

Why didn't anybody say this?

You can pass variables from the calling scope into the called function by binding it a context.

function called_function () {
   console.log(`My env ${this} my args ${arguments}`, this, arguments);
   console.log(`JS Dynamic ? ${this.jsDynamic}`);
}

function calling_function () {
   const env = Object.create(null);
   env.jsDynamic = 'really?';

   ... 

   // no environment
   called_function( 'hey', 50 );

   // passed in environment 
   called_function.bind( env )( 'hey', 50 );

Perhaps it's worth mentioning that in strict mode, all functions have no "environment" sent to them by default (this is null). In non strict mode the global object is the default this value for a called function.

Abbatial answered 12/6, 2015 at 14:11 Comment(2)
People already did say this: Arman, Thilo. If you think about it this is just another argument. So, you don't really need this either. Just use an extra parameter. That's exactly what ephemient describes in his answer and that's the reason I accepted it. Your answer doesn't really add any value.Crossways
I guess you could see it that way.Abbatial
I
0

You can simulate dynamic scoping using global variables, if you have a way to do syntactic sugar (e.g. macros with gensyms) and if you have unwind-protect.

The macro can appear to rebind the dynamic variable by saving its value in a hidden lexical and then assigning a new value. The unwind-protect code ensures that no matter how that block terminates, the original value of the global will be restored.

Lisp pseudocode:

(let ((#:hidden-local dynamic-var))
  (unwind-protect
    (progn (setf dynamic-var new-value)
           body of code ...)
    (set dynamic-var #:hidden-local)))

Of course, this is not a thread-safe way of doing dynamic scope, but if you aren't doing threading, it will do! We would hide it behind a macro like:

(dlet ((dynamic-var new-value))
   body of code ...)

So if you have unwind-protect in Javascript, and a macro preprocessor to generate some syntactic sugar (so you're not manually open-coding all your saves and unwind-protected restores) it might be doable.

Imprison answered 8/4, 2012 at 6:34 Comment(5)
Would you please care to elaborate in simpler terms? Perhaps link me to an online resource?Crossways
I updated the comment with a code illustration using Lisp; hope that helps.Imprison
I guess I better start learning Lisp now. Thanks for the help though. Appreciated. =)Crossways
Looks like JavaScript's unwind protect is try { ... } finally { ... }. So you would set the new value in a try block, and then restore the variable to its saved value in finally. JavaScript has no macros, but a text-to-text preprocessor of some kind could be used. JavaScript coders use such things anyway, like the Closure "compiler" that condenses JavaScript code.Imprison
That's not really an option. Macro preprocessors work at compile time. What I need is a runtime solution.Crossways
L
0

I know this doesn't exactly answer the question but it's too much code to put into a comment.

As an alternative approach, you may want to look into ExtJS's extend function. This is how it works:

var Rectangle = Ext.extend(Object, {
    constructor: function (w, h) {
        var width = w, height = h;
        this.area = function () {
            return width * height;
        };
    }
});

With public properties instead of private variables:

var Rectangle = Ext.extend(Object, {
    width: 0,
    height: 0,  

    constructor: function (w, h) {
        this.width = w;
        this.height = h;
    },

    area: function () {
        return this.width * this.height;
    }
});
Lewan answered 8/4, 2012 at 9:27 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.