An open API service indexing awesome lists of open source software.

https://github.com/scymtym/computation.environment

Overly general (and slow) environment library
https://github.com/scymtym/computation.environment

environments namespace visualization

Last synced: 6 months ago
JSON representation

Overly general (and slow) environment library

Awesome Lists containing this project

README

          

#+TITLE: computation.environment README
#+AUTHOR: Jan Moringen
#+EMAIL: jmoringe@techfak.uni-bielefeld.de
#+LANGUAGE: en

#+SEQ_TODO: TODO STARTED | DONE
#+OPTIONS: num:nil

* STARTED Introduction

The ~compoutation.environment~ library provides protocols and
implementations for environments (not necessarily Common Lisp
environments or Lisp environments), that is data structures which
manage bindings of names to values. Different kinds of environments
are available:

+ Global environments
+ Lexical environments

Lexical environments can be organized in a hierarchy such that child
environments inherit entries from their ancestors.

Another feature are first-class namespaces: environments contain
namespaces which in turn control how names are organized and
processed within the environment.

* STARTED Concepts

This section introduces the terminology used in the remainder of
this document.

+ <> Name :: In this context, a name is an object the
purpose of which is referring to another object. Their are
different kinds of names with different associated rules
regarding which objects are legal name of that kind and the
comparison of names. All names of one particular kind form a
[[glossary:namespace][namespace]].

+ <> Namespace :: A namespace defines a
particular kind of [[glossary:name][name]] for which it controls

+ <> Name syntax :: Which objects are valid
names in the namespace?

For example, legal variable names are typically symbols
(excluding constants such as ~cl:nil~, ~cl:t~ and
~cl:pi~). Function names, on the other hand, can also be of
the form ~(cl:setf NAME)~.

+ <> Name comparison :: Given two
objects which are valid names, how to decide whether they
designate the same name?

For example, ~name1~ and ~name2~ must be ~eq~ in order to
designate the same [[glossary:name][variable name]]. However, ~name1~ and
~name2~ in ~(let ((name1 (list 'setf foo)) (name2 (list 'setf
foo))))~ are not ~eq~ yet still designate the same /function
name/.

+ Entry isolation :: Not sure

For example, legal variable names are typically symbols (excluding
constants such as ~cl:nil~, ~cl:t~ and ~cl:pi~) and can be
compared using ~cl:eq~. Function names, on the other hand, can
also be of the form ~(cl:setf NAME)~ and must be compared using
~cl:equal~ or a specialized function. In a non-Lisp use-case,
names could be non-empty strings and ~string=~ or
~string-equal~ could be appropriate comparison functions.

+ <> Environment :: At the minimum, a
collection of [[glossary:namespace][namespaces]] and associated [[glossary:binding][binding]] collections. An
environment may have other parts such as a reference to a
parent environment.

+ <> Binding :: An association of a [[glossary:name][name]] in a
[[glossary:namespace][namespace]] and a value.

+ <> Scope :: A scopes controls the way in which a
value is looked up for a given name, [[glossary:namespace][namespace]] and [[glossary:environment][environment]].

As a concrete example, the /direct/ scope constrains the lookup
to the specified environment, that is ancestors of the
environment are not considered.

#+BEGIN_SRC plantuml :exports results :file static-view.png
hide members

class environment
class namespace
class bindings

class eq-namespace extends namespace {
}
class equal-namespace extends namespace {
}

environment -> namespace
namespace -> bindings
#+END_SRC

#+RESULTS:
[[file:static-view.png]]

Object diagram without hierarchy

#+BEGIN_SRC plantuml :exports results :file objects-without-hierarchy.png
object "environment : lexical-environment" as environment
object "function-namespace ; function-name-namespace" as function_namespace
note bottom of function_namespace {
Legal names are of type ""(or symbol (cons (eql setf) (cons symbol null)))""
}
map "function-bindings : equal-hash-table-bindings" as function_bindings {
function-name₁ => function₁
function-name₂ => function₂
… => …
}
object "variable-namespace : eq-namespace" as variable_namespace
map "variable-bindings : eq-hash-table-bindings" as variable_bindings {
variable-name₁ => variable₁
variable-name₂ => variable₂
… => …
}
object "namespace-namespace : eq-namespace" as namespace_namespace
map "namespace-bindings : eq-hash-table-bindings" as namespace_bindings {
namespace *---> namespace_namespace
function *---> function_namespace
variable *---> variable_namespace
}

environment *-- namespace_bindings
environment *-- function_bindings
environment *-- variable_bindings
#+END_SRC

#+RESULTS:
[[file:objects-without-hierarchy.png]]

Object diagram with hierarchy

#+BEGIN_SRC plantuml :exports results :file objects-with-hierarchy.png
object "global-environment" as global_environment
object "namespace-namespace : eq-namespace" as namespace_namespace
object "variable-namespace : eq-namespace" as variable_namespace
object "function-namespace ; function-name-namespace" as function_namespace
map "namespace-bindings : eq-hash-table-bindings" as namespace_bindings {
namespace *--> namespace_namespace
function *--> function_namespace
variable *--> variable_namespace
}

object "environment : lexical-environment" as environment
map "function-bindings : equal-hash-table-bindings" as function_bindings {
function-name₁ => function₁
function-name₂ => function₂
… => …
}
map "variable-bindings : eq-hash-table-bindings" as variable_bindings {
variable-name₁ => variable₁
variable-name₂ => variable₂
… => …
}

global_environment *-right- namespace_bindings

environment -up-> global_environment : "parent"
environment *-- function_bindings
environment *-- variable_bindings
#+END_SRC

#+RESULTS:
[[file:objects-with-hierarchy.png]]

* STARTED Tutorial

#+BEGIN_SRC lisp :exports results :results silent
(ql:quickload :computation.environment)
#+END_SRC

** STARTED Making environments

This library provides different kinds of environments such as
~global-environment~. Clients create instances of theses
environment classes using ~make-instance~. In order to hold any
[[glossary:binding][bindings]], an environment must contain at least one
[[glossary:namespace][namespace]]. Namespaces are stored as bindings in a special
namespace, but the details of that mechanism are not important at
this point. The following code creates a global environment that
contains a single namespace, called ~function~, but no "ordinary"
bindings:

#+BEGIN_SRC lisp :exports both :results output
(defvar *environment* (make-instance 'computation.environment:global-environment))

(setf (computation.environment:lookup 'function 'computation.environment:namespace *environment*)
(make-instance 'computation.environment::eq-namespace))

(describe *environment*)
#+END_SRC

#+RESULTS:
: #
:
: EQ-NAMESPACE COMMON-LISP:FUNCTION 0 entries
:

** STARTED Looking up bindings

As mentioned above, the new environment does not yet contain any
bindings in its ~function~ namespace, so an attempt to look up a
function in that environment must result in an error:

#+BEGIN_SRC lisp :exports both
(handler-case
(computation.environment:lookup 'foo 'function *environment*)
(error (condition)
(princ-to-string condition)))
#+END_SRC

#+RESULTS:
: An entry for name FOO does not exist in namespace #
: in environment #

Correspondingly, listing all entries contained in the ~function~
namespace in the environment results in the empty list:

#+BEGIN_SRC lisp :exports both
(computation.environment:entries 'function *environment*)
#+END_SRC

#+RESULTS:
: NIL

** STARTED Adding bindings

New bindings can be created in two ways

1. Destructively modifying a given environment by adding the new
binding to it

2. Creating a new environment object that contains the new binding
and is linked to the existing environment object

The first way can be achieved using the ~(setf
computation.environment:lookup)~ generic function:

#+BEGIN_SRC lisp :exports both
(setf (computation.environment:lookup 'foo 'function *environment*) :foo)
(computation.environment:lookup 'foo 'function *environment*)
#+END_SRC

#+RESULTS:
: :FOO

The functions ~computation.environment:augmented-environment~ and
~computation.environment:augmented-namespace~ implement the second
way:

#+BEGIN_SRC lisp :exports both :results output
(let ((augmented (computation.environment:augmented-namespace
*environment* 'function '(bar) '(:bar)
:class 'computation.environment::lexical-environment)))
(describe augmented)
(format t "~&----------------")
(handler-case
(print (computation.environment:lookup 'bar 'function augmented))
(error (condition)
(princ-to-string condition))))
#+END_SRC

#+RESULTS:
: #
:
: EQ-NAMESPACE COMMON-LISP:FUNCTION 2 entries
: BAR → :BAR
: FOO → :FOO [inherited from #]
: ----------------
: :BAR

but the original environment is not affected:

#+BEGIN_SRC lisp :exports both :results output
(describe *environment*)
#+END_SRC

#+RESULTS:
: #
:
: EQ-NAMESPACE COMMON-LISP:FUNCTION 1 entry
: FOO → :FOO

** STARTED Shadowing

#+BEGIN_SRC lisp :exports both :results output
(let ((augmented (computation.environment:augmented-namespace
*environment* 'function '(foo) '(:bar)
:class 'computation.environment::lexical-environment)))
(describe *environment*)
(terpri) (terpri)
(describe augmented))
#+END_SRC

#+RESULTS:
#+begin_example
#

EQ-NAMESPACE COMMON-LISP:FUNCTION 1 entry
FOO → :FOO

#

EQ-NAMESPACE COMMON-LISP:FUNCTION 2 entries
FOO → :BAR
FOO → :FOO [inherited from #]
#+end_example

* STARTED Dictionary

#+BEGIN_SRC lisp :results none :exports none :session "doc"
#.(progn
#1=(ql:quickload '(:computation.environment :alexandria :split-sequence :text.documentation-string))
'#1#)
,#+no (defun doc (symbol kind)
(let* ((lambda-list (sb-introspect:function-lambda-list symbol))
(string (documentation symbol kind))
(lines (split-sequence:split-sequence #\Newline string))
(trimmed (mapcar (alexandria:curry #'string-left-trim '(#\Space)) lines)))
(format t "~~~(~A~)~~ src_lisp[:exports code]{~<~{~A~^ ~}~:@>}~2%~{~A~^~%~}"
symbol (list lambda-list) trimmed)))
(in-package #:cl-user)

(defun doc (symbol kind)
(let* ((lambda-list (sb-introspect:function-lambda-list symbol))
(string (documentation symbol kind))
(trimmed (when string
(text.documentation-string.parser::remove-common-leading-whitespace string))))
(format t "#+begin_quote~%")
(format t "*~~~(~A~)~~* src_lisp[:exports code]{~<~{~(~A~)~^ ~}~:@>}~2%"
symbol (list lambda-list))
,#+no (format t "~(~A~) ~<~{~A~^ ~}~:@>~2%"
symbol (list lambda-list))
(if trimmed
(text.documentation-string.unparser::render-org
'list
(text.documentation-string.parser::decide-meta-variables
(text.documentation-string.parser:parse trimmed 'list) 'list
:initial-entries (map 'list #'symbol-name lambda-list))
,*standard-output*)
(format t "/not documented/~%"))
(format t "#+end_quote~%")))
#+END_SRC

** STARTED The bindings protocol

This low-level protocol is responsible for creating and accessing
[[glossary:binding][bindings]] in a given [[glossary:namespace][namespace]] in one particular
[[glossary:environment][environment]]. Clients should usually use the higher-level
[[#sec:dictionary-environment-protocol][environment protocol]].

<>
#+BEGIN_SRC lisp :results output raw :exports results :session "doc"
(doc 'computation.environment:make-bindings 'function)
#+END_SRC

#+RESULTS:
#+begin_quote
*~make-bindings~* src_lisp[:exports code]{namespace environment}

Return a bindings object for /namespace/ in /environment/.

The returned object must be usable with /namespace/ and /environment/
in the bindings protocol.
#+end_quote

<>
#+BEGIN_SRC lisp :results output raw :exports results :session "doc"
(doc 'computation.environment:entry-count-in-bindings 'function)
#+END_SRC

#+RESULTS:
#+begin_quote
*~entry-count-in-bindings~* src_lisp[:exports code]{bindings namespace
environment}

Return the number of entries in /bindings/ in /namespace/, /environment/.
#+end_quote

<>
#+BEGIN_SRC lisp :results output raw :exports results :session "doc"
(doc 'computation.environment:map-entries-in-bindings 'function)
#+END_SRC

#+RESULTS:
#+begin_quote
*~map-entries-in-bindings~* src_lisp[:exports code]{function bindings namespace
environment}

Call /function/ with each entry in /bindings/ in /namespace/, /environment/.

The lambda list of /function/ must be compatible with

#+BEGIN_EXAMPLE
(name value)
#+END_EXAMPLE
where /name/ is the name of the entry and /value/ is the associated
value. Any value returned by /function/ is discarded.
#+end_quote

<>
#+BEGIN_SRC lisp :results output raw :exports results :session "doc"
(doc 'computation.environment:lookup-in-bindings 'function)
#+END_SRC

#+RESULTS:
#+begin_quote
*~lookup-in-bindings~* src_lisp[:exports code]{name bindings namespace environment}

Lookup and return the value for /name/ in /bindings/ in /namespace/, /environment/.

Return two values: 1) the found value or ~nil~ 2) a Boolean
indicating whether a value exists
#+end_quote

<>
#+BEGIN_SRC lisp :results output raw :exports results :session "doc"
(doc '(setf computation.environment:lookup-in-bindings) 'function)
#+END_SRC

#+RESULTS:
#+begin_quote
*~(setf lookup-in-bindings)~* src_lisp[:exports code]{new-value name bindings namespace environment}

Set the value of /name/ in /bindings/ in /namespace/, /environment/ to /new-value/.

Return /new-value/ as the primary return value.
#+end_quote

** STARTED The environment protocol
:PROPERTIES:
:CUSTOM_ID: sec:dictionary-environment-protocol
:END:

This protocol allows accessing the [[glossary:binding][bindings]] in all [[glossary:namespace][namespaces]] in a
given [[glossary:scope][scope]] starting at a particular [[glossary:environment][environment]]. The scope
controls, for example, whether bindings inherited from parent
environments should be considered.

<>
#+BEGIN_SRC lisp :results output raw :exports results :session "doc"
(doc 'computation.environment:entry-count 'function)
#+END_SRC

#+RESULTS:
#+begin_quote
*~entry-count~* src_lisp[:exports code]{namespace environment &key scope}

Return the number of entries in /namespace/ in /environment/ for /scope/.
#+end_quote

<>
#+BEGIN_SRC lisp :results output raw :exports results :session "doc"
(doc 'computation.environment:map-entries 'function)
#+END_SRC

#+RESULTS:
#+begin_quote
*~map-entries~* src_lisp[:exports code]{function namespace environment &key
scope}

Call /function/ for each entry in /namespace/ in /environment/ for /scope/.

The lambda list of /function/ must be compatible with

#+BEGIN_EXAMPLE
(name value container)
#+END_EXAMPLE
Any value returned by /function/ is discarded.
#+end_quote

<>
#+BEGIN_SRC lisp :results output raw :exports results :session "doc"
(doc 'computation.environment:entries 'function)
#+END_SRC

#+RESULTS:
#+begin_quote
*~entries~* src_lisp[:exports code]{namespace environment &key scope}

Return entries in /namespace/ in /environment/ for /scope/ as an alist.
#+end_quote

<>
#+BEGIN_SRC lisp :results output raw :exports results :session "doc"
(doc 'computation.environment:lookup 'function)
#+END_SRC

#+RESULTS:
#+begin_quote
*~lookup~* src_lisp[:exports code]{name namespace environment &key
if-does-not-exist scope}

Lookup and return the value for /name/ in /namespace/ in /environment/ for /scope/.

Return three values:
1) the found value (subject to /if-does-not-exist/)
2) a Boolean indicating whether a value exists
3) the environment in which the value was found.

/scope/ controls which bindings are considered. Examples of scopes
include binding directly contained in /environment/ and bindings
contained in /environment/ or any of its ancestor environments.

/if-does-not-exist/ controls the behavior in case such a value does
not exist.
#+end_quote

<>
#+BEGIN_SRC lisp :results output raw :exports results :session "doc"
(doc '(setf computation.environment:lookup) 'function)
#+END_SRC

#+RESULTS:
#+begin_quote
*~(setf lookup)~* src_lisp[:exports code]{new-value name namespace environment &key if-does-not-exist}

Set the value of /name/ in /namespace/ in /environment/ to /new-value/.

Return /new-value/ as the primary return value.

/if-does-not-exist/ is accepted for parity with ~lookup~.
#+end_quote

<>
#+BEGIN_SRC lisp :results output raw :exports results :session "doc"
(doc 'computation.environment:make-or-update 'function)
#+END_SRC

#+RESULTS:
#+begin_quote
*~make-or-update~* src_lisp[:exports code]{name namespace environment make-cont
update-cont &key scope}

Use /make-cont/ or /update-cont/ to set /name/ in /namespace/ in /environment/ for /scope/.

Return four values:
1) the new value of /name/ in /namespace/ in /environment/
2) a Boolean indicating whether the value of /name/ in
/namespace/ in /environment/ has been updated
3) the previous value of /name/ in /namespace/ in /environment/
4) the container in which the previous value was found.

If no value exists for /name/ in /namespace/ in /environment/, /make-cont/
is called to make a value which is then set as the value of /name/
in /namespace/ in /environment/.

If a value exists for /name/ in /namespace/ in /environment/,
/update-cont/ is called with the existing value and the container of
that existing value to potentially compute a new value. If a new
value is computed, that value is set as the value of /name/ in
/namespace/ in /environment/.

/make-cont/ has to be a function with a lambda list compatible with

#+BEGIN_EXAMPLE
()
#+END_EXAMPLE
and has to return the new value as its primary return value.

/update-cont/ has to be a function with a lambda list compatible
with

#+BEGIN_EXAMPLE
(old-value old-container)
#+END_EXAMPLE
and must return between two values and three values when called:
1) a new value based on OLD-VALUE
2) a Boolean indicating whether the first return value is different
from /OLD-VALUE/
3) optionally a container in which the returned new value should be
set.
#+end_quote

<>
#+BEGIN_SRC lisp :results output raw :exports results :session "doc"
(doc 'computation.environment:ensure 'function)
#+END_SRC

#+RESULTS:
#+begin_quote
*~ensure~* src_lisp[:exports code]{name namespace environment make-cont &key
scope}

Maybe use /make-cont/ to set /name/ in /namespace/ in /environment/ for /scope/.

Return four values:
1) the new value of /name/ in /namespace/ in /environment/
2) a Boolean indicating whether the value of /name/ in
/namespace/ in /environment/ has been updated
3) the container in which the previous value was found.

If no value exists for /name/ in /namespace/ in /environment/, /make-cont/
is called to make a value which is then set as the value of /name/
in /namespace/ in /environment/.

/make-cont/ has to be a function with a lambda list compatible with

#+BEGIN_EXAMPLE
()
#+END_EXAMPLE
and has to return the new value as its primary return value.
#+end_quote

** STARTED The hierarchical environment protocol

<>
#+BEGIN_SRC lisp :results output raw :exports results :session "doc"
(in-package #:cl-user)
(doc 'computation.environment:parent 'function)
#+END_SRC

#+RESULTS:
#+begin_quote
*~parent~* src_lisp[:exports code]{environment}

Return the parent of /environment/ or ~nil~.

#+end_quote

<>
#+BEGIN_SRC lisp :results output raw :exports results :session "doc"
(doc 'computation.environment:root 'function)
#+END_SRC

#+RESULTS:
#+begin_quote
*~root~* src_lisp[:exports code]{environment}

Return the ancestor of /environment/ that has no parent.

In particular, if /environment/ does not have a parent, return
/environment/.
#+end_quote

<>
#+BEGIN_SRC lisp :results output raw :exports results :session "doc"
(doc 'computation.environment:depth 'function)
#+END_SRC

#+RESULTS:
#+begin_quote
*~depth~* src_lisp[:exports code]{environment}

Return the number of ancestors /environment/ has.

In particular, return /0/ if /environment/ does not have a parent.

#+end_quote

# Local Variables:
# eval: (mapcar 'require '(ob-lisp ob-plantuml))
# End: