Lexspaces

Table of Contents

A binding based approach to proper namespaces for GNU Emacs

<2020-05-08 Fri>

Recently on emacs-devel the topic of proper namespaces came-up.

I'm convinced a simple prototype is the most efficient way to explore an idea but also to explain it, so yeah… This is what I managed to tape together in the last day or so.

This is about having a system to prevent name clashing between Emacs Lisp programs but potentially could open the door to other interesting options like namespace versioning. Different version of the same library could then coexist loaded.

This is based on the idea of handling namespaces (modules?) splitting the binding from the symbol itself. I've got the original idea from Lexicons but given this implementation is radically different I called these Lexspaces. I don't know if other similar implementations exists (I'd guess so) and I'd like or hear about those if someone are aware.

I believe the symbol/binding split makes sense because the real name clash has not to do necessarily with the first but rather with the seconds

Getting practical I'll first introduce some of the behavior and then explain how I've implemented it:

;;; -*- lexical-binding: t -*-

;; Create a Lexspace lib1 deriving it from the Lexspace el.
;; This is the fundamental one.
(lexspace-make 'lib1 'el)

;; Set as current Lexspace lib1.  Here all definition of the original
;; Lexscape are avalable.
(lexspace-in 'lib1)

(defvar bar 3)
(defun foo ()
  (message "bar value is %s" bar))

;; Function foo and variable bar are bound as expected.

;; Create lib2 as was done for lib1 and jump into.
(lexspace-make 'lib2 'el)
(lexspace-in 'lib2)

;; We can also import the definition of single symbol. Here we import
;; foo from lib1.
(lexspace-import-symbol 'foo 'lib1)

;; Foo works correctly because runs in the Lexspace lib1 and so
;; having visibility on bar.
(foo)

;; bar is non visible as expected because never imported into lib1.
(if (boundp 'bar)
    (message "I can see bar")
  (message "I can't see bar"))

(lexspace-in 'el)
;; Back in the original el lexspace bar and foo are not visible.
(if (boundp 'bar)
    (message "I can see bar")
  (message "I can't see bar"))
(if (fboundp 'foo)
    (message "I can see foo")
  (message "I can't see foo"))

;; Finally we can also define lib3 and prove this does not have name
;; clashes with lib1
(lexspace-make 'lib3 'el)
(lexspace-in 'lib3)

(defvar bar 66)
(defun foo ()
  (message "this is a different foo and bar value is %s" bar))

;; Will message "this is a different foo and bar value is 66"
(foo)

(lexspace-in 'lib1)
;; Will message "bar value is 3"
(foo)

;; Etc...

For the record this is the output running it batch:

bar value is 3
I can’t see bar
I can’t see bar
I can’t see foo
this is a different foo and bar value is 66
bar value is 3

Implementation

As mentioned the key point is the separation of the symbol from the binding, also all symbols are still interned in one single obarray.

Lets say you are in the fundamental Lexspace el and (defvar foo "xxx") is evaluated.

Because the symbol is bound for the first time a binding is allocated, this will serve binding values of that symbol for all the Lexspaces. After the evaluation this will be the status:

lex1.png

Note: each Lexspace at creation is associated with and index. The first slot of the binding is used in this case because the fundamental Lexspace el is associated with the index number 0.

Lets create a Lexspace lib1 derived from el and set it as current.

(lexspace-make 'lib1 'el)
(in-lexspace 'lib1)

lib1 will be associated internally to index 1 and all the bindings will be redirected.

Resolving the symbol-value of foo will be now equivalent to:

lex2.png

This configuration allow for an hierarchy of Lexspaces because lib1 can be derived again as expected with (lexspace-make 'lib2 'lib1) to create lib2.

Note also that this design implies that if foo will be modified by any code running in Lexspace el we will have the update value visible in lib1.

Similarly evaluating (setq foo "yyy") from Lexspace lib1 the binding update will be done in a "non shallow" way, that is the original binding is updated as follow:

lex3.png

Because foo was originally defined in el all the Lexpaces derived from it will have visibility on this binding update.

Talking about functions these are working with the same identical mechanism.

The function execution tho is contextual that means the original Lexspace is saved in the function in such a way that when this is executed it will operate in the original Lexspace it was defined.

This allow for exposing a function from a Lexspace and not all the symbols it's working on.

Finally is possible to import bindings from another Lexspace for a single symbol as for instance (lexspace-import-symbol 'bar 'lib3) where we would be importing bar from Lexspace lib3.

Some thoughts

I suspect that decoupling symbols and bindings could guarantee the sufficient degree of freedom to solve most if not all problems in this area including namespace versioning. I think also that having a single obarray is more simple to handle in general.

  • I've implemente this in GNU Emacs as a 24h hack. It compiles and bootstrap. I played a little toying both batch or in the scratch buffer and seems to work but is basically completely not tested (just finished). I'm dropping in emacs.git a branch as scratch/lexspaces if someone is curious.
  • I've not optimized nor for space nor for memory footprint.
  • No harmonization exists with symbol alias, I guess these should be somehow considered and symbol alias should be Lexspace local.
  • I'm saving for now the Lexspace index only in lexical scoped interpreted functions. It should be quite easy to go further but I think is sufficient to discuss the approach. This was the main scope of this prototype.

I'm conscious this is potentially a complete stupid idea, but I find interesting the subject and I'd be interested in opinions.

Author: Andrea Corallo

Created: 2020-05-08 Fri 21:53

Validate