Skip to content

inserting variable into the current lexical scope #292

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
weepy opened this issue Mar 27, 2010 · 31 comments
Closed

inserting variable into the current lexical scope #292

weepy opened this issue Mar 27, 2010 · 31 comments
Labels

Comments

@weepy
Copy link

weepy commented Mar 27, 2010

I think there might not be a way round this. I'm trying to insert variables from another scope into the current scope as local variables like so:

var vars = { a: 1, b: 2}
function mixin() {
  for(var i in vars)
    eval("var " + i + "=" + vars[i])
}
(function() {
  mixin()
  console.log(a)
})()

Obviously this doesn't work since "eval" is outside the closure. But there doesn't seem to be a way to reference the current lexical scope. Even mixin.call(this) wouldn't work.

As I said above, I'm not sure there's an easy round this - even for Coffee, but I'd be interested in all your ideas.

Cheers

J

@matehat
Copy link
Contributor

matehat commented Mar 27, 2010

Well, that'd really be feasible in coffeescript. We could have a global operator that inject the variables of an object into the local scope. If people show an interest, I'd be happy to produce a patch for that.

@weepy
Copy link
Author

weepy commented Mar 27, 2010

So how might you rewrite the example above?

@matehat
Copy link
Contributor

matehat commented Mar 27, 2010

We could have a keyword, maybe something like use or mixin or whatever, that, with an object, transforms into (disclaimer: naive implementation ...) :

use obj

=>

for (k in obj)
  eval("var " + k + "= obj[" + k + "]");

Which would imply you'd be able to write your example as :

vars: { a: 1, b: 2}

(->
  use vars
  console.log a
)()

@jashkenas
Copy link
Owner

matehat: I'd be interested in seeing your implementation as well. Tim_Smart and I have talked a lot about sandboxing in JS and the dynamic creation of local variables, but outside of with or eval, I don't know of a good way to create local variables in JavaScript when you don't know their name in advance.

If we could have dynamic local variables, it would greatly improve the process.mixin alternatives, and could really enable some nice DSL-style libraries without polluting the scope of other files.

@matehat
Copy link
Contributor

matehat commented Mar 27, 2010

Hmm, maybe I'm seeing to simple then. I might be missing something obvious :P

@weepy
Copy link
Author

weepy commented Mar 27, 2010

That might well be quite useful . It does use "eval" - but that doesn't really matter here.

@silentmatt
Copy link

This is basically the same thing that with does. The only difference I can see is that with won't overwrite any of the variables that are already in scope; it would just hide them.

@matehat
Copy link
Contributor

matehat commented Mar 28, 2010

that's the idea. Anyway, we had a talk on #coffeescript and decided, for performance issues (real ones), that it should not be in the core. Sorry weepy ...

@weepy
Copy link
Author

weepy commented Mar 28, 2010

I'd be interested to know what the performance issues were ? As far as I knew - the eval would occur just once.

@matehat
Copy link
Contributor

matehat commented Mar 28, 2010

It would for the usual case, but if it is in the core, it's kind of as if we proposed it as a good option in general, even in recurring or iterative situations. After some benchmarking, the ratio in performance between normal and evaled assignment is between 150 to 2000! That's pretty big ..

A thing that could make us overcome this is if the syntax were more precise on what we want to use from an object :

from vars use a, b

That would not require us to use the eval directive and we could avoid the perfomance issue. If people show an interest, that would be feasible.

@weepy
Copy link
Author

weepy commented Mar 28, 2010

Yes that's kinda cool. But I suppose it's not much terser than

a: vars.a; b: vars.b 

?
Regarding performance - I found that you could significantly improve performance by only performing 1 eval:

O = {a:1,b:2,c:function()  {} }

for(var i=0; i< 100000; i++) {  var a=O.a, b=O.b, b=O.c; }
244 ms
0.024ms per iteration

for(var i=0; i< 100000; i++) {  eval("var a=O.a, b=O.b, b=O.c;") }
570ms
0.057ms per iteration

Which is only twice as slow ? Given that a "using" statement wouldn't typically be called that often compared with looped code, I don't think that's signifcantly slow ?

@matehat
Copy link
Contributor

matehat commented Mar 28, 2010

What I thought would be nice in explicit injection like the above, is that it could offer the from a use * alternative, where we could emphasize on how more restrictive is better for most situation, regarding performances.

Sure, it does not look so much terser, but injecting 5 variables in your alternative does get kinda awkward. A direct application of this whole issue is module sandboxing and in that case, we don't always need 20 names or so to be imported and such a syntax would still look clean with 5-7 names injection.

@matehat
Copy link
Contributor

matehat commented Mar 28, 2010

In your example, I get 65k ops/sec for vanilla and 471 for eval'ed one .. so that's more a factor of around 140 times faster for vanilla.

@weepy
Copy link
Author

weepy commented Mar 28, 2010

Funny we get different amounts of performance - what browser are you running on (mine is FF 3.6 - so it probably ) ?

Point taken on the from .... idea, and the idea about from a use * is interesting- meaning that it would be easy to optimize it if you wanted to. (Perhaps: use * from A might read nicer)

We should also get a better handle on whether it really would be a performance issue - it really depends on use. It's not relative speed compared with naked assignment that's important here - but time taken relative to the rest of the functionality of the program.
My feeling is that really the "use" type keyword is only likely called a few times in a program's life cycle, (if you imagine how it's often used in something like Ruby) - so it might not be such an issue.

@weepy
Copy link
Author

weepy commented Mar 28, 2010

Here's a nice example of where this is useful for cleaning up, using matehat's suggestion:

(->
  MyClass: {
    map: (object, callback) -> 
      items: []
      each(object, (i) -> items.push(callback.apply(@, arguments))
      items
    each: (object, callback) -> 
      for v,i in object
        callback(i)
  }

  use MyClass *
  # or 
  use MyClass each, map

  # export
  @MyClass: MyClass
)()

As you can see it mixes in the module's method's to itself, meaning it can refer to them simply by itself without the MyClass prefix.

This example was adapted from : http://dailyjs.com/2010/03/19/private-vars-scope/

@StanAngeloff
Copy link
Contributor

With Node.js deprecating process.mixin everyone will rush to implement their own way to extend the current scope. with and eval are both evil. The closest thing that's working and is acceptable in speed is extending this -- given we all are aware of the fact we are not declaring local variables, but rather extending an object. I'd really like to see weepy's example compile to this:

use MyClass *
-->
for (var _a in MyClass) { if (__hasProp(MyClass, _a) {
    this[_a] = MyClass[_a];
}}
map(..) // implicit to this.map

As an alternative syntax, it would be nice to provide a way to copy properties onto another object:

use MyClass * on self
-->
var self = (typeof self !== "undefined" && self !== null) ? self : {};
for (var _a in MyClass) { if (__hasProp(MyClass, _a) {
    self[_a] = MyClass[_a];
}}
self.map(..) // explicit call on self

It shouldn't be too bad and as long as we are aware of the actual process and implementation.

@weepy
Copy link
Author

weepy commented Mar 29, 2010

Interesting Stan, but I think that extending this would make it much less flexible and useful, e.g see my example above. The problem being that this is often some global or other.

I'm working on an extension that uses eval for * (all) methods, or specified methods, if you are concerned about the efficiency of eval, or only need a few methods.

I think that gives enough flexibility either way.

@StanAngeloff
Copy link
Contributor

eval is not a preferred way in my book. I stay as far away from it as possible... but come to think of it -- the use of eval here is exactly what it was intended for, i.e., execute dynamic code. It should be possible to do this:

MyClass: { map: (->), first: (->) }
use MyClass *
    -->
    eval("var ${("$property = MyClass['$property']" for property of MyClass).join(', ')};")
    map() # function() { .. }

use MyClass map, first
    -->
    eval("var ${("$property = MyClass['$property']" for property in ['map', 'first']).join(', ')};")
    first() # function() { .. }

I hope this helps.

@weepy
Copy link
Author

weepy commented Mar 29, 2010

The second example can be written:

use MyClass map, first
    -->
    var map = MyClass.map, first = MyClass.first

which neatly avoids the eval.

@matehat
Copy link
Contributor

matehat commented Mar 29, 2010

I personally think the two could be used at once. We could write:

from obj use * on this    #=> direct assignment on `this`, no need for eval
from obj use *            #=> no choice but eval ...
from obj use name1, name2 #=> explicit names, no need for eval

That way, only one case needs eval and we could inject on object/classes as well as on scopes

@weepy
Copy link
Author

weepy commented Mar 30, 2010

ok - I made an extension that handles this : http://gist.github.com/348943 !!

Syntax is:

using MyModule *
using MyModule get, set, del

I left out the 'on this' type syntax for now. The issue I have with it is that while the other two methods affect local scope and so are somewhat contained, adding methods to 'this' is a bit more trixy and could cause problems elsewhere. If you really wanted to do this, you could do this using something like extend(this, MyModule)

edit: it seems to be able to compile each line ok, but blows up if it's used in multiple lines. I think it might be something to do with @i: + @chunk.length

@matehat
Copy link
Contributor

matehat commented Mar 30, 2010

Good job weepy.

About the on this comment, it thought it could really be any object, and so that syntax could be an handy way to extend any objects. this in particular would be useful in constructor methods, for instance, where it would act as a kind of primitive include method.

For the problem you are experiencing, you're splitting by single spaces the rest of all content (with the split method applied on the whole @chunk variable). You should stop parsing as soon as you encounter a newline.

@weepy
Copy link
Author

weepy commented Mar 30, 2010

Ah thanks matehat - I've updated the gist to fix that.

I see where you're coming from on the 'extends' functionality. We'd need to get the syntax right - so that it's clear that the object methods are being mixed into the current scope ?

include MyModule *
include MyModule get, set
include MyModule * on OtherObj

Any other ideas ?

@matehat
Copy link
Contributor

matehat commented Mar 30, 2010

To get enough attention, you should reopen the ticket, though. Being closed, we're the only ones reading it ;)

@StanAngeloff
Copy link
Contributor

Prolly good idea to extend the regex:

return false unless @chunk.match(/^using/)
-->
return false unless @chunk.match(/^using\s+([a-zA-Z\$_](\w|\$)*)/)

?

@jashkenas
Copy link
Owner

I'm getting the updates too. It's really neat that you've made an extension for it, but we've talked about it a couple of times in #coffeescript, and I still don't think that it makes sense to have an eval-dependent feature (and a special function) in the core language. Nice extension though.

@weepy
Copy link
Author

weepy commented Mar 30, 2010

Well I think it's misguided to be concerned about eval, as it doesn't really have any tangible downside (especially as it can be made specific if necessary via matehat's syntax). But obviously it's an area of clearly opinions, so it would make the most sense at the moment as an extension? Are they still on freeze?

@StanAngeloff
Copy link
Contributor

I've started working on a macros branch and the test file implements a simple from obj use ... macro. There is no from obj use * at this time, but even at this stage it's pretty useful. There are bugs, don't doubt that. I will look into ironing those out over the weekend. Here's an example:

obj: {
  method1: -> 'obj.method1'
  method2: -> 'obj.method2'
}

from obj use method1

 
var method1, method2, obj;
obj = { ..snip.. };
method1 = obj.method1;
method2 = obj.method2;

@weepy
Copy link
Author

weepy commented Apr 8, 2010

defo useful functionality - i use something similar in my JS all the time.

Trouble with the keywords from or use is that they are
often used as variables or functions. Perhaps using is a better choice?

I wanted to ask - what is the advantage of your macro implementation over the normal CoffeeScript.extend ? They appear to server similar functions ?

@StanAngeloff
Copy link
Contributor

The current macro can be renamed to using. The use identifier is actually skipped so you can write crazy imports like using Obj bring method1, method2. It's just a sample macro, so no validation is performed.

More in issue 313.

@weepy
Copy link
Author

weepy commented Apr 8, 2010

They look pretty interesting :-D

Nemerle is another language that seems to use macros extensively: http://nemerle.org/Macros

This issue was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

5 participants