satyr/coco

export keyword

Opened this issue · 24 comments

I was wondering if it'd be possible to add an export keyword. The reason is that in some JavaScript environments (hint hint, QtScript) people tend to require certain variable on the global scope. However, with the QtScript transition to V8, adding them to the "this" property will become a strict mode error if you don't use .call on a method, furthermore, many implementations don't posses a window-like object for the global scope.

The solution is to use the bare mode, but this isn't desirable. Instead I would propose the use of a special keyword, export.

a = 1
function randomNumber
 ...
export function eventAttacked victim, attacker
 ...

Might compile to:

var eventAttacked;
(function(){
 var a;
 eventAttacked = function (victim, attacker) { ... }
 function randomNumber () { ... }
 a = 1;
})();

adding them to the "this" property will become a strict mode error

You mean, the global object is frozen by default in that environment or something? Then you shouldn't be able to add global variables at all.

Running

<script>
"use strict"
var a
Object.freeze(this)
a = 1
</script>

on Firefox 11 gives Error: a is read-only.

"use strict"

function f () {
 this.q = 3;
}
f()
console.log(q);
ryan@R26695g /tmp $ node file

node.js:201
        throw e; // process.nextTick error, or 'error' event on first tick
              ^
TypeError: Cannot set property 'q' of undefined
    at f (/tmp/file:4:9)
    at Object.<anonymous> (/tmp/file:6:1)
    at Module._compile (module.js:432:26)
    at Object..js (module.js:450:10)
    at Module.load (module.js:351:31)
    at Function._load (module.js:310:12)
    at Array.0 (module.js:470:10)
    at EventEmitter._tickCallback (node.js:192:40)

In strict mode, you can't use this inside a function that's not a property of an object or called with the call method. The compiler wraps all your code in a function. Using the bare option defeats the safeguards.

Edit: Fixed

That's why we use .call(this) rather than just ().

@satyr
Sorry, maybe this(coco example) better shows my point:

"use strict"

this.x = 0
x := 3 # error
x = 4 # wrong
function e
 x := 2 # error
 this.x = 3 # error

Yes, it can be gotten around, but there are times when your environment does stupid things...

x := 2 # error

How?

this.x = 3 # error

Depends on how e is called.

Well, you have to remove the line marked #wrong to get the error. It's an "undeclared variable" error.

And I don't want to call e with funny calls every time, or bind every function in my code. I think an export would be a better solution, it's much less coding.

Assuming your intention at this.x = 3 is to change global x,

global = this
function e
  global.x = 3

is what you want.

Isn't that rather verbose?

And the issue is when I want to do that with a function.
do f
export function f

I would have to opt for the much less readable global.f = function ... for every function in my code which needs to be put on the global object.

less readable global.f = function

On top level that's merely @f = ->.

I'm not always going to be on the top level, if I want to reference it, I have to make the global object a variable, or call all my functions using .call @

global = this
@a = !(b, c) ->
 d
function e
 global.a 3, 6

I think export would be easier, it hurts my eyes to see functions assigned, especially with all that extra stuff...

Edit: Fixed.

@a = (b, c) !->

Try @a = !(b, c) ->.

The @a syntax does NOT hoist the functions. I have multiple files, and if I run code in part of it, a function may be in another file. It might end up below code that requires it. A good example is a util file: The functions have to be hoisted, if they aren't, they wont be available to most files. (those that start with anything which comes before u.)

I'm aware you don't like using multiple files and joining them. But we don't all live in a node.js environment where we can require the other files.

Ah, hoisting. In that case you do need export function I guess. See #116 and the mockup I wrote there.

Looks good. Though I would make export work with regular variables as well.

I would personally rather have export exporting to exports if it exists (as in the #116 mockup, where __out is set to exports ? this).

Currently I'm using a modified version of CoffeeScript where export sets any expression that can have a name determined for on exports, which includes variables, classes and assignments. It looks like that:

macro func 'export', (expr) -> assign(
  objAccess (value 'exports'),
    if      expr instanceof Class  then expr.determineName()
    else if expr instanceof Assign then expr.variable
    else if expr instanceof Value  then expr
    else throw new Error 'Cannot determine export name'
  expr
)
$ bin/coffee -bep 'export foo = (a,b) -> c'
var foo;

exports.foo = foo = function(a, b) {
  return c;
};
$ bin/coffee -bep 'export bar'
exports.bar = bar;
$ bin/coffee -bep 'export class Baz'
var Baz;

exports.Baz = Baz = (function() {

  function Baz() {}

  return Baz;

})();

I've been using that for some time now and its been working out quite well for me.

We can further define:

export x, y         #=> export x; export y
export {x: y}       #=> __out <<< {x: y}
export [x, @y] = z  #=> what should this do?

export class Bar

The top level this equals exports in Node though, so you'd rather write class @Bar.

Is it possible to make the whole function return the exported variables? For when some environments use eval on your script...

Like how?

export function f
 a()
(function(){
 var __returned = false, __result = {}, __out = typeof exports != 'undefined' && exports || this;
 function f () { 
  a();
 }
 __out.f = f;
 if (!__returned ) __result.f = f;
 __returned = true;
 return __result; 
}).call(this);

Faster way might be to make the code return the "this" object, but that might cause problems.

Too bombastic for a relatively narrow use case.

I guess you can just add return this at the end. Ideally your environment should provide meaningful exports or this.

"use return_exports"
?
Then you don't even bother with the other code. (__out)

And for the record, one QtScript environment I script for DOES use eval on your script. It's stupid, I know. They all seem to be stupid somehow, seems to be a thing with programs which use QtScript.

Edit: Just realised this wont work, since there's no way to put the "use return_exports" at the top of the script for sure...

It does sound like hard to make QtScript code reusable in browser/CommonJS.

Maybe you can abuse hoisting:

$ coco -sp
  eval Coco.compile """
    export a = 1
    return exports
    function exports then
  """

{ [Function: exports] a: 1 }

export class Bar

The top level this equals exports in Node though, so you'd rather write class @Bar.

But than the constructor won't be accessible in scopes with different
this, unless you manually assign it to Bar too (Bar = class @Bar)

Hm, this at-name linking is kinda annoying.

the constructor won't be accessible in scopes with different this

It will:

$ coco -bcs
  class @Bar then  # same as: @Bar = Bar = class then

var Bar;
this.Bar = Bar = (function(){
  Bar.displayName = 'Bar';
  var prototype = Bar.prototype, constructor = Bar;
  function Bar(){}
  return Bar;
}());