Differences between revisions 6 and 11 (spanning 5 versions)
Revision 6 as of 2011-06-15 04:59:04
Size: 7339
Editor: BtTempleton
Comment: adding vm instructions
Revision 11 as of 2011-07-10 22:14:41
Size: 8819
Editor: BtTempleton
Comment: lexical-let + lambda
Deletions are marked like this. Additions are marked like this.
Line 147: Line 147:
The macros `eval-when-compile` and `eval-and-compile` work everywhere, unlike `cl:eval-when`. But macro definitions only have compile-time effects at the top level in Elisp -- the current behavior (2011-06-20) is incorrect

I originally thought that `&rest` alone at the end of a lambda list meant to ignore extra arguments, but in fact `&rest` and `&optional` are ignored entirely if there isn't a symbol following them. For example, `(lambda (x &rest) ...)` = `(lambda (x) ...)`. (I don't think it's necessary to do the same in GuileElisp)

In Emacs, although `(eval-and-compile (setq lexical-binding t))` has the expected effect with the byte-compiler, the evaluator only checks the local value before evaluating a buffer or loading a file.

Emacs's `lexical-let` interacts oddly with `lambda`:

{{{
(setq x 0)
(defun dynx () x)
(lexical-let ((x 1)) ((lambda (x) (list x (dynx))) 2))
;; => (1 2) in Emacs, (2 0) in Guile
(lexical-let ((x 1)) (let ((x 2)) (list x (dynx))))
;; => (2 0) in both implementations
}}}

In Emacs, the lambda expression establishes a dynamic binding but `x` still refers to the lexical binding. The `let` example works as you'd expect because the CL package macroexpands it to a `letf` of `symbol-value`. However, the CL package's macroexpander more or less ignores the lambda expression's argument list even though `x` is a symbol macro. This situation is probably quite rare, so I'm going to leave it as a known incompatibility.

BtTempleton has submitted a proposal to complete eir work on Guile-Elisp during Summer 2011.

2010 GSoC work

Lisp-1 / Lisp-2

> Currently Elisp symbols are Scheme symbols, and their value and function
> cell contents are looked up in submodules of (language elisp runtime).
> This works well enough for now, but it will get quite ugly if Elisp is
> ever integrated with Guile's module system to any degree. Every module
> containing Elisp function bindings might need to have a 'function-cell'
> submodule, for example; an uninterned symbol could be used to avoid name
> clashes, but it would still pollute the namespace. It would be cleaner
> to maintain multiple hash tables in every module, and then e.g. (@
> (emacs) set-buffer function) could refer to the function binding.
>
> But here's another alternative: Guile already has function and plist
> slots in symbols, so clearly it's okay to waste a few bytes for features
> nobody uses. (: Why not keep the extra cells in variable objects, or in
> a new type of variable object? (I call this option "big variables" or
> "bigvars".) Guile's double cells are just the right size; there's enough
> room for a function cell as well as a pointer to a properties object,
> which would contain the plist cell and implementation-internal
> annotations. I've implemented the (trivial) libguile side of this, but
> at least Tree-IL, the module system, and the VM would also need to be
> modified to support this, if it's even a good idea.

Hmmm. Yes, I think this is a good idea. Does it make sense to be a
subtype of variables -- that is, with the same tc7, but with a flag in
the first word? Then we could make Guile's variables all be treated as
having a value binding already. Perhaps the new variable-function-ref /
toplevel-function-ref / etc opcodes (you are adding them, yes? :) should
return the value if it's a narrow variable holding a procedure. We would
need toplevel-value-define / toplevel-function-define as well, to ensure
that the binding is a wide variable.

Scope

> I've been reading old CL proposals regarding global lexical bindings,
> and I think I now understand why this can't work in Guile. Previously I
> wasn't considering the existence of a *global* environment, only lexical
> and dynamic bindings, and hence I wasn't understanding the bindings
> lookup order required for this. Scheme only allows LG lookup -- it looks
> for variables in the lexical environment, then falls back to the global
> environment.
>
> Globally-bound Elisp and CL variables are pervasively special, and
> cannot be lexically bound. Globally-bound CL functions are pervasively
> lexical (IIUC), and cannot be dynamically bound. Elisp bindings have
> only global bindings, and cannot be lexically or dynamically bound. But
> for Elisp function bindings, we need a globally-bound variable which can
> be bound both lexically and dynamically. To allow dynamic shadowing of a
> global lexical (but not pervasively lexical!) variable, the
> implementation must do LDG lookup -- checking in the dynamic environment
> after the lexical environment but before the global environment, which
> is distinct from both the lexical and dynamic environments. But Scheme
> doesn't *have* a true dynamic environment; it only simulates it by
> storing fluids (i.e., indices into local dynamic state objects) in the
> global environment, which of course means Guile Elisp can't have a
> dynamic binding for a variable while retaining the global binding.
> Therefore, it appears to me that Guile needs a real dynamic environment
> if Elisp function bindings are to be non-special without breaking cl.el
> FLET.

Aliases

> Elisp has both defalias and defvaralias. My first thought was to assign
> the same variable object to multiple names in an obarray; that works for
> the submodule-based implementation of symbol cells currently used, but
> not for the bigvars implementation. It would be useful to have a special
> object like SCM_UNDEFINED to mark cells to be redirected, but if that's
> not practical, redirected cells can remain SCM_UNBOUND, redirection
> information information can be stored in the property cell, references
> to aliases can be processed in an error handler, and Elisp setq can
> check the property cell for every assignment. I think I'll implement a
> proper def(var)alias next; subr.el and other basic libraries load with
> defalias = fset, but Emacs implements buffer- and frame-local variables
> using a similar technique so this will make it easier to implement that
> when it becomes necessary.

Nil

I'll have to go through my notes, but I'm not sure #nil is the best solution

Adding VM instructions

Procedure for adding nil?' and not-nil?' VM instructions for Elisp conditionals:

1. Add the new instructions to "vm-i-scheme.c". This is all that needs to be done for it to be usable in GLIL.

2. Test it in the REPL:

scheme@(guile-user)> ,L glil
Happy hacking with Guile Lowlevel Intermediate Language (GLIL)!  To switch back, type `,L scheme'.
glil@(guile-user)> (program () (std-prelude 0 0 #f) (const #f) (call nil? 1) (call return 1))
$1 = #t
glil@(guile-user)> (program () (std-prelude 0 0 #f) (const ()) (call nil? 1) (call return 1))
$2 = #t
glil@(guile-user)> (program () (std-prelude 0 0 #f) (const #nil) (call nil? 1) (call return 1))
$3 = #t
glil@(guile-user)> (program () (std-prelude 0 0 #f) (const #t) (call nil? 1) (call return 1))
$4 = #f
glil@(guile-user)> (program () (std-prelude 0 0 #f) (const #f) (call not-nil? 1) (call return 1))
$5 = #f
glil@(guile-user)> (program () (std-prelude 0 0 #f) (const ()) (call not-nil? 1) (call return 1))
$6 = #f
glil@(guile-user)> (program () (std-prelude 0 0 #f) (const #nil) (call not-nil? 1) (call return 1))
$7 = #f
glil@(guile-user)> (program () (std-prelude 0 0 #f) (const #t) (call not-nil? 1) (call return 1))
$8 = #t

3. Add a Scheme or C definition in the (guile) module.

4. Add the procedure to the appropriate lists in "language/tree-il/primitives.scm".

5. Compile an application of the procedure to GLIL. It should be recognized as a primitive:

scheme@(guile-user)> (compile '(nil? #f) #:to 'glil)
$1 = #<glil (program () (std-prelude 0 0 #f) (label :LCASE99) (const #f) (call nil? 1) (call return 1))>

6. That's it!

There may be more work for certain types of instructions. For `nil?', I added support for a new type of conditional, which involved adding a branch instruction and support for it in the Tree-IL compiler.

@@ Should VM instructions be renumbered?

Miscellaneous

Trying to compile the invalid Tree-IL program (begin) used to lead to weird errors

See the "elisp glimpses" email for some notes on a package system for Elisp

The macros eval-when-compile and eval-and-compile work everywhere, unlike cl:eval-when. But macro definitions only have compile-time effects at the top level in Elisp -- the current behavior (2011-06-20) is incorrect

I originally thought that &rest alone at the end of a lambda list meant to ignore extra arguments, but in fact &rest and &optional are ignored entirely if there isn't a symbol following them. For example, (lambda (x &rest) ...) = (lambda (x) ...). (I don't think it's necessary to do the same in GuileElisp)

In Emacs, although (eval-and-compile (setq lexical-binding t)) has the expected effect with the byte-compiler, the evaluator only checks the local value before evaluating a buffer or loading a file.

Emacs's lexical-let interacts oddly with lambda:

(setq x 0)
(defun dynx () x)
(lexical-let ((x 1)) ((lambda (x) (list x (dynx))) 2))
;; => (1 2) in Emacs, (2 0) in Guile
(lexical-let ((x 1)) (let ((x 2)) (list x (dynx))))
;; => (2 0) in both implementations

In Emacs, the lambda expression establishes a dynamic binding but x still refers to the lexical binding. The let example works as you'd expect because the CL package macroexpands it to a letf of symbol-value. However, the CL package's macroexpander more or less ignores the lambda expression's argument list even though x is a symbol macro. This situation is probably quite rare, so I'm going to leave it as a known incompatibility.


CategoryGuileEmacs

GuileElisp (last edited 2011-07-10 22:14:41 by BtTempleton)