Arc Forumnew | comments | leaders | submitlogin
1 point by evanrmurphy 5130 days ago | link | parent

> But why does it have to depart from arc's syntax?

The problem arises when you try to make use of Arc's dot ssyntax. Here are a couple jQuery examples using the old ArcScript, which is more ssyntactically faithful to Arc:

  ($.document.`ready (fn ()              $(document).ready(function() {
    ($!a.`click (fn ()                     $("a").click(function() {
      (alert "Hello world!")))))             alert("Hello world!");
                                           });
                                         });


  ($.document.`ready (fn ()              $(document).ready(function() {
    ((($ "#orderedlist")                   $("#orderedlist")
        `addClass) "red")))                  .addClass("red");
                                         });
While you can tell that the left side is supposed to be jQuery, it's completely non-obvious how the syntax transformation works. It takes too much effort every time you see the dot to remember that it has nothing to do with JavaScript's dot operator, but rather means:

  $.document => ($ document) => $(document)
You could just resolve not to use Arc's dot ssyntax because of the confusion with JavaScript's dot operator, but now you have this great unused character you can decide what to do with. Since this is a DSL for JavaScript, and JS's dot operator plays such a key role in idiomatic JavaScript, why not let Arc's dot compile to it?

> Also, does = inside a def creates a local variable?

This depends on whether you want a DSL that's really close to the metal of JavaScript, or one that makes some nice abstractions for you, like always guaranteeing lexical scope or making all statements valid expressions. I can see the utility of each. Maybe they could both be available as different modes you can toggle:

  ; Metal mode

  (def bar ()                     
    (var= pet "dog")                       
    (return                                 
      (alert                                ; Both compile to this JavaScript        
        (+ "my pet is a " pet)))            
                                            var bar = function() {
  ; Abstraction mode                          var pet = "dog";
                                              return alert("my pet is a " + pet);
  (def bar ()                               };
    (= pet "dog")                 
    (alert                        
      (+ "my pet is a " pet)))


1 point by rocketnia 5130 days ago | link

With "var bar = function() { ... };", I bet you run into self-recursion issues. Unless I'm mistaken, the variable "bar" isn't in the scope of the closure.

At the very least, (def bar ...) and (def foo ...) won't be corecursive, since foo isn't in bar's scope. That in particular is what the "function bar() { ... }" syntax is for. ^_^ I believe that kind of statement assigns to a variable that's implicitly declared at the top of the current scope, meaning it's visible to itself and all the other functions declared that way, without the need for a "var bar, foo; bar = ...; foo = ...;" idiom.

makes some nice abstractions for you, like always guaranteeing lexical scope

For me, that's one of those "why would I ever want the language to do this for me" things. ^_- Maybe this means I'm a Blub programmer, but I prefer explicitly declaring the new scope's variables rather than explicitly declaring which variables I take from the outer scope.

Declaring only globals is the worst, 'cause it implies not being able to shadow a local with another local of the same name. Ever since 'it, '_, and 'self, I've been accustomed to using the same variable names over and over, even in nesting scopes. Shame on me, but still. :-p

You can probably disregard most of this rant, 'cause it's probably just as annoying either way. XD Maybe someone else has an opinion though.

-----

1 point by evanrmurphy 5130 days ago | link

> With "var bar = function() { ... };", I bet you run into self-recursion issues. Unless I'm mistaken, the variable "bar" isn't in the scope of the closure.

You're right, thanks for the correction. There is an alternative to the "function bar() { ... }" syntax that allows for recursion, though:

  var bar;
  bar = function() {
    ...
  };
This is how CoffeeScript compiles function definitions, and it's what I meant to write. I'm not really sure yet about all the advantages and disadvantages when compared to the other format.

> You can probably disregard most of this rant, 'cause it's probably just as annoying either way.

Your rants are always welcome here! ^_^

---

Update: Sorry, rocketnia. Reading your comment more carefully I see you already talked about this:

> I believe that kind of statement assigns to a variable that's implicitly declared at the top of the current scope, meaning it's visible to itself and all the other functions declared that way, without the need for a "var bar, foo; bar = ...; foo = ...;" idiom.

I guess whether you use the explicit var declaration is largely a matter of taste. Declaring all variables explicitly at the top of the scope more transparent, but less concise. I suppose since CoffeeScript tends to declare other variables at the top of the scope, including functions variables in that group allows for a certain consistency.

-----

1 point by evanrmurphy 5120 days ago | link

> With "var bar = function() { ... };", I bet you run into self-recursion issues. Unless I'm mistaken, the variable "bar" isn't in the scope of the closure. At the very least, (def bar ...) and (def foo ...) won't be corecursive, since foo isn't in bar's scope.

Actually, both of these cases are working fine for me with the ``var bar = function() {...};`` format. This is testing in Chrome 5 on Linux:

  // recursion

  var foo = function(x) { 
    if (x == 0) 
      return "done"; 
    else 
      return foo(x - 1); 
  };

  foo(5)
  // => "done" 

  // corecursion

  var foo = function(x) { 
    if (x == 0) 
      return "done"; 
    else 
      return bar(x - 1); 
  };

  var bar = function(x) { 
    if (x == 0) 
      return "done"; 
    else 
      return foo(x - 1); 
  };

  foo(5)
  // => "done"
  bar(5)
  // => "done"
Am I missing something here?

-----

1 point by rocketnia 5119 days ago | link

Actually, you're right. ^^; In JavaScript, all variables declared in a function are visible throughout the function, even before the declaration statement.

  var foo = 4;
  
  (function(){
    alert( foo );  // "undefined"
    var foo = 2;
  })();
My mistake. ^_^;

-----

1 point by evanrmurphy 5130 days ago | link

> why would I ever want the language to do this for me

Hmm... so would you also prefer that return statements never be implicit?

I'd like to design the bottom layer operators of this compiler to have a one-to-one correspondence with JavaScript and zero automagic. This means explicit var declarations, explicit returns and an embrace of the expression/statement dichotomy. Then, we should be able to layer on top all desired forms of automagic using macros so that programmers can opt-in or opt-out as they like.

Or is it really naive to think it could work out this way? :P

-----

2 points by rocketnia 5130 days ago | link

Nah, that comment was only about function scope. I'm saying I really prefer the kind of scoping Arc and JavaScript both have, where variables shadow each other and closed-over variables don't need to be declared (but local ones do). I can't put my finger on why... but wait, pg can. :D

http://www.paulgraham.com/arclessons.html

Skip to "Implicit local variables conflict with macros."

As for implicit return statements, I don't know what you expect me to be, but I'm for them. ^_^ I think you can always say "return undefined;" in JavaScript to accomplish the same thing as leaving out the return statement, so in a way, leaving the return statement out is just syntactic sugar.

I'm for explicit return statements too, for the purposes of short-circuiting. However, they probably won't play well with macros, for the same reason as pg encountered with implicit local variables: Lexical scope boundaries might pop up in unexpected places and change the meaning of "return;".

> Or is it really naive to think it could work out this way? :P

Well, I do have lots of suggestions. They go pretty deep into the fundamental design too. That said, they sort of lead to not programming in Arc. You'll see what I mean in a second (unless I go scatterbrained on ya again).

I think it would be best to treat this as a combinator library. You can have utilities that build not only JavaScript expressions and statements,* but also other intermediate forms, like lists of statements. I wouldn't be surprised to see http://en.wikipedia.org/wiki/Control_flow_graph pop up at some point. As the combinator library goes on, it can introduce more convenient construction syntaxes, including full DSLs.

If this is beginning to sound similar to the topic I just posted, "A Language Should Target Moving Platforms"[1], that's because it is. We're talking about generating JavaScript code. It's very closely related to the topic of generating code in general. :-p

I was developing Penknife not too long ago. It's a language much like Arc, but what's important here is that its expressions are compiled and macro-expanded to an intermediate format first--i.e. parsed into an AST--and it's easy to make closures whose ASTs can be inspected. My plan is for the Penknife AST itself to be extensible using user-defined types, and my goal is for non-Penknife ASTs to be made using the same syntax technology used elsewhere in Penknife, right down to using the same compiler.

So yeah, this is my apprach to a JavaScript DSL. ^_^ I don't expect you to go adopt the whole approach, but if you do want to pursue it, or if there's some aspect of it that would fit into your design too, be my guest.

* Fine-grained control is fine by me. :-p So is automagic. I want it all!

[1] http://arclanguage.org/item?id=12939

-----

1 point by akkartik 5130 days ago | link

"Lexical scope boundaries might pop up in unexpected places and change the meaning of "return;"."

I couldn't believe this, but I looked around and yes, it's true, "..the scope container in javascript is a function." (http://mark-story.com/posts/view/picking-up-javascript-closu...)

That seems bad. Don't create a new function in js everytime I do a let in arc. But I don't know if you can avoid doing so.

Perhaps one way to do it would be to play a little fast and loose with semantics. Assume let translates to the scope of the containing function.

-----

1 point by evanrmurphy 5129 days ago | link

> I couldn't believe this, but I looked around and yes, it's true, "..the scope container in javascript is a function."

Yep! Function scope + no tail-call optimization = a formidable challenge. ^_^

> Perhaps one way to do it would be to play a little fast and loose with semantics. Assume let translates to the scope of the containing function.

Interesting approach. I'll play around with this. There might also be weird hacks that could simulate let without costing a whole function on the stack. For example, you might be able to get away with something like this:

  (let x 5         var _temp = true;
    ... )          while (_temp) {
                     _temp = false;
                     var x = 5;
                     ...
                   }
Or this:

  (let x 5         var _temp = true;
    ... )          for (var x = 5; _temp = false; _temp)                           
                     ...
                   }
But I'm not sure yet if these work, or if they're less expensive than the straightforward let translation:

  (let x 5         (function() {
    ... )            var x = 5;
                     ...
                   }).call(this);

-----

2 points by rocketnia 5129 days ago | link

I tried these out, and they don't seem to help, at least in Chrome:

  var x = 1;
  {
      var x = 2;
      var y = "but why!?";
      alert( x );           // 2
  }
  alert( x );               // 2
  alert( y );               // but why!?
  
  var x = 1;
  do
  {
      var x = 2;
      var y = "but why!?";
      alert( x );           // 2
  }
  while( false );
  alert( x );               // 2
  alert( y );               // but why!?
The next thing you might try is mangling variable names. If you expect the JavaScript code to never use threads (which it doesn't, I think), you could temporarily replace the variable values using assignment.

For a high-level (and non-idiomatic) approach, it's possible to implement trampolining using exceptions. I've done it in about a page of JavaScript (right next to my Fermat's Last Theorem proof, of course of course :-p ), and I think the API can be as simple as this:

  - Use bounce( foo, foo.bar, 1, 2 ,3 ) instead of
      foo.bar( 1, 2, 3 ) in tail positions. This throws an
      exception containing information about the next call
      to make.
  - To begin a trampolining loop, call the function with
      trampoline( foo, foo.bar, 1, 2, 3 ). This should be
      done when calling functions at the top level and when
      calling functions from within functions that are to be
      exposed to other JavaScript utilities and DOM event
      handlers (which won't call trampoline themselves).
Okay, so determining when a closure could "escape" into the outside world might be hard. O_o To avoid that trouble, uh, keep a global variable saying that a trampolining loop is already in progress, and use trampoline( ... ) for almost every single call? Well, that'll complicate things a little. ^_^;

-----

1 point by evanrmurphy 5129 days ago | link

> The next thing you might try is mangling variable names.

I think this could work pretty nicely, actually:

  (let x 5         var _x;
    ... )          _x = 5;
                   ...
Here's an example with a global and local variable:

  (= x 6)            var x, _x;
  (let x 5           x = 6;
    (alert x))       
  (alert x)          _x = 5;
                     alert(_x); // => 5

                     alert(x); // => 6
If you use multiple lets with the same variable name in one scope, you would need to differentiate with additional underscores or something:

  (let x 5           var _x, __x;
    (alert x))         
  (let x 6           _x = 5;
    (alert x))       alert(_x); // => 5

                     __x = 6;
                     alert(__x); // => 6
Of course, there is still some risk of variable name collision, and the output isn't super readable. Maybe it's good to have two operators, `let` and `let!`. The former uses function scope, so it's safer and compiles to more readable JavaScript, but it increases the stack size. The latter (let!) is the optimized version that uses one of the schemes we've been talking about.

-----

1 point by rocketnia 5129 days ago | link

What's your policy regarding the ability for people to say (let _x 5 ...) to spoof the mangler? ;) Also, will the name "let!" conflict with ssyntax?

These questions shouldn't keep anyone up nights, but I thought I'd point them out.

-----

1 point by evanrmurphy 5129 days ago | link

> What's your policy regarding the ability for people to say (let _x 5 ...) to spoof the mangler? ;)

The underscore prefix system would be nice because it's pretty readable, but it's not necessary. In the worst case, we can use gensyms. I thought using underscores for internal variables might work because CoffeeScript does it [1], but maybe it won't. So you raise a good point. :)

> Also, will the name "let!" conflict with ssyntax?

Yes. I'm starting from scratch with the ssyntax. `!` is a regular character now. It can be used in names like `let!`, and when by itself in functional position it compiles to the JavaScript negation operator:

  (! x)   =>   !x
`.`, `:`, `~`, `[]`, `&` and friends are out, too. This may displease my Arc buddies, but I just found it too difficult to think in JavaScript when the gap in meaning between Arc and JS for these characters was so large. `.` compiles to the JS dot operator now, `{}` to the JS object literal and `[]` to the array literal (see [2] for some details). Note that quote ('), quasiquote (`), unquote (,), unquote-splicing (,@) and string quoting (") should all work as expected.

---

[1] http://jashkenas.github.com/coffee-script/#comprehensions

[2] http://arclanguage.org/item?id=12948

-----

1 point by akkartik 5130 days ago | link

Probably just different aesthetics. I sense that you're thinking in javascript and rocketnia is thinking in arc.

-----