arclanguage / anarki

Community-managed fork of the Arc dialect of Lisp; for commit privileges submit a pull request.
http://arclanguage.github.io
Other
1.17k stars 160 forks source link

ns.arc fails to act as a loading sandbox #95

Closed rocketnia closed 6 years ago

rocketnia commented 6 years ago

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:

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.

rocketnia commented 6 years ago

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":


All right, this interaction is working pretty smoothly now. Closing the issue. 😀

rocketnia commented 6 years ago

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.