Arc Forumnew | comments | leaders | submitlogin
On Modules (
1 point by aw 3799 days ago | 6 comments

3 points by Pauan 3798 days ago | link

Option 3: first-class namespaces, which is the approach that Python uses (albeit it doesn't have macros).

So for instance:

  ; file "foo.arc"
  (def helper (x)
    (+ 5 x))

  (mac something ()
    `(helper 10))

  ; file "bar.arc"
  (import foo)

  (foo!something) -> 15

  (w/eval foo
    (something)) -> 15

  (something) -> error: undefined variable something

  (= something foo!something)

  (something) -> 15

  (w/eval namespace
    (something)) -> error: undefined variable helper
I did some work on this with my previous "import.arc", and I just added in initial support for macros. Because namespaces are first-class objects, they can contain all sorts of information about the mapping between names and values. There's also some nifty stuff you can do with them as well, like namespace inheritance.

The primary benefit of namespace inheritance is speed: rather than recompiling the entire ar compiler every time, you instead compile it once, and then create light-weight namespaces that inherit from it.

You can think of it like the difference between storing copies of two files, and storing the diff of two files. Obviously storing the diff will be much shorter in most cases, because there will be many parts that are the same between the two.

So rather than creating an entire fresh new namespace every time, you just store the differences, which allows namespaces to be created super-fast and also consume less memory.

I would like to note that all of this already works with "import.arc"... it's not just theory, it works right now, and you can use it right now in your own code. It's not perfect, of course, but at least it works.

Also, I'll note that my solution for macros with regard to namespaces is to basically evaluate the macro at runtime. This works, but is obviously less efficient than I'd like. I think it's possible to optimize it so it only needs to do the macro expansion one time.


By the way, I would like to point out that "import.arc" does not create new runtimes... runtimes are a superset of namespaces. You can think of them like namespaces + other features. "import.arc" focuses solely on namespaces.

Why? Well, ar already has support for runtimes. I see runtimes as being useful for creating new languages and having them run side-by-side with other languages. Like, say, Arc/3.1 and Arubic. Or Arc/3.1 and Perl. Or whatever.

But I see a difference between runtimes and namespaces. Sometimes you want namespaces within a language. Using a full-blown runtime for that is overkill. You just want a mapping between names/values, and that's what "import.arc" gives you. If you want to create a new language, or a language variant, then I would suggest you use ar's already-existing support for runtimes.


Also, I still need to solve one particular problem with namespace inheritance. One solution I've been thinking about is making all global variables dynamic, but I'll need to play around with that to see if it'll work or not.


1 point by aw 3798 days ago | link

Ah, so when a macro gets evaluated, it gets evaluated in its namespace? That's clever!


1 point by Pauan 3798 days ago | link

Yes. At runtime. Even ignoring the inefficiency of expanding macros at runtime, there's one other thing that concerns me, and that's anaphoric macros.

If somebody writes a macro like "aif" or "aand" or whatever, you want the variable "it" to refer to the caller's namespace, but all the other variables to refer to the definer's namespace...

I think this is a solvable problem, but I think it'll require more complexity to solve.

By the way, I think I can add in a system where you can selectively choose which variables should be expanded in your namespace, and which ones shouldn't. Something like this:

  (import foo)

  (w/names (it)
This says that the variable "it" should be expanded into your namespace, and all other variables should be expanded in foo's namespace.

Interestingly enough, if I implemented such a system, you could then overwrite certain variables in a macro's body:

  (def helper (x)
    (+ x 50))

  (foo!something) -> 15 ; uses foo's version of helper

  (w/names (helper) ; uses my version of helper
    (foo!something)) -> 60


1 point by Pauan 3798 days ago | link

Actually, there's another thing that also concerns me... Consider this macro:

  (def foo args
    (apply prn args))

  (mac bar (x . args)
    `(foo (string ,x) ,@args))
If you call bar, you want the variables "x" and "args" to be expanded in your namespace, but everything else to be expanded in bar's namespace.


1 point by aw 3798 days ago | link

Have you published import.arc somewhere?


1 point by Pauan 3798 days ago | link

Yes. It's in the "lib" branch of my fork:

It's a bit messy right now, though.


Also, `import` is still using `load`, rather than `use`. So you'd use something like this:

  (import foo "../path/to/foo.arc")
Rather than this:

  (import foo)
This has both advantages and disadvantages. What I'd like to do is have (import foo) behave like (use foo), but with namespace isolation. And then we could have an (import-as foo "../path/to/foo.arc") for more precise control.


I'd also like to note that "import.arc" is still basically alpha-state, so I make absolutely no guarantees about whether it'll work or not. My suggestion would be to try it, and if something breaks, tell me about it so I can try to fix it.

Basically, it's a prototype that attempts to demonstrate that the concept is both possible and feasible.