20 ms·
Stop Writing JavaScript Compilers, Make Macros Instead
- johmacindu 13y agoDon't tell me what to do. If I want to write a JS compiler, I can write a JS compiler, and it's nobody's business to tell me not to.
- pork 13y agoDon't post garbage on Hacker News.
- pork 13y agoVery interesting! I've often thought that Javascript could actually be a pretty language with some syntactic sugar. It'd be even more interesting if you could run it client-side without preprocessing an sjs file into js first, sort of like how you can include LESS stylesheets and load a JS module to compile them in the browser.
- chc 13y agoThis is basically just saying "Choose Sweet.js in combination with some unknown array of macro libraries as your only language that compiles to JavaScript." But because the article doesn't really frame it this way, it doesn't really explain to me why I would choose Sweet.js over ClojureScript or Scala.js or Black Coffee or whatever.
- sync 13y agoI'd be afraid of inheriting any codebase that made extensive use of these JS macros. You really have no idea what this is doing (the canonical example from sweetjs.org): class Person { constructor(name) { this.name = name; } say(msg) { console.log(this.name + " says: " + msg); } } It's not javascript -- it's your own made up "language" of macros. I do believe with a steady hand this could lead to some great things, but the example on sweetjs.org seems rather heavy handed. Having different `class` macro definitions between codebases that use sweet.js will devolve into madness.
- jlongster 13y agoYou're absolutely right, and we need to establish a good culture that doesn't abuse macros. Of course, some people always will and let's hope we don't have to work with that code. There are a lot of ripe ES6 features to implement as macros. That `class` macro is actually taken from ES6 and has been standardized for JavaScript, so it's not exactly random.
- pfraze 13y agoThe syntax "crowding" could be solved by scoping macro rules. Maybe `macro class, foo, bar` at the top of the file?
- jlongster 13y agoThe next big thing sweet.js is working on is modules. You will be able to actually pull in macros just like you would ES6 libs: import { foo, bar } from "macros"; The scoping of macros will all stay in tact; any other macros from "macros.js" will be available, but the code that `foo` or `bar` expands to will be able to access them (this is really nice for helper macros and other things). So yes, modules are going to be a big part of distributing and composing macros.
- 6cxs2hd6 13y agoRacket shows how, with strong module support, a whole "tower of languages a.k.a. macros" can work reliably. Else not so much.
- orthecreedence 13y agoGlad someone finally wrote a post about this. Coming from the lisp world, that's the thing I constantly see missing from languages wrapping javascript. They generally change the syntax a bit, add a few features to minimize boilerplate in some areas, and call it a day. Why would I want to learn a new syntax to program in a language I already know well? Unless you give me the tools to write code that writes code, I'm not going to bother learning/using a new javascript syntax.
- nawitus 13y agoAt least TypeScript brings to the table something very valuable: optional type checking.
- aaronem 13y agoAnd sweet.js macros can be used to implement the type system of your choice in otherwise stock Javascript. (For that matter, you could un-overload the + operator so that it only works with numbers, and invent a . operator to do string concatenation.) I can see a potential argument that doing type checking via macros costs more than precompilation in efficiency terms, but on the other hand, that's not necessarily so. How does TypeScript do type checking for non-constant values? That is, if I define function foo(bar: string) in a TypeScript library, then compile it, load the library in a Javascript interpreter, and call foo(Integer.parseInt("42")), what happens? I'm guessing that either foo() misbehaves, or that foo() throws an exception. In the former case, there is no benefit to TypeScript because your type annotations are evanescent and misleading -- you can't rely on them, so you either have to write fences around input values just like you would without them, or risk your code misbehaving because it's written to rely on unreliable type annotations. In the latter case, TypeScript is adding those fences automatically, so that a function which expects an integer argument can check whether it actually got one and fail if it didn't. That's basically what a macro system implementing type checking would do, too, and it seems to me that in both cases optimization would be merely an implementation detail.
- nawitus 13y ago
- lightblade 13y agosweetjs was a good idea, but I've found it hard to use, or rather..it's not the using part that's hard, but writing your own macro. sweetjs has this hygienic thing that renames identifiers. To prevent it from renaming, there are many hoops that you have to jump through to prevent it. sweetjs introduces of a lot of new concepts. These concepts are not as easy to understand as the ones in C. The macros are certainly more powerful. It's just that assuming it's C-like macro will give you the wrong kind of expectation with sweetjs.
- aaronem 13y agoOf course! These aren't C "macros"; they're Lisp macros, which is a wholly different, and far more valuable and reliable, proposition. Granted, I'm not sure how much I like the idea of a macro system which finds macro instances by string-matching the source it's given. But in a non-homoiconic language, I'm not sure anything else is feasible, and I can easily see how something like this could improve Angular.js's dependency injection, for example.
- taejo 13y agoUnhygienic macros aren't a C-ism: hygiene was an innovation (I think originally in Scheme). Ordinary Common Lisp macros are unhygienic.
- aaronem 13y agoSo are sweet.js macros, or else they wouldn't need to have reimplemented GENSYM. But I get the impression (parent (parent)) had sweet.js macros mixed up with C-style string-replacement "macros", which was what prompted my comment.
- mnemonik 13y agoSweet.js is hygienic but you can choose to be unhygienic if you want. It is the best of both worlds. http://sweetjs.org/#getting_dirty__breaking_hygiene http://sweetjs.org/#getting_dirty__breaking_hygiene
- sheetjs 13y agoSince sweet.js code can't be directly run in the browser, you need some "compiling" or "interpreting" step. It's not obvious why this is any different from a compile-to-js language that had support for macros. For example, someone wrote a macro system for coffeescript: https://github.com/mrluc/macros.coffee https://github.com/mrluc/macros.coffee Related discussions: https://github.com/jashkenas/coffee-script/pull/3171 https://github.com/jashkenas/coffee-script/pull/3171 https://github.com/gkz/LiveScript/issues/328 https://github.com/gkz/LiveScript/issues/328
- jlongster 13y agoThis isn't intended for people interested in other languages; if you like CoffeeScript, certainly use it. I really like ClojureScript myself. But there's a lot of reasons you already need a build step for JS anyway (especially for client-side), and this gives you a lot of power without having to completely switch languages. It's worth noting that sweet.js is a robust macro system grounded in academic research (the core of it from the Honu paper). That CoffeeScript system is a bit of a hack and its flaws would show it the whole community started using it (I don't have time to go into this right now, but things like hygiene, etc are really difficult).
- mr_luc 13y agoHi, I made macros.coffee, and I'd like to say that this guy is right. It's non-hygenic (on purpose) and it's a hack. And purely a labor of love! Slides: http://mrluc.github.io/hammy/12-7-rum-macros/deck.html#1 http://mrluc.github.io/hammy/12-7-rum-macros/deck.html#1 Code: http://mrluc.github.io/macros.coffee/docs/macros.html http://mrluc.github.io/macros.coffee/docs/macros.html I wanted to see how easily I could add (non-hygenic) macros to CoffeeScript, and if I could do it as an npm-installable module instead of a fork of the language. It was also an experiment in making macros very clear to myself. Non-hygenic (CL-style) macros are very simple conceptually. But the basic hack that made it possible to get true CL-style macros also makes the whole thing incredibly dependent on the structure of the CoffeeScript AST. Which makes it fragile. I'm excited by BlackCoffee, actually. This is the first I've heard of it. Thanks a lot for giving me a nod in the github PR, and of course let's mention Oran Looney as well, who wrote the best deepcopy ever for Javascript (I wrapped this up as owl-deepcopy and made an npm module for it).
- Fishrock123 13y agoI find the title kind of ironic, given that sweet.js is still a compiler. :P However, it is true it would be better to have one standard-ish compiler that can just execute whichever macros you throw into it. That being said, you still run into the issue that `sync` brought up.
- cromwellian 13y agoJavaScript compilers aren't only for syntax sugar. Closure Compiler, Dart2JS, and GWT are optimizing compilers. Most large Google applications for example would be hideously large without an optimizing compiler.
- jlongster 13y agoDefinitely. It was a terse statement for simplicity. I was hoping that my sentence "it's not good for extending javascript" clarified that.
- badman_ting 13y agoI'm not super into the idea of code running on code because I likes it as simple as I can gets it. But, I know this notion is popular with lispers so I get it.
- kaoD 13y agoDon't be fooled by a legion of old-time lispers. Macros are a last resort in real-life Lisp (aka Clojure). Hard to write (way harder than a function), hard to debug, hard to reason about, they're not functions (so fit worse in the rest of the language)... but powerful when you really need them. "When you have a hammer...", and a desire to stand out from other languages made the lisp-macro myth flourish.
- aaronem 13y agoModern Lispers (and modern Lisp learners' resources such as Seibel's Practical Common Lisp) talk about macros in exactly this fashion. I'd be willing to argue that the lesson has been learned.
- 6cxs2hd6 13y agoWell actually, in a modern lisp (like Racket), you use macros in a variety of ways. In addition to "deep" things, often you use them as practical, simple alternatives to using stuff like "snippets" or heavy IDEs. You can "DRY" annoying patterns, without resorting to external tooling. Although macros used badly can be mysterious, so can any excessive pre-processing and build magic. Macros provide "an API for the compiler", and since the compiler is involved, it can be smarter than external tools.
- kaoD 13y agoRacket? I said real life! (just kidding :P) As I said, when you really need them they're useful (e.g. in typed Clojure). Most (if not all) DRY patterns can be fixed using only functions. The problem is, the macro mantra has been parroted for so long now it's part of the Lisp culture and its external image ("Lisp is homoiconic! You can modify code! Macros! MACROS!"), when it's actually one of the ugly (but powerful) parts of Lisp you should avoid most of the time. This confuses beginners and people interested in Lisp. I see macros fitting mainly in DSLs (which is probably a code smell most of the time) and extending the language, as typed Clojure does. What other real use cases do you see for macros?
- yuchi 13y agoDid you had a look at [gorillascript](http://ckknight.github.io/gorillascript/ http://ckknight.github.io/gorillascript/)? Its [prelude](https://github.com/ckknight/gorillascript/blob/master/src/jsprelude.gs https://github.com/ckknight/gorillascript/blob/master/src/js...) is written in gorilla macros :)
- al2o3cr 13y agoSup dawg, I heard you wanted to stop writing JS compilers and start using macros so here's a JS compiler that implements macros. /head asplodes
- kirtijthorat 13y agoI would also recommend the WISP: A homoiconic JavaScript dialect with Clojure syntax, s-expressions and macros. More details at: https://github.com/Gozala/wisp https://github.com/Gozala/wisp
- nessus42 13y agoI enjoyed the article. I just have a factual correction: Lisp had real macros as early as the 70s, not the 80s. (Maybe even earlier, for all I know.)
- daniel-cussen 13y agoLisp macros were introduced in 1963 by Tim Hart at MIT.
- pmcjones 13y agoHere's the original technical report: ftp://publications.ai.mit.edu/ai-publications/pdf/AIM-057.pdf
- rcarmo 13y agoBest way I know of using macros in JS is https://github.com/Gozala/wisp https://github.com/Gozala/wisp, which generates pretty readable code. If you're going to pre-process, you might as well write in a nicer language...
- ahunt09 13y agoSo, after puzzling around with the macro for swap a bit, it seems like macros are like functions, except without the scoping rules, which suggests to me that we now have two kinds of functions, to keep track of in the code, each of which look identical, but behave differently. swap(a,b) looks like a function, but can interact with all the variables in the same scope (or above) as when it's called (is this the same for global functions?). Maybe I'm missing something big, but that seems dangerous when the macro is declared in another file, and I have to go figure out how it behaves.
- MBlume 13y agoMacros are not functions, macros are a mechanism for adding features to the language itself.
- ahunt09 13y agoMaybe I wasn't clear. I am not saying that macros are functions. I'm saying that without seeing the implementation, both look identical, and thus could be conflated, but do not treat scope identically.
- ahunt09 13y agoSo, after puzzling around with the macro for swap a bit, it seems like macros are like functions, except without the scoping rules, which suggests to me that we now have two kinds of functions, to keep track of in the code, each of which look identical, but behave differently. swap(a,b) looks like a function, but can interact with all the variables in the same scope (or above) as it. Maybe I'm missing something big, but that seems dangerous when the macro is declared in another file, and I have to go figure out how it behaves.
- skrebbel 13y agoI strongly agree with everything the author writes, but I believe that sweet.js might not go far enough. For example, I am very fond of TypeScript. I'm also very fond of React's JSX. Yet, I cannot mix the two, and neither JavaScript extension could be expressed as Sweet.js macros.
- smrtinsert 13y agoThere are many reasons why people are writing *->js compilers, lack of macros isn't the least of the problems for js. Please keep the compilers coming, no productive coder really wants to write js.
- bryanlarsen 13y agoThe author's point is that the best way to implement Javascript sugar like CoffeeScript is by writing sweet.js macros rather than by writing compilers.
- 14113 13y agoSo wait, what does this have that's better than C macros apart from hygiene? I'm afraid I really don't follow.
- norswap 13y agoIf you need the same for Java: https://github.com/norswap/caxap https://github.com/norswap/caxap
- Dewie 13y agoI think things like syntactic sugar is perfectly fine, as long as I can desugar it in a straightforward way. I want to be able to programmatically desugar some piece of code, not have to Google it each time I am curious. I think that if it is easy to investiage things like syntactic sugar, and not have it be buried in something like a compiler or lang spec, then DSL/language implementers (and anyone else, if the language permits it) could get away with implementing things that objectively make the language more complex to deal with, because to decipher it is only a query away, anyway.
- charlieflowers 13y agoRight. IDE features would be needed such as Emacs features which show the expansion of the macro you're on.
- chromanoid 13y agoHow do you deal with naming collisions of macros from different developers? Are you considering to introduce name spaces? To me macros as well as other surprise code modification stuff seem unfeasible in teams with more than a few developers. Don't you think you risk maintainability with a huge macro apparatus?
- disnet 13y agoWe're working on integrating macros and modules to address this. Once it's implemented you'll be able to do something like `import { m } from "macros.js"`.
- charlieflowers 13y ago>>>> "What if JavaScript actually adopted macros natively so that we never even had to run the build step?" Honestly, that's pretty damn exciting. In my book, if JavaScript did that, it would very nearly complete a "worst to almost-first" transformation as a language. Starting with its early versions and all their ugly "bad parts", and advancing all the way to one of the most exciting, expressive languages to work with.
- aryastark 13y agoI barely trust Guy Steele with macros. You know who I don't trust with macros? Random JS coder dudes that haven't learned what true hardship is, when debugging a macro that is 5 levels deep and subtly changes the language because, hey, I have a macro! And having a macro means using a macro. Language design is hard. Scheme has gotten it wrong. Over. And over. And over. They didn't get hygiene right for more than a decade. They still don't have it right. But people think they can easily invent new syntax that somehow doesn't do unexpected things. Your clever macro isn't so clever when some poor son of a bitch has been beating his head on a bug all day long just to find out your macro-that-looks-like-a-function isn't evaluating all its arguments because your macro-that-looks-like-a-function is really a function-that-is-a-macro. Don't get me started on missing IF branches and undefined behavior...
- vpj 13y agoYou can also use C macros with Javascript http://vpj.svbtle.com/coffeescript-and-macros-clean-and-fast http://vpj.svbtle.com/coffeescript-and-macros-clean-and-fast
- deleted 13y ago[deleted]
- voidr 13y agoYou won't have good IDE support. You won't be able to directly debug your code. Also it can be really fun to reason about nested macros.