ns.arc fails to act as a loading sandbox
rocketnia opened this issue · 2 comments
At the Arc Forum, I gave an example of how ns.arc is supposed to work. Here's the example again:
; my-file.arc
(= n 2)
(= my-definition (* n n))
arc>
(= my-definition
(let my-ns (nsobj)
; Populate the namespace with the current namespace's bindings.
(each k (ns-keys current-ns)
; Racket has a variable called _ that raises an error when
; used as an expression, and it looks like an Arc variable, so
; we skip it. This is a hack. Maybe it's time to change how
; the Arc namespace works. On the other hand, copying
; namespaces in this naive way is prone to this kind of
; problem, so perhaps it's this technique that should be
; changed.
(unless (is k '||)
(= my-ns.k current-ns.k)))
; Load the file.
(w/current-ns my-ns (load "my-file.arc"))
; Get the specific things you want out of the namespace.
my-ns!my-definition))
4
arc> n
_n: undefined;
cannot reference an identifier before its definition
in module: "/home/nia/mine/drive/repo/mine/prog/repo/not-mine/anarki/ac.rkt"
context...:
/home/nia/mine/drive/repo/mine/prog/repo/not-mine/anarki/ac.rkt:1269:4
The problem is that for some reason, (load "my-file.arc")
actually loads n
into the same namespace the REPL uses, when it should be loading it into my-ns
.
I suspect the cause of the error is another contribution I made to Anarki when I made the anarki
Racket package. I think I changed the way Anarki evaluates code so that it always evaluates it in the Anarki namespace, with the goal of preventing Racket programmers from having to worry about what current-namespace
is bound to every time they load Anarki code. This needs to be redesigned somehow, and I would say the ideal design is probably to interfere with the binding of current-namespace
as little as possible in places where such interference isn't explicitly requested. That will make the places where it is explicitly requested more intuitive in their effects.
While I have some wild ideas in mind for how namespaces for Arc and Racket interop could work, I want to keep the scope small here:
- Write a unit test that demonstrates the kind of bug that currently occurs in the above ns.arc example.
- Explore changes that reduce the places we parameterize
current-namespace
. If we arrive at a reasonable enough design that fixes the test, let's close the issue.
I'm basically making this issue just to assign it to myself and to keep myself focused on the scope of the task. I think if I have to take a break from this, other people can pick this up from where I left off, but I think between my familiarity with these parts of the code and my clarity of purpose about what the code is supposed to do, I'll be making progress on this pretty fast.
The example I've gotten to work is slightly different than the one I originally gave.
; my-file.arc
(= n 2)
(= my-definition (* n n))
arc> (require 'lib/ns.arc)
arc>
(= my-definition
(let my-ns (nsobj)
; Load the Arc builtins into the namespace so we can evaluate
; code.
(w/current-ns my-ns ($.anarki-init))
; Overwrite most of the namespace with the current namespace's
; bindings.
(each k (ns-keys current-ns)
; Racket has a variable called _ that raises an error when
; used as an expression, and it looks like an Arc variable, so
; we skip it. This is a hack. Maybe it's time to change how
; the Arc namespace works. On the other hand, copying
; namespaces in this naive way is prone to this kind of
; problem, so perhaps it's this technique that should be
; changed.
(unless (is k '||)
(= my-ns.k current-ns.k)))
; Load the file.
(w/current-ns my-ns (load "my-file.arc"))
; Get the specific things you want out of the namespace.
my-ns!my-definition))
4
arc> n
_n: undefined;
cannot reference an identifier before its definition
in module: "/home/nia/mine/drive/repo/mine/prog/repo/not-mine/anarki/ac.rkt"
context...:
/home/nia/mine/drive/repo/mine/prog/repo/not-mine/anarki/ac.rkt:1269:4
The difference is just one line: It's necessary to initialize the namespace with at least some Anarki builtins and Racket-side bindings, which I do using a new function, anarki-init
.
The anarki-init
function prepares a namespace for use as a space to evaluate Arc code, and it can make an almost completely independent instance of Anarki each time it's called. This isn't just to populate all the Arc builtins; it's also to populate the Racket bindings that are seen by $
.
I want to mention there are a few places these instances of Anarki aren't quite "completely independent":
-
The mutable variable
current-function
in ac.scm is currently shared between them. Since it's pretty kludgy regardless of this and it's only used for error messages, I left it alone for now. -
The statement
(print-hash-table #t)
in ac.scm is only performed once, not once peranarki-init
. This line of code has been around since arc0 in 2008, and it changes the way tables are displayed. It happens that Racket has already hadprint-hash-table
default to#t
since 2007, although the documentation is out of date and says it defaults to#f
. (Or maybe the documentation is right, and this is a bug in Racket.) I suspect the Racket documentation will be updated -- I've filed a bug with them -- and and once it is, this(print-hash-table #t)
statement can probably just be removed. -
The semaphore underlying the implementation of
atomic
is shared between Anarki instances. That seems positively desirable to me, so it's just the way I want it.
All right, this interaction is working pretty smoothly now. Closing the issue. 😀
Although it's a digression from this issue, I'm following up to my last comment to mention that my bug report for print-hash-table
has resulted in a fix to the Racket docs. This means the code (print-hash-table #t)
is and has always been pretty much redundant, so I'm going to remove it.
This will leave current-function
and the atomic
semaphore as the only two observable pieces of state allocated at the top level of ac.rkt.