Arc Forumnew | comments | leaders | submitlogin
2 points by Pauan 4228 days ago | link | parent

https://github.com/Pauan/ar/commit/d3971088cdf3160124d9affa7...

Arc/Nu now uses hyper-static scope and hygienic macros by default. This means I had to change "02 arc" in the following ways:

* Rearranged things so that they are always defined before they are used.

* Fixed some code duplication (e.g. alist now uses acons).

* Fixed some places where macros were supposed to use gensyms but didn't.

* Fixed unhygienic macros (like aif) so they work in the new hygienic macro system.

* The various defining macros (def, mac, etc.) all use "var" now rather than "assign".

* The "defs" macro has been changed a bit and now has an actual use.

* Added in a few new macros, like redef, remac, w/sym.

For the most part, though, I tried to change it as little as possible.

Thanks to this, I also found a few typos in the Arc/Nu libraries.

By the way, I made sure to change it in such a way that it works correctly whether hyper-static/hygienic-macros is turned on or off.

And of course, the Arc/Nu compiler can still load Arc 3.1's "arc.arc" unmodified, if you want to do that.



2 points by Pauan 4228 days ago | link

By the way, if you want to write macros that work correctly in both Arc 3.1 and Arc/Nu, here's how.

Non-anaphoric macros should Just Work(tm).

Anaphoric macros (aif, awhen, etc.) need to be tweaked a bit. First, let's look at the definition in Arc 3.1:

  (mac awhen (expr . body)
    `(let it ,expr (if it (do ,@body))))
Now, there's two ways to make this work in Arc/Nu. First, you can unquote+quote:

  (mac awhen (expr . body)
    `(let ,'it ,expr (if ,'it (do ,@body))))
Secondly, you can use "w/sym":

  (mac awhen (expr . body)
    (w/sym it
      `(let it ,expr (if it (do ,@body)))))
I personally recommend "w/sym", since you can just slap it onto the front and be done, but it's up to you.

And that's it. Now your macros work in both unhygienic Arc 3.1 and hygienic Arc/Nu.

-----

2 points by Pauan 4228 days ago | link

After some thought, I decided to stop half-assing it. What I've been trying to do is make a new implementation of Arc that is backwards compatible with Arc 3.1 but adds new features and fixes bugs.

The problem is that eventually I'll run into features I want to add, or bugs I want to fix, and I won't be able to do that without breaking compat with Arc 3.1.

This is very restricting, and part of Arc is about unbridled freedom. I thought about going the pg route and just saying "screw compat!" but if I did that I'd just end up with Nulan, so what's the point?

So instead, here's what I'm gonna do. There will be two separate languages implemented by the Arc/Nu compiler: Arc/Nu and Arc/3.1.

Arc/3.1 is the Arc you know and love. Unhygienic macros, dynamically scoped globals, and most of the warts and bugs and missing features.

Arc/Nu is a language similar to Arc, but with new features, bug fixes, and general cleanup. It has hyper-static scope and hygienic macros. It doesn't try to maintain backwards compat with Arc 3.1.

Now, you might be wondering, what's the point? I mean, we already have vanilla Arc 3.1 as implemented by pg, so why should Arc/Nu support Arc 3.1 at all?

The twist is that thanks to the magical power of booooxes, I can have Arc/3.1 and Arc/Nu running in the same namespace. This means Arc/3.1 can import Arc/Nu stuff, and Arc/Nu can import Arc/3.1 stuff.

And it all works correctly, so that if you define an unhygienic macro in Arc/3.1 and import it into Arc/Nu, it will behave unhygienically. And vice versa, if you define a hygienic macro in Arc/Nu and import it into Arc/3.1, it will behave hygienically.

And you can do things like import an Arc/3.1 library and mutate its stuff, or do the same to an Arc/Nu library. There's no restrictions between the two, because it all runs in a single namespace.

This means you can write new code with all the shiny features, yet still use old Arc/3.1 libraries. And if you just plain like using Arc/3.1 but there's some Arc/Nu library you want to use? You can.

And if pg ever releases Arc 4, I can just add it as a third language, which will let it interact with both Arc/Nu and Arc/3.1. This is all very very easy to do thanks to boxes.

-----

2 points by Pauan 4226 days ago | link

https://github.com/Pauan/ar/commit/2c7c5f8f0ea2f313a47fadb2a...

After some major refactoring, I reverted it back to use vanilla Arc 3.1.

Now, Arc/Nu has a rudimentary "multiple language" feature. When using the "arc" executable, you can now specify the "--lang" property.

For instance, when using "arc --lang foo", it will look for a folder called "foo" in the "lang" subdirectory. This folder should contain a file called "main" which should be written in Racket. Using this file, you can implement custom languages using the Arc/Nu compiler.

Currently, only two languages are supported: "arc/nu" and "arc/3.1"

https://github.com/Pauan/ar/tree/2c7c5f8f0ea2f313a47fadb2ae6...

https://github.com/Pauan/ar/tree/2c7c5f8f0ea2f313a47fadb2ae6...

As you can see, "nu.nu" is quite sparse at the moment, but it does load correctly if you use "arc --lang arc/nu". With some more refactoring, I'll be able to make it so that arc/nu and arc/3.1 can use libraries written in the other language.

-----

1 point by Pauan 4226 days ago | link

https://github.com/Pauan/ar/commit/6352cbffc8866fac7b19ed3b3...

Now multiple languages are fully supported. The way it works is that, within a file, you can use "w/lang" to temporarily change the language. For instance, suppose you had the following "arc/3.1" program:

  (w/lang arc/nu
    (var foo 1))

  (= bar (+ foo 2))
Within the "w/lang" block, it's using "arc/nu", but outside, it's using "arc/3.1"! Using this, it's easy to import libraries written in "arc/nu":

  (w/lang arc/nu
    (import foo))
And vice versa, if you're writing a program in "arc/nu", you can use "w/lang" to import things from "arc/3.1":

  (w/lang arc/3.1
    (import foo))
By the way, because all of this is using boxes at compile-time, there's zero runtime cost. The only downside is that if you use two languages at once, it has to load both of them, which increases the startup time.

-----

4 points by Pauan 4226 days ago | link

Well! I just learned something new! Racket's "procedure-rename" is ridiculously slow. In case you don't know what I'm talking about, it's just a function that lets you rename functions:

  (procedure-rename (lambda () 1) 'foo)
Anyways, here's the timing tests I did for Arc/Nu using "procedure-rename":

  > (+ 1 2)
  Arc/Nu   iter: 45,419,082   gc:   0   diff:  0.00%
  Arc 3.1  iter: 52,867,613   gc: 188   diff: 16.40%

  > (no ())
  Arc/Nu   iter: 26,594,507   gc:   0   diff:   0.00%
  Arc 3.1  iter: 54,532,505   gc: 152   diff: 105.05%
And here's the results when I removed "procedure-rename":

  > (+ 1 2)
  Arc/Nu   iter: 88,933,497   gc:   0   diff: 71.88%
  Arc 3.1  iter: 51,741,236   gc: 116   diff:  0.00%

  > (no ())
  Arc/Nu   iter: 78,026,418   gc:   0   diff: 37.11%
  Arc 3.1  iter: 56,909,869   gc: 156   diff:  0.00%
What a ginormous difference. Removing it doubled the speed of Arc/Nu! Given that the sole purpose of "procedure-rename" is to, well, rename functions, I wouldn't expect it to have such a huge runtime performance penalty, but apparently it does...

-----

3 points by lark 4225 days ago | link

I sometimes wonder if garbage collection in dynamic languages is necessary.

-----

2 points by rocketnia 4225 days ago | link

Sounds like a good topic, and I'd be interested in hearing to hear your thoughts on this. I've been thinking about the same kind of thing, but I'm looking to use it in a weakly typed subset of a statically typed language. My motivation is mostly to see if we can make it convenient to externally manage the memory needs of otherwise encapsulated programs.

This is probably a discussion for another thread. ^_^;

-----

1 point by akkartik 4225 days ago | link

My arc variant uses ref-counting, for what it's worth: https://github.com/akkartik/wart/blob/adf058706b/010memory

-----

1 point by rocketnia 4226 days ago | link

I wonder if the compiler can't see through a 'procedure-rename call to realize it can still apply optimizations to the code. Like, maybe it can optimize ((lambda (x) ...) 2) but not ((procedure-rename (lambda (x) ...) 'foo) 2).

-----