-
Notifications
You must be signed in to change notification settings - Fork 2k
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
Hygienic macro proposal #3171
Hygienic macro proposal #3171
Conversation
Impressive piece of work. |
@Nami-Doc, thanks! Two things I forgot to mention:
|
(still not there yet though..)
Errors during the lexer/parser phases are simply attributed to whichever file last started parsing. After that, locationData on all nodes is amended with `file_num`, which references the filenames and scripts. These are used for compilation errors, run-time backtraces and (inline) sourceMap generation. Also, sourceMap generation from fragments is now done by the SourceMap class itself, so that this functionality may also be accessed seperately from CS.compile.
Really, really interesting. Something to mull over. |
Can we drop the But this is an interesting piece indeed. I wonder if there's a way to provide a simpler way for people to generate the structure they want inside a macro without using Strings, which are fairly error-prone and difficult to debug. Lisp is at a clear advantage here because the language is basically a huge AST ;) |
This seems pretty interesting, but I'm not sure how it would work for the specific rare cases where I've wanted macros. How would I use this to implement an infix |
@killdream: Indeed! Please take a look at the commit I just pushed. I updated the examples in my initial pull request too. Better? Also, using @ as the namespace for macro helpers felt like a kludge. I'm using the @STRd6: Operator overloading in a dynamically typed language sounds pretty scary. You would end up having to emit extra code for every addition you are doing, in order to check if the operands happen to be of the types you're interested in. This pull request only concerns macros with a function-call syntax. |
@vanviegen I wasn't speaking of operator overloading, but of defining the |
@STRd6: Ah, I see that you're into game programming, so you're after vector/matrix arithmetic - I can relate to that. :) It turns out this is actually pretty easy to do: (although this solution does rely on coffee's AST not changing too much) macro delegateArithmetic (func) ->
operators =
'+': 'add'
'-': 'sub'
'*': 'mult'
macro.walk func.body, (node) ->
if node instanceof macro.Op and (op = operators[node.operator])
macro.csToNode("(a).#{op}(b)").subst {a: node.first, b: node.second}
# this uses regular javascript operators:
eq 7, 3+4
eq "3,45,6", [3,4]+[5,6]
delegateArithmetic ->
# this uses the arithmetic methods defined on the prototypes:
eq [3,4,5], [1,2,3]+2
eq 20, [1,2,3]*[2,3,4]
eq [1,0,1], [3,2,4]-[2,0,3]
eq 9, 3*3 The generated javascript looks like this: eq(7, 3 + 4);
eq("3,45,6", [3, 4] + [5, 6]);
eq([3, 4, 5], [1, 2, 3].add(2));
eq(20, [1, 2, 3].mult([2, 3, 4]));
eq([1, 0, 1], [3, 2, 4].sub([2, 0, 3]));
eq(9, 3..mult(3)); |
Haxe has an interesting macro system. Rather than using a ...
macro.walk...
...
new macro.Call new macro.LiteralAccess(node.first, new macro.Literal(op)), node.last They also have a macro do_thing ->
ast = macro do ->
thing = -> console.log 'hello'
ast instanceof macro.Do # true
ast.target = macro.makeExpt thing
do_thing() # logs 'hello' Obviously this conflicts with the |
@connec: the The keyword to convert an expression to an AST is called I'm not really sure if it would be possible to implement a |
@vanviegen Thanks for this pull request, it's a dream come true. Is it possible for one .coffee file to use macros from another .coffee file? Is the following behaviour of
|
@ngn, that was a bug, thanks for reporting it! Fixed, and added a few cases to the unit test. |
Great! Now let me elaborate on the other problem I mentioned above: From One solution might be to have a built-in macro that imports all macro definitions from another file:
It wouldn't be too hard to implement outside the compiler:
but it's only useful as a built in since we can't afford repeating this code in the beginning of every other coffee file. Any other ideas? |
@ngn, I think it would be cleaner to just have a separate file that defines common macros. You could include it from both # common-macros.coffee
macro TEST -> macro.valToNode 42 # a.coffee
macro -> macro.fileToNode 'common-macros.coffee'
eq TEST(), 42 It might be even better if you would be able to specify a common prefix file on the coffee compiler cli. I don't think it can do that at present. We use our own little cli that accepts multiple input files to be combine into a single js file. Check out my |
Ah, I didn't realize that file inclusion can be made as brief as |
I noticed that macros in switch statements don't get expanded. |
@ngn, fixed, thanks! |
Are you willing to maintain this as a fork with a nice README for a bit? If so, I'd love to start linking folks over to you from the homepage so you can get more feedback... |
@jashkenas, allright, how's this? https://github.com/paiq/blackcoffee |
I played with blackcoffee a little bit, and opened a new pull request on paiq#1 for adding method macros in the form of |
I just found my way here via linkage in comments in Hacker News. I had not heard about this; I live in (remote-ish) South America 9 months out of each year so ... I miss things. Wow! +100 @vanviegen. Thanks for the nod, too -- macros.coffee was a labor of love that started when I wondered if I could get 'quote' to work with just AST fiddling (are you guys still using deepcopy on the AST?), but I've been waiting (and hoping) for a 'heavy-duty' implementation of macros from somewhere, especially one that has decided on an approach to compilation (which I broke in a rewrite and never revisited). I'd love nothing better than for quality macros to happen in Coffeescript. It's already the most beautiful, fluid and convenient language out there. @jashkenas - I love pretty much all of your code, but mostly I love Coffeescript. I want to use it for everything - and I really think that macros are an approach that would let people use it for things no one can imagine right now. Also, since there are apparently macro-loving Coffeescripters in this thread, can I just say: sorry. I've had zero time to work on macros.coffee in the past year and more. Like I say on the project page, for anyone who makes more of a 'real' implementation, I'd love to just use yours. Some implementation thoughts that may or may not still be helpful: Besides not having time, a somewhat paralyzing consideration was the fact that, at the end of the day, my approach relied on copying an AST and direct fiddling with its nodes in a way that depends on compiler implementation details, and that unedifying work to 'insulate' ourselves from the fragility of that approach would probably be required to move forward in a serious way -- either by creating a compiler from AST <-> our own representation, or by hiding the fragility behind a library of common AST functions (which, when updated for new versions of coffeescript, could insulate macro authors from changes in the CoffeeScript AST api). Important notes:
I haven't checked the implementation much at all, I just saw the notes on HN a few minutes ago and had to say thanks, and gush, and wish you all the best, so my comments above might not be relevant to your implementation. Macros + Coffeescript development. Extremely exciting. I'll help if I can. |
Can't believe JS lived this long without macros. Excited to see where blackcoffee goes. |
|
@Charuru +1 the excitement, though this PR is about CS, not JS. There have been several projects that add macros to javascript before (even before sweetjs, which adds hygenic macros). Those projects, which are very tied to fiddling with Javascript syntax, and which in HN discussion were talked about by some people as a way of building in 'some of the bits of coffeescript that I like into javascript, but not the rest', would not be as interesting to people who like Coffeescript. Many people like Coffeescript, find it to be an absolutely joyous and natural language to use, and are interested in macros, perhaps partly for aesthetic reasons, but mostly because macros give you new capabilities, and they want to have those capabilities available to them. |
+1 |
👍 I feel coffeescript project is a bit conservative to ES6. I often want escape hatch about them on syntax level like sweet.js. Macro may solve them. |
This is seriously incredible. 👍 |
As awesome as this is, it doesn’t look like this is headed into the compiler anytime soon. I think an approach that might pass muster is if the compiler is updated to allow plugins somehow, and then this could be released as its own project as a plugin. (The parsing of Markdown in Literate CoffeeScript would make another excellent candidate to extract into a plugin.) I’ve added BlackCoffee and a link to this PR to the List of languages wiki page. |
Could we please please please have blackcoffee merged into coffeescript? 🥺 I specifically heard Jesus mention he's not coming back until we have macros (and infix operators on custom objects) in coffeescript. For realsies. I'll forgo all other presents if I can just have macros for christmas. 🎁 |
It hasn't been updated in 8 years, which means it's a fork of CoffeeScript 1 rather than 2. So unless blackcoffee catches up to trunk, it can't be merged in (on a technical level, aside from whether folks agree that it's a good idea). |
I don't think he means literally more that the macros are implemented into coffee. I really want this too, I've since started using TS but if coffee gets macros... |
Thanks so much for the reply!
I really, really hope they do. From the looks of it, the reception to this issue was very warm. |
Yeah, I’m just wishing for those tasty macros. Especially for operators.
Me too :D I miss CS so much though. I thought the type system would be enough of a carrot, but alas, no. I miss the expressivity and cleanliness of CS. |
@zolomatok and @Charuru if you like CoffeeScript and TypeScript you may want to check out https://github.com/DanielXMoore/Civet. It's currently in active development and aims to merge the benefits of both CoffeeScript and TypeScript. It doesn't yet have hygenic macros but I'm not fundamentally opposed to the idea and I would be happy to explore it with your feedback. |
@STRd6 Very intriguing, thanks! 👏 It's awesome that this is a thing! I was happy to see the VSC extension as well! I would love to see macros and infix operators on custom objects in Civet 🌈 |
Here's the macro system we've been using internally. It's not very lispy, but that might be a good thing to most. :) I'd say it's pretty clean, well, for a macro system anyway...
Some examples. Optionally compiled debug info:
The mandatory swap example. Cheating a bit by using deconstruction, but this does show some of the more interesting features. The coffeescript code "[x,y] = [y,x]" get translated to nodes, and then an identifier substitution replaces x and y by whatever nodes are the arguments.
If you want, you can go pretty crazy with this. Non-sense from the unit test:
One thing we're using compile-time logic for, is resource versioning of web resources (so we can have clients cache them permanently, and all references to them will automatically change the (otherwise ignored) version number whenever the resource is updated.
Also one of the more interesting applications (for us), is string translations.
In this example, we're just doing the actual translation compile-time, but one could easily improve upon this by resolving the string insertions at compile time, and generating custom code for the pluralizations.
One could even accept the coffeescript string interpolation syntax. Eg:
would translate to:
I'm quite curious to hear any thoughts on this approach...
Regards,
Frank