Live data from Hacker News

Sweet.js: Hygienic Macros for JavaScript

github.com

11–20 of 21 posts

Re: Sweet.js: Hygienic Macros for JavaScript

#11
post #10
post #9

Earlier quoted context omitted.

> Clojure does not have these issues: when the macro is called, the symbols are already attributed with the full namespace qualification, and usage of quasi-quote inside of the macro definition will also apply namespace qualification to variables local to the definition of the macro; you have to go out of your way to break this. Again, not a Clojure expert, but a namespace is coarser-grained than individual local sco…

Correct: I did not hear the private conversation. I only heard the talk that was made public along with this project that was posted here, and which was recommended as an information source, and pretty much serves as the web page and documentation for this project ;P. > Again, not a Clojure expert, but a namespace is coarser-grained than individual local scopes, right? The problem I'm talking about is when you have a…

> Ok, so are you are concerned with the case where the person defining the macro uses a symbol from the namespace of the person using the macro but that symbol has been rebound by the user inside of a let surrounding the aforementioned usage of the macro?

> If so, that requires a cyclic module dependency...

Not necessarily. For example (forgive the Scheme syntax), all in one module:

    (define thing "outer thing")
    ;; define-inline is the above macro-defining-macro
    (define-inline (foo prefix)
      (string-append prefix thing))
    (let ((thing "inner thing"))
      (foo "should say outer thing: "))

Re: Sweet.js: Hygienic Macros for JavaScript

#12
post #11
post #10

Earlier quoted context omitted.

Correct: I did not hear the private conversation. I only heard the talk that was made public along with this project that was posted here, and which was recommended as an information source, and pretty much serves as the web page and documentation for this project ;P. > Again, not a Clojure expert, but a namespace is coarser-grained than individual local scopes, right? The problem I'm talking about is when you have a…

> Ok, so are you are concerned with the case where the person defining the macro uses a symbol from the namespace of the person using the macro but that symbol has been rebound by the user inside of a let surrounding the aforementioned usage of the macro? > If so, that requires a cyclic module dependency... Not necessarily. For example (forgive the Scheme syntax), all in one module: (define thing "outer thing") ;; de…

That seems to be my "if not," case, which I provided some examples for; if this is different, can you please be more explicit? It seems like your "thing" is my "t" and your "foo" is my "run": the only difference is then that I went out of my way to make it more complex my passing the inner thing through the macro to demonstrate it wouldn't get mangled.

    (def thing "outer thing")
    (defmacro foo [prefix]
        `(str ~prefix thing))
    (let [thing "inner thing"]
        (prn (foo "should say outer thing: ")))
"should say outer thing: outer thing"

(edit:) Alternatively, maybe you are focussing on the define-inline "macro-defining macro"; you mentioned it here again as "the above", and you had used it above, but as it wasn't defined it didn't seem important. I tried to go ahead and implement it, although to be honest I feel like I did it wrong (spending more time thinking about it, I believe it is correct, modulo your definition of "inline"); that said, it "worked".

    (defmacro def-inline [[name & args] code]
        `(defmacro ~name ~(apply vector args)
            ~code))

    (def thing "outer thing")
    (def-inline [foo prefix]
        (str prefix thing))
    (let [thing "inner thing"]
        (prn (foo "should say outer thing: ")))
"should say outer thing: outer thing"

Re: Sweet.js: Hygienic Macros for JavaScript

#13
post #12
post #11

Earlier quoted context omitted.

> Ok, so are you are concerned with the case where the person defining the macro uses a symbol from the namespace of the person using the macro but that symbol has been rebound by the user inside of a let surrounding the aforementioned usage of the macro? > If so, that requires a cyclic module dependency... Not necessarily. For example (forgive the Scheme syntax), all in one module: (define thing "outer thing") ;; de…

That seems to be my "if not," case, which I provided some examples for; if this is different, can you please be more explicit? It seems like your "thing" is my "t" and your "foo" is my "run": the only difference is then that I went out of my way to make it more complex my passing the inner thing through the macro to demonstrate it wouldn't get mangled. (def thing "outer thing") (defmacro foo [prefix] `(str ~prefix th…

Interesting. I don't see how this works. I wonder, is it different if you do this?

    (let [thing "outer thing"]
      (defmacro foo [prefix]
          `(str ~prefix thing))
      (let [thing "inner thing"]
          (prn (foo "should say outer thing: "))))
Dave

Re: Sweet.js: Hygienic Macros for JavaScript

#14
OK, either your description page doesn't get your point across at all, or you're missing a definition somewhere.

'Wish the function keyword in JavaScript wasn't so long? What if you could define functions with def instead?'

Erh, no? Why in the lord's name would I ? Is that your big selling point? 'I don't like function() because it's too long?'

'Want a better way to make "classy" objects?'

Why would you want to make javascript less like javascript, introduce a dependency to javascript that can read your language, and then compiles back into javascript in realtime, in a way that will obviously make debugging nearly impossible (like coffeescript)?

Am I the only one that doesnt understand the use case of this? Or should this have been presented as just another lexer/parser?

Re: Sweet.js: Hygienic Macros for JavaScript

#15
post #13
post #12

Earlier quoted context omitted.

That seems to be my "if not," case, which I provided some examples for; if this is different, can you please be more explicit? It seems like your "thing" is my "t" and your "foo" is my "run": the only difference is then that I went out of my way to make it more complex my passing the inner thing through the macro to demonstrate it wouldn't get mangled. (def thing "outer thing") (defmacro foo [prefix] `(str ~prefix th…

Interesting. I don't see how this works. I wonder, is it different if you do this? (let [thing "outer thing"] (defmacro foo [prefix] `(str ~prefix thing)) (let [thing "inner thing"] (prn (foo "should say outer thing: ")))) Dave

That is not possible as written, because the "defmacro" is not executed to define the macro until after the outer let is already executing, which is after macro expansion of that form (and thereby its children), as it has already been read: so what I get for that is a really weird error that I'm passing too many arguments to "foo", as if it were a function (which it is not; albeit I'm not certain what it is ;P).

However, I can use the def-inline that I wrote in the edit to my earlier reply to demonstrate that if you reorganized this code in a way that was semantically equivalent but hoisted the macro, it would work the way you think it should: the definition of the thing from the let surrounding the macro-ish definition is used, not the one from the call site (or the global one in the namespace).

    (defmacro def-inline [[name & args] code]
        `(defmacro ~name ~(apply vector args)
            ~code))

    (def thing "outer thing")
    (let [thing "middle thing"]
        (def-inline [foo prefix]
            (str prefix thing)))
    (let [thing "inner thing"]
        (prn (foo "should say middle thing: ")))
"should say middle thing: middle thing"

(edit:) Oh, that wasn't semantically equivalent, as the second let is not inside of the first. However, if I do that, I get the same behavior as I get in the other case (that it doesn't actually expand the macro at all and treats the form as a function call), as I'm obviously just defining the macro again inside of the same already-read form in which I'm using it.

(further:) Okay, and the reason why that is working is that the way I wrote def-inline inlined the code from def-inline into the macro itself. That is probably not what you wanted from def-inline: this is more like def-const (or def-static or something). I thereby tried doing this instead:

    (let [thing "middle thing"]
        (defmacro foo [prefix]
            `(str ~prefix thing)))
    (let [thing "inner thing"]
        (prn (foo "should say middle thing: ")))
This, in fact, does not return the "correct" string: instead, it fails to work at all, as the "thing" used inside of the macro is supposedly not defined (and worse, if I have a global def for "thing", I get that value). So, this is is a case where the macro is unable to use bindings that are local to the time when the macro definition is executed: it can only deal with global-ish names.

For the record, I think that is unrelated to what I normally think of with relation to hygiene: the macro is able to modify the code using it without accidental capture, but humorously it, itself, is unable to take advantage of symbols that have been bound locally around it. I totally accept that this is probably a flaw (I only say "probably" as I'm willing to believe someone from Clojure can convince me otherwise; it certainly seems like a flaw, though).

(more:) I am increasing the weight of that "probably", as I'm noting that the person calling this code has absolutely no way to refer to the thing that I have access to: there is no path no matter how complex or awesome that would let it refer to my "middle thing". I can inline it with ~, but then it isn't a binding anymore; however, that's actually equivalent, as Clojure does not have setf: "thing" is a constant, and so even if I had a function inside of this let closed over that thing, I couldn't modify its value.

Re: Sweet.js: Hygienic Macros for JavaScript

#16
post #6
post #4

Despite spending a lot of time both in the design Wiki and in the talk discussing the importance of being able to determine whether a / indicates a regular expression literal or a division operator entirely within the lexer (as opposed to using the parser, which is how JavaScript is generally defined), the algorithm that this developer implemented does not actually work. First off, an example where it works: a /5/ 7…

Yeah there are certainly a few bugs remaining in the reader :) It actually does the right thing if the function is named: a = function foo() {} /5/ 7 correctly translates to: a = function foo() { } / 42 / 7; But clearly I missed the unnamed case. You mentioned finding a few other bugs? Would you mind submitting a bug report on github? I would love to fix those too!

Sorry, was distracted by the other conversation with dherman, and really shouldn't be spending any time on this anyway, but I've verified your operator associativity is wrong.

To start with some example code:

    for (var a = 7 in /7/ in 9 in b);
If I run that in node I get:

    TypeError: Cannot use 'in' operator to search for '/7/' in 9
As this code is vaguely equivalent to:

    var a = 7; for (a in ((/7/ in 9) in b));
However, it gets converted by sjs to:

    for (var a = 7 in (/7/ in (9 in b)));
That's obviously different, and gives this error instead:

    ReferenceError: b is not defined
(I really should get back to actually doing my job now, though; if dherman responds again I'll totally notice and follow up: that conversation is really interesting to me.)

Re: Sweet.js: Hygienic Macros for JavaScript

#17

OK, either your description page doesn't get your point across at all, or you're missing a definition somewhere. 'Wish the function keyword in JavaScript wasn't so long? What if you could define functions with def instead?' Erh, no? Why in the lord's name would I ? Is that your big selling point? 'I don't like function() because it's too long?' 'Want a better way to make "classy" objects?' Why would you want to make…

Simple examples are good for people to understand; however, the reason people want macros is usually for much more complex situations where you otherwise end up with a lot of boilerplate or repetitive code: a macro is most simply a way for code to manipulate and generate other code.

Regardless, if you don't like macros (and your arguments are common among people who don't; that said, they are also common arguments against using higher-level languages or even functions), then you won't like this project or any other project attempting to provide similar functionality. ;P

Re: Sweet.js: Hygienic Macros for JavaScript

#18
post #15
post #13

Earlier quoted context omitted.

Interesting. I don't see how this works. I wonder, is it different if you do this? (let [thing "outer thing"] (defmacro foo [prefix] `(str ~prefix thing)) (let [thing "inner thing"] (prn (foo "should say outer thing: ")))) Dave

That is not possible as written, because the "defmacro" is not executed to define the macro until after the outer let is already executing, which is after macro expansion of that form (and thereby its children), as it has already been read: so what I get for that is a really weird error that I'm passing too many arguments to "foo", as if it were a function (which it is not; albeit I'm not certain what it is ;P). Howe…

I'd forgotten about the whole Lisp tradition of expanding + evaluating one form at a time -- once again betraying my Scheme biases. :) That's making it hard for me to think about how to compare the two. I'm really not familiar enough with the one-form-at-a-time approach.

Anyway, my takeaways here are:

- I shouldn't've said anything about Clojure specifically, because a) the design space is different and b) they have some form of hygiene-like something-or-other that I don't know enough about. Gotta go study them!

- I still don't know how to do hygiene in Scheme-like languages any other way than the approaches I've seen, but I do agree they're more complicated than I wish they were.

Dave

Re: Sweet.js: Hygienic Macros for JavaScript

#19

OK, either your description page doesn't get your point across at all, or you're missing a definition somewhere. 'Wish the function keyword in JavaScript wasn't so long? What if you could define functions with def instead?' Erh, no? Why in the lord's name would I ? Is that your big selling point? 'I don't like function() because it's too long?' 'Want a better way to make "classy" objects?' Why would you want to make…

> OK, either your description page doesn't get your point across at all, or you're missing a definition somewhere.

The description page (like the rest of the project at them moment) is definitely a work in process. Sorry about the confusion!

> 'Wish the function keyword in JavaScript wasn't so long? What if you could define functions with def instead?'

> Erh, no? Why in the lord's name would I ? Is that your big selling point? 'I don't like function() because it's too long?'

This is just a really basic example of what you can do. Obviously you could have the same effect with sed but we need something simple to shows what basic macro definitions look like.

> 'Want a better way to make "classy" objects?'

>

I'm assuming you refer to the macro definition with $ and whatnot. If you have ideas for better syntax please contribute! The notation is by no means final. That said I think it's pretty good for what it needs to do, matching syntax patterns and transforming them into new bits of syntax. Certainly more readable than a full parser/compiler.

> Am I the only one that doesnt understand the use case of this? Or is it just another lexer/parser?

The idea is to provide a middle way between no syntax extensions and writing your own full compile-to-js language languages like CoffeeScript which allow you to add any kind of syntax you want, but at the expense of being able to compose extensions. If you like the class notation of CS you must buy into all the syntax choices of CS whereas macros allow you to add just the syntax you want.

Whether you want new syntax is of course a different question. Other languages like scheme and closjure have found macros useful so sweet.js is an experiment to find out if the same syntactic extensibility that they provide is useful in JavaScript.

Hope that helps!

Re: Sweet.js: Hygienic Macros for JavaScript

#20
post #16
post #6

Earlier quoted context omitted.

Yeah there are certainly a few bugs remaining in the reader :) It actually does the right thing if the function is named: a = function foo() {} /5/ 7 correctly translates to: a = function foo() { } / 42 / 7; But clearly I missed the unnamed case. You mentioned finding a few other bugs? Would you mind submitting a bug report on github? I would love to fix those too!

Sorry, was distracted by the other conversation with dherman, and really shouldn't be spending any time on this anyway, but I've verified your operator associativity is wrong. To start with some example code: for (var a = 7 in /7/ in 9 in b); If I run that in node I get: TypeError: Cannot use 'in' operator to search for '/7/' in 9 As this code is vaguely equivalent to: var a = 7; for (a in ((/7/ in 9) in b)); However…

Thanks for taking the time to write these up! I'm tracking them here [1]. The first two should be fixed and I should have the third ready soon.

[1] https://github.com/mozilla/sweet.js/issues/18

Post reply on HN