The named-closure Reference Manual

Next: , Previous: , Up: (dir)   [Contents][Index]

The named-closure Reference Manual

This is the named-closure Reference Manual, version 0.0.1, generated automatically by Declt version 4.0 beta 2 "William Riker" on Thu Sep 15 05:42:52 2022 GMT+0.

Table of Contents


1 Introduction

#+TITLE: NAMED-CLOSURE - introspectable and redefinable closures
* Why?
  Hot take: closures in Common Lisp and most lisps are broken for
  long-running images. (Maybe more so in non-lisps, but who cares.)

  Comparing to function symbols, closures are nearly unusable in the
  following aspects:
  
  1. They're hard to introspect, both from a user aspect and (even
     more so) programmatically. SBCL for example allows you to
     retrieve values in the flat closure, but do not save variable
     information.
     
  2. As a consequence, they in general don't have readable print
     syntax, and it might be impossible to write one. Function symbols
     on the other hand can be print and then read to get the exactly
     same object.
     
  3. They're near impossible to redefine. For a function symbol,
     setting its function cell causes all call site to consistently
     call the new definition. This is impossible for closures.

     Why redefining closure? It allows you to fix buggy code for
     closure (nobody always write correct code the first
     time!). Without redefinability you'll have closure with wrong
     code floating around in the image that is almost impossible to
     fix, unless you remember all the location the closure is used and
     fix it manually -- I find it much less pleasant and isn't always
     possible.

  Closures are still useful because:
  
  1. Concise syntax.
     
  2. They're the lingua franca for a whole bunch of "functional
     programs", which expect objects that are funcallable.

* What
  ~NAMED-CLOSURE~ provides ~DEFNCLO~ and ~NCLO~.

  - ~(nclo NAME LAMBDA-LIST . BODY)~ is similar to ~(lambda
    LAMBDA-LIST . BODY)~, but returns a funcallable object with slots
    corresponding to free variable in ~BODY~, has readable print
    syntax, and if ~nclo~ with the same ~NAME~ is encountered (for
    example, if re-evaluated from REPL), the function definition of
    all such funcallable objects is updated. Closed variables with the
    same names are carried over across update.

  - ~defnclo~
    #+BEGIN_SRC lisp
      (defnclo something (lambda-list-1...)
          (lambda-list-2...)
        body...)
    #+END_SRC
    is similar to
    #+BEGIN_SRC lisp
      (defun make-something (lambda-list-1...)
        (lambda (lambda-list-2...)
          body...))
    #+END_SRC
    except that ~make-something~ now returns a funcallable object with
    slots corresponding to variables declared in ~lambda-list-1~, has
    readable print syntax, and re-evaluating the ~defnclo~ updates the
    function definition of all such funcallable objects. Closed
    variables with the same names are carried over across update.

  Note: newly introduced variables are unbound for updated old
  closures! This will likely cause an ~unbound-slot~ condition when
  such closure is called. You're free to use the ~store-value~ restart
  your implementation (usually) provides to fix up the closure if
  possible.  There isn't anything more we can help ~:/~

* !!!Caveat!!!
  Saying ~nclo~ is similar to ~lambda~ is *a lie*. Currently, ~nclo~
  effectively copies the captured environment instead of directly
  link to it. See [[https://github.com/BlueFlo0d/named-closure/issues/1]]
  to understand the important behavioral difference between ~nclo~ and ~lambda~.

** Excuses

   It is possible to simulate the "correct" environment sharing behavior
   identical to ~lambda~. I'm currently not doing it because
   - It complicates introspection and proper readable printing
   - It nukes upgradability. While it's not unreasonable to ask user
     to fix up old closures using ~store-value~, it sounds pretty
     impratical to expect user to fix the sharing structure between
     different closures.
   - Closures are convenient. But remember we have *real objects*!
     If you need to rely on multiple closures sharing one environments,
     maybe it's better to just use CLOS.
   
* Example
  #+BEGIN_SRC lisp
    (use-package :named-closure)
    (defun make-inc-1 (x) (nclo inc (y) (setf x (+ x y))))
    (defparameter *test-instance* (make-inc-1 5))
    *test-instance* ; => #.(MAKE-INC :X 5)
    (funcall *test-instance* 6) ; => 11
    (funcall *test-instance* 6) ; => 17
    (defun make-inc-1 (x) (nclo inc (y) (setf x (- x y)))) ; changed our mind!!!
    (funcall *test-instance* 6) ; => 11
    (funcall *test-instance* 6) ; => 5
  #+END_SRC

  p.s. I will probably ensure ~NAME-CLOSURE~ only ever exports obscure
  names, so it should be quite safe to ~use-package~ it!
  
* How
  Under the hood, ~defnclo~ defines a funcallable class named
  ~something~, which in turn indirect calls through its
  class-allocated ~'named-closure::code~ slot so that it is
  redefinable.

  ~nclo~ is implemented by walking ~BODY~ and collecting its free
  variables, then calling ~defnclo~ with the free variable list (with
  ~&key~ prepended) passed as ~LAMBDA-LIST-1~.

  Note: Because free variables are converted to keyword argument,
  their ~symbol-name~ must be distinct. Is this good enough?

  There's one subtlety involved with ~nclo~: ~nclo~ usually appears as
  a non-top-level form, but it needs to ensure creating a top-level
  function definition for ~NAME~ in the runtime environment. We do this
  by abusing ~load-time-value~.  
  


2 Systems

The main system appears first, followed by any subsystem dependency.


Previous: , Up: Systems   [Contents][Index]

2.1 named-closure

Named closures

Maintainer

Qiantan Hong <qhong@alum.mit.edu>

Author

Qiantan Hong <qhong@alum.mit.edu>

License

GPLv3+

Version

0.0.1

Dependencies
  • closer-mop (system).
  • alexandria (system).
  • hu.dwim.walker (system).
  • hu.dwim.util (system).
Source

named-closure.asd.

Child Component

named-closure.lisp (file).


3 Files

Files are sorted by type and then listed depth-first from the systems components trees.


Previous: , Up: Files   [Contents][Index]

3.1 Lisp


3.1.1 named-closure/named-closure.asd

Source

named-closure.asd.

Parent Component

named-closure (system).

ASDF Systems

named-closure.


3.1.2 named-closure/named-closure.lisp

Source

named-closure.asd.

Parent Component

named-closure (system).

Packages

named-closure.

Public Interface
Internals

4 Packages

Packages are listed by definition order.


Previous: , Up: Packages   [Contents][Index]

4.1 named-closure

Source

named-closure.lisp.

Use List

closer-common-lisp.

Public Interface
Internals

5 Definitions

Definitions are sorted by export status, category, package, and then by lexicographic order.


Next: , Previous: , Up: Definitions   [Contents][Index]

5.1 Public Interface


5.1.1 Macros

Macro: defnclo (name lambda-list-1 lambda-list-2 &body body)

Defines a named closure type.

Similar to
(defun make-NAME LAMBDA-LIST-1
(lambda LAMBDA-LIST-2 . BODY))

except that ‘make-NAME’ now returns a funcallable object with slots corresponding to variables declared in LAMBDA-LIST-1, has readable print syntax, and re-evaluating the DEFNCLO updates the function definition of all such funcallable objects. Closed variables with the same names are carried over across update.

Package

named-closure.

Source

named-closure.lisp.

Macro: nclo (name lambda-list &body body)

Similar to (lambda LAMBDA-LIST . BODY).

Returns a funcallable object with slots corresponding to free variable in BODY, has readable print syntax, and if ‘nclo’ with the same NAME is encountered (for example, if re-evaluated from REPL), the function definition of all such funcallable objects is updated. Closed variables with the same names are carried over across update.

Package

named-closure.

Source

named-closure.lisp.


Previous: , Up: Public Interface   [Contents][Index]

5.1.2 Classes

Class: nclo
Package

named-closure.

Source

named-closure.lisp.

Direct superclasses

funcallable-standard-object.

Direct slots
Slot: code
Allocation

:class


5.2 Internals


Next: , Previous: , Up: Internals   [Contents][Index]

5.2.1 Special variables

Special Variable: *inhibit-walker-eval-load-time-value*

Mega Haxx!

Package

named-closure.

Source

named-closure.lisp.


Previous: , Up: Internals   [Contents][Index]

5.2.2 Ordinary functions

Function: lambda-list-fvs (lambda-list)
Package

named-closure.

Source

named-closure.lisp.

Function: lambda-list-serialize-form (lambda-list)
Package

named-closure.

Source

named-closure.lisp.

Function: make-function-name (symbol)
Package

named-closure.

Source

named-closure.lisp.

Function: walk-fvs (form env)
Package

named-closure.

Source

named-closure.lisp.


Appendix A Indexes


Next: , Previous: , Up: Indexes   [Contents][Index]

A.1 Concepts