feat: LLM Wiki 세컨드 브레인 초기 셋팅

- CLAUDE.md 생성 (볼트 운영 규칙, Karpathy LLM Wiki 10가지 규칙)
- 나의 핵심 맥락.md 생성 (아키텍트 프로필, 세컨드 브레인 목적, 핵심 소스)
- raw/ 구조 정립 (book/기존 설계원칙 보존, articles/repos/notes/ 추가)
- wiki/ 초기화 (index.md, log.md, concepts/sources/patterns/ 폴더)
- output/ 초기화
- LLMWiki/ 기존 프롬프트 패턴 파일 보존

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
minsung
2026-04-30 14:34:29 +09:00
parent d7a123de97
commit 44e26d6972
48 changed files with 14334 additions and 0 deletions

View File

@@ -0,0 +1,816 @@
# **Layering**
In section 1.1 we alluded to the idea that programming could learn from the practice of architecture. A programmer might start with an executable skeleton plan (a *parti* ) to help try out an idea. When the *parti* looks good the programmer could elaborate it with more information.
For example, declared implementation types may enable the compilation of efficient code and inhibit the occurrence of type errors. Declared dimensions and units may be added to prevent some bugs and support documentation. Assertions of predicates can help with the localization of errors that occur at run time and they could support the automatic or manual construction of proofs of "correctness." Declarations of how much precision is needed for some numerical quantities and operations can give clarity to numerical analysis problems. Suggestions of alternative implementations can enable useful degeneracy in an implementation. We can track the provenance of a result by carrying dependencies.
But the usual way of adding these important and powerful features to the text of a program turns the program text into a tangled mess. To continue with the architecture analogy, it does not separate the served spaces from the servant spaces. The separation of the "essential" features of a program (the code that defines its behavior) from the "accidental" ones (e.g., type information for a compiler or code for logging) has been an important issue. Aspectoriented programming [67] was an attempt to address part of this problem, by explicitly identifying "cross-cutting concerns" such as
logging. Layering is another way to effect the separation. The ability to annotate any piece of data or code with other data or code is a crucial mechanism in building flexible systems. The decoration of a value is a generalization of the tagging used to support extensible generic operations. Here we introduce the idea of *layered programming*. Both the data and the procedures that process it will be made up of multiple layers that enable additive annotation without introducing clutter.
### 6.1 Using layers
Layers give us the ability to sketch out a computation and then elaborate that computation with metadata that is processed along with the computation. Let's consider some annotations that we think may be valuable in many situations. For example, suppose we are interested in using Newton's force law for gravity:
```
(define (F m1 m2 r)
(/ (* G m1 m2) (square r)))
```
This is a simple numerical calculation, but we can elaborate it to carry support information and units.
We find Newton's constant G by looking up a recent measurement published by NIST:
```
(define G
(layered-datum 6.67408e-11
unit-layer (unit 'meter 3 'kilogram -1 'second -2)
support-layer (support-set 'CODATA-2018)))
```
Here we show the numerical value of the measurement, the units of that measurement ( $m^3/(kg s^2)$ ), and the source of the data (its support). We could extend this to also carry the uncertainty in the measurement as a range in another layer, but we won't do that here.
We can also find the mass of the Earth, the mass of the Moon, and the distance to the Moon (semimajor axis) from other sources:
```
(define M-Earth
(layered-datum 5.9722e24
unit-layer (unit 'kilogram 1)
support-layer
(support-set 'Astronomical-Almanac-2016)))
(define M-Moon
(layered-datum 7.342e22
unit-layer (unit 'kilogram 1)
support-layer
(support-set 'NASA-2006)))
(define a-Moon
(layered-datum 384399e3
unit-layer (unit 'meter 1)
support-layer
(support-set 'Wieczorek-2006)))
```
Now we can ask the question, "What is the gravitational force of attraction between the Earth and the Moon at that distance?" and we will get the answer:
```
(pp (F M-earth M-Moon a-moon))
#[layered-datum 1.9805035857209e20]
(base-layer 1.9805035857209e20)
(unit-layer (unit kilogram 1 meter 1 second -2))
(support-layer
(support-set Wieczorek-2006
NASA-2006
Astronomical-Almanac-2016
CODATA-2018))
```
The result gives the numerical value, the units of that result, and the sources that the result depended upon.
### **6.2 Implemention of layering**
There are two parts to layering. The first is that it must be possible to create a datum that contains multiple layers of information. In our example, we used layered-datum to do this. The second part is that we need to be able to enhance a procedure so that it can process
each layer (somewhat) independently. A procedure enhanced in this way is called a *layered procedure*.
We also need a way to assign names to layers. Every layer must have a name, so that the layer in a datum can be specified. The name is also used by a layered procedure to connect the processing for that layer to the corresponding layers in the incoming data. We have written our example to use variables to refer to layer names, as in unit-layer, which is bound to the name for the unit layer. This makes the user interface independent of the details of how a layer name is specified; this will turn out to be useful.
Another aspect of layer naming is that there must be a distinguished *base layer*, which represents the underlying computation being performed. In our example using layereddatum, the base layer's value is distinguished by being the first argument and by not having an associated name.
Layered data can be built from simple data structures. We can use any convenient data structure that can associate a layer name with a value and that permits many such associations. A special name can be used to identify the base layer, making the data structure simple and uniform.
Building layered procedures is more complicated, because the processing for most layers will need some information from the computation in the base layer. For example, suppose we are multiplying two numbers that carry support information. Normally, the support of the result is the union of the supports of the arguments. But suppose one argument has a base-layer value of zero; then the support of the result is the support of the zero, and the support of the other argument is irrelevant.
The base layer must not depend on any non-base layer because that violates the idea of the base layer: that it is an independent computation that the other layers enhance. And a non-base layer should not depend on another non-base layer. A non-base layer generally shouldn't share information with another non-base layer since its behavior would be different depending on the presence or absence of the other layer. This would be inconsistent with our general approach of building additive programs.
So building a layered procedure involves a balance between sharing information from the base layer to the non-base layers and isolating layers in most other cases. We will address this in the next sections as we explore the details of implementing layering.
#### **6.2.1 Layered data**
A layered data item is a base value annotated with extra information about that value. The annotation is an association of layer names with their values. For example, the number 2 may be the base value in many data items: if we are dealing in potatoes there may be a 2 dollar price tag on a 2-pound bag of potatoes. Each of these instances of the number 2 must be a distinct data item, with different values (dollars or pounds) for the units layer. There may be other layers as well: the 2-dollar price may have information saying how it was derived from the price paid to the farmer and the cost of transportation and processing.
To address this issue we introduce the *layered datum*. A layered datum is represented as a bundle that contains an association of layers and their values. So a 2-pound quantity of potatoes and a 2 dollar price for potatoes will be separate layered data items:
```
(define (make-layered-datum base-value alist)
(if (null? alist)
base-value
(let ((alist
(cons (cons base-layer base-value)
alist)))
(define (has-layer? layer)
(and (assv layer alist) #t))
(define (get-layer-value layer)
(cdr (assv layer alist)))
(define (annotation-layers)
(map car (cdr alist)))
(bundle layered-datum?
has-layer? get-layer-value
annotation-layers))))
```
The associations between layers and their values are represented as an *association list*, or *alist*—a list of keyvalue pairs.
For convenience, we provide layered-datum, which takes its layer arguments in *property-list* form (alternating layer and value, as in the examples on page 300) and calls make-layered-datum with the corresponding alist.
```
(define (layered-datum base-value . plist)
(make-layered-datum base-value (plist->alist plist)))
```
This design provides great flexibility. There may be many different kinds of layered data, and for each there is no a priori commitment to any particular layer or number of layers. The only common feature is that each layered datum has a distinguished layer, the base-layer, which contains the object that all the other layer values are annotations on.
Each layer is represented by a bundle that embodies the specifics of that layer. The simplest is the base layer:
```
(define base-layer
(let ()
(define (get-name) 'base)
(define (has-value? object) #t)
(define (get-value object)
(if (layered-datum? object)
(object 'get-layer-value base-layer)
object))
(bundle layer? get-name has-value? get-value)))
```
This shows the primary operation of a layer: the get-value operation that fetches the layer value, if present, or returns a default. In the case of the base layer, the default is the object itself.
The *annotation layers* have a little more complexity. In addition to the above, they also manage a set of named procedures that will be explored when we look at layered procedures. The makeannotation-layer procedure provides the common infrastructure used by all annotation layers; it calls its constructor argument to supply the layer-specific parts.
```
(define (make-annotation-layer name constructor)
(define (get-name) name)
(define (has-value? object)
(and (layered-datum? object)
```
```
(object 'has-layer? layer)))
(define (get-value object)
(if (has-value? object)
(object 'get-layer-value layer)
(layer 'get-default-value)))
(define layer
(constructor get-name has-value? get-value))
layer)
```
We use make-annotation-layer to construct the units layer:
```
(define unit-layer
(make-annotation-layer 'unit
(lambda (get-name has-value? get-value)
(define (get-default-value)
unit:none)
(define (get-procedure name arity)
See definition on page 308.)
(bundle layer?
get-name has-value? get-value
get-default-value get-procedure))))
```
This implementation shows the rest of the layer structure: a provider for the default value, and the procedure get-procedure that implements this layer's support for layered procedures, which we will examine in the next section (page 308).
As a convenience for a common use case, layer-accessor creates an accessor procedure that is equivalent to calling a layer's get-value delegate:
```
(define (layer-accessor layer)
(lambda (object)
(layer 'get-value object)))
(define base-layer-value
(layer-accessor base-layer))
```
#### **6.2.2 Layered procedures**
Procedures are also data that can be layered. A layered procedure is similar to a generic procedure, in which there are handlers for different argument types. A layered procedure instead provides implementations for separate layers in the incoming data, and
<span id="page-7-0"></span>processes all of them to produce a layered result. [1](#page-30-0) For example, when combining a numeric layer with a units layer, the procedure can process the numeric parts of the arguments using its numeric layer, and also process the units parts of the arguments using its units layer.
In the numerical example shown in section 6.1, the code F for Newton's force represents the *parti*, the essential plan for the computation to be performed. It operates on numbers; the units annotate the numbers. The layered generic procedures that implement the arithmetic operators, such as multiplication, have a base component that operates on the numbers in the base layer and they have other components, one for each layer that might annotate the numerical base layer. The units layer is an annotation layer that gives more information about the data and the computation, but is not essential to the computation.
In a layered system the base layer must be able to compute without reference to the other layers. But the annotation layers may need access to the values that are in the base layer. If an annotation layer of an argument is missing, the procedure's annotation layer may use a default value or simply not run. In any case, the base layer always runs.
To construct a layered procedure, we need a unique name and arity for the procedure, and a base-procedure to implement the base computation:
```
(define (make-layered-procedure name arity base-procedure)
(let* ((metadata
(make-layered-metadata name arity base-procedure))
(procedure
(layered-procedure-dispatcher metadata)))
(set-layered-procedure-metadata! procedure metadata)
procedure))
```
Information about the layered procedure is kept in metadata for that procedure. The metadata also manages the handlers for the base layer and the annotation layers.
The metadata for a layered procedure is implemented as a bundle. It is created with the name of the layered procedure, its
arity, and the base-procedure (the handler for the base layer). The metadata provides access to each of these. It also provides sethandler! for assigning a handler for an annotation layer and gethandler for retrieving the handler for an annotation layer.
Each annotation layer, for example the unit-layer, provides get-procedure that when given a procedure name and arity returns the appropriate handler for that procedure name and arity for that layer. The get-handler provided by the layered metadata first checks if it has a handler for that layer. If so it returns that handler; otherwise it returns the result of the layer's getprocedure.
```
(define (make-layered-metadata name arity base-procedure)
(let ((handlers (make-weak-alist-store eqv?)))
(define (get-name) name) (define (get-arity) arity)
(define (get-base-procedure) base-procedure)
(define has? (handlers 'has?))
(define get (handlers 'get))
(define set-handler! (handlers 'put!))
(define (get-handler layer)
(if (has? layer)
(get layer)
(layer 'get-procedure name arity)))
(bundle layered-metadata?
get-name get-arity get-base-procedure
get-handler set-handler!)))
```
The actual work of applying a layered procedure is done by layered-procedure-dispatcher. The dispatcher must be able to access and apply the base procedure and the annotation layer procedures that are associated with the layered procedure. All of this information is provided by the metadata.
```
(define (layered-procedure-dispatcher metadata)
(let ((base-procedure (metadata 'get-base-procedure)))
(define (the-layered-procedure . args)
(let ((base-value
(apply base-procedure
(map base-layer-value args)))
(annotation-layers
(apply lset-union eqv?
(map (lambda (arg)
(if (layered-datum? arg)
```
```
(arg 'annotation-layers)
'()))
args))))
(make-layered-datum base-value
(filter-map ; drops #f values
(lambda (layer)
(let ((handler (metadata 'get-handler layer)))
(and handler
(cons layer
(apply handler base-value args)))))
annotation-layers))))
the-layered-procedure))
```
When called, a layered procedure first calls base-procedure on the base-layer values of the arguments to get the base value. It also determines which annotation layers are applicable by examining each of the arguments; if there are no annotation layers that have handlers, then the result is just the base-layer value, because makelayered-datum (on page 303), will return the unannotated base value. Otherwise, each applicable layer's handler is called to produce a value for that layer. The layer-specific handler is given access to the computed base-value and the arguments to the layered procedure; it does not need any layer values other than its own and those of the base layer. Generally, the result is a layered datum containing the base value and the values of the applicable annotation layer handlers.
To see how this works in practice, let's look at the implementation for the units layer (on page 304). The getprocedure handler of the units layer (below) looks up the layerspecific procedure by name if the layered procedure's name is an arithmetic operator, and then calls the layer-specific procedure with the units from each argument. (There is a special exception for expt, whose second argument is not decorated with units—it is a number.) For other procedures, the units handling is undefined, so get-procedure returns #f to indicate that.
```
(define (get-procedure name arity)
(if (operator? name)
(let ((procedure (unit-procedure name)))
(case name
```
```
((expt)
(lambda (base-value base power)
(procedure (get-value base)
(base-layer-value power))))
(else
(lambda (base-value . args)
(apply procedure (map get-value args))))))
#f))
```
Notice that because get-procedure is an internal procedure of unit-layer, it has access to the units layer get-value inherited from make-annotation-layer (on page 304). We will see unitprocedure when we talk about the units implementation in section 6.3.1.
Let's look at an example. Consider the simple procedure square that squares its argument.
```
(define (square x) (* x x))
```
We make a layered version of our square procedure, giving the numerical version to the base layer.
```
(define layered-square
(make-layered-procedure 'square 1 square))
```
This layered squaring procedure behaves the same as the base version:
```
(layered-square 4)
16
(layered-square 'm)
(* m m)
```
However, if we provide an argument with a units layer, both the base layer and units layer will be processed separately and combined in the output:
```
(pp (layered-square
(layered-datum 'm
unit-layer (unit 'kilogram 1))))
#[layered-datum (* m m)]
```
```
(base-layer (* m m))
(unit-layer (unit kilogram 2))
```
### **6.3 Layered arithmetic**
Now that we know how to make layered procedures, we can add layers to an arithmetic. All that is required is to build an arithmetic with a layered procedure for each operation supplied in the base arithmetic. We start with a pleasant arithmetic
```
(define (generic-symbolic)
(let ((g (make-generic-arithmetic
make-simple-dispatch-store)))
(add-to-generic-arithmetic! g numeric-arithmetic)
(extend-generic-arithmetic! g function-extender)
(extend-generic-arithmetic! g symbolic-extender)
g))
```
and build an extender to handle the layers on that substrate:
```
(define generic-with-layers
(let ((g (generic-symbolic)))
(extend-generic-arithmetic! g layered-extender)
g))
```
The layered extender has to do a bit of work. It makes a layered extension arithmetic that operates on layered data. The domain predicate of the layered extension arithmetic is layered-datum?. The base predicate for the layered operations is just the domain predicate of the underlying arithmetic, with the extra provision that it must reject layered data items. [2](#page-30-1) The constants are the base constants, and for each arithmetic operator the operation is a layered procedure applicable if any argument is layered, with the base procedure inherited from the underlying arithmetic.
```
(define (layered-extender base-arith)
(let ((base-pred
(conjoin (arithmetic-domain-predicate base-arith)
(complement layered-datum?))))
(make-arithmetic (list 'layered
```
```
(arithmetic-name base-arith))
layered-datum?
(list base-arith)
(lambda (name base-value)
base-value)
(lambda (operator base-operation)
(make-operation operator
(any-arg (operator-arity operator)
layered-datum?
base-pred)
(make-layered-procedure operator
(operator-arity operator)
(operation-procedure base-operation)))))))
```
Nearly all of this is boilerplate, including leaving the constant objects alone and requiring that at least one argument to an operation be layered. The only interesting part is the final three lines, in which the base arithmetic's operation procedure is wrapped in a layered procedure. The operator is used as the name of the layered procedure, so that each layer can provide special handling should that operation need it.
#### **6.3.1 Unit arithmetic**
<span id="page-12-0"></span>We need an arithmetic of units for the units annotation layer on an arithmetic. A unit specification has named base units, and an exponent for each base unit. [3](#page-30-2) In the units arithmetic, the product of unit specifications is a new unit specification where the exponent of each base unit is the sum of the exponents of the corresponding base units in the arguments.
```
(unit:* (unit 'kilogram 1 'meter 1 'second -1)
(unit 'second -1))
(unit kilogram 1 meter 1 second -2)
```
Here we assume that the base units are just named by symbols, such as kilogram.
#### **Representation of unit specifications**
To make it easy to create a unit specification, we represent it externally as a property list (with alternating keys and values) of base unit names and exponents.
But internally, it is convenient to represent a unit specification as a tagged alist; so we must convert a raw property list to the alist representation, using plist->alist. We keep the alists sorted by the base unit name. In this conversion we do some error checking. The argument list to unit must be in the form of a property list. The exponent associated with each base unit name must be an exact rational number (usually an integer). It is an error if a named base unit is duplicated. The sort by base unit names will signal an error if the base unit name is not a symbol.
```
(define (unit . plist)
(guarantee plist? plist 'unit)
(let ((alist
(sort (plist->alist plist)
(lambda (p1 p2)
(symbol<? (car p1) (car p2))))))
(if (sorted-alist-repeated-key? alist)
(error "Base unit repeated" plist))
(for-each (lambda (p)
(guarantee exact-rational? (cdr p)))
alist)
(alist->unit alist)))
(define (sorted-alist-repeated-key? alist)
(and (pair? alist)
(pair? (cdr alist))
(or (eq? (caar alist) (caadr alist))
(sorted-alist-repeated-key? (cdr alist)))))
```
The procedure alist->unit just attaches a unique tag to an alist; and unit->alist extracts the alist from a unit specification:
```
(define (alist->unit alist)
(cons %unit-tag alist))
(define (unit->alist unit)
(guarantee unit? unit 'unit->alist)
(cdr unit))
```
Here, the value of %unit-tag is just a unique symbol that we use to head a unit specification alist. To make the printed output of unit specifications look like the property lists that we give to unit to make a unit specification, we arrange that the Scheme printer prints unit specifications in property list form. This magic arrangement (not shown here) is triggered by the unit-tag symbol at the head of the list.
The predicate unit? is true if its argument is a legitimate unit specification:
```
(define (unit? object)
(and (pair? object)
(eq? (car object) %unit-tag)
(list? (cdr object))
(every (lambda (elt)
(and (pair? elt)
(symbol? (car elt))
(exact-rational? (cdr elt))))
(cdr object))))
```
### **Unit arithmetic operations**
We construct the unit arithmetic as a mapping between the operator name and the operation that implements the required behavior. Pure numbers, like *π*, are unitless. When a quantity with units is multiplied by a unitless number, the result is the units of the quantity with units. So the unit arithmetic needs a multiplicative identity for unitless numbers—this is unit:none. The procedure simple-operation combines the operator, the test for applicability, and the procedure that implements the operation:
```
(define (unit-arithmetic)
(make-arithmetic 'unit unit? '()
(lambda (name)
(if (eq? name 'multiplicative-identity)
unit:none
(default-object)))
(lambda (operator)
(simple-operation operator
unit?
(unit-procedure operator)))))
```
We call unit-procedure to get the appropriate procedure for each operator:
```
(define (unit-procedure operator)
(case operator
((*) unit:*)
((/) unit:/)
((remainder) unit:remainder)
((expt) unit:expt)
((invert) unit:invert)
((square) unit:square)
((sqrt) unit:sqrt)
((atan) unit:atan)
((abs ceiling floor negate round truncate)
unit:simple-unary-operation)
((+ - max min)
unit:simple-binary-operation)
((acos asin cos exp log sin tan)
unit:unitless-operation)
((angle imag-part magnitude make-polar make-rectangular
real-part)
;; first approximation:
unit:unitless-operation)
(else
(if (eq? 'boolean (operator-codomain operator))
(if (n:= 1 (operator-arity operator))
unit:unary-comparison
unit:binary-comparison)
unit:unitless-operation))))
```
For each case above we must provide the appropriate operation. For example, to multiply two unit quantities we must add corresponding exponents and elide any base unit that has zero exponent:
```
(define (unit:* u1 u2)
(alist->unit
(let loop ((u1 (unit->alist u1)) (u2 (unit->alist u2)))
(if (and (pair? u1) (pair? u2))
(let ((factor1 (car u1)) (factor2 (car u2)))
(if (eq? (car factor1) (car factor2)) ; same unit
(let ((n (n:+ (cdr factor1) (cdr factor2))))
(if (n:= 0 n)
(loop (cdr u1) (cdr u2))
(cons (cons (car factor1) n)
(loop (cdr u1) (cdr u2)))))
```
```
(if (symbol<? (car factor1) (car factor2))
(cons factor1 (loop (cdr u1) u2))
(cons factor2 (loop u1 (cdr u2))))))
(if (pair? u1) u1 u2)))))
```
Some operators, such as remainder, expt, invert, square, sqrt, and atan, require special treatment. The rest of the operators fit into a few simple classes. Simple unary operations, like negate, just propagate the units of their argument to their result:
```
(define (unit:simple-unary-operation u)
u)
```
But some, like the implementation of addition, check that they are not "combining apples and oranges:"
```
(define (unit:simple-binary-operation u1 u2)
(if (not (unit=? u1 u2))
(error "incompatible units:" u1 u2))
u1)
```
# **Exercise 6.1: Derived units**
Although the unit computation given above is correct and reasonably complete, it is not very nice to use. For example, the unit specification for kinetic energy (as shown on page 316) is:
```
(unit kilogram 1 meter 2 second -2)
```
This is correct in terms of the International System of Units (SI) base units {kilogram, meter, second}, but it would be much nicer if expressed in terms of joules, the SI derived unit of energy:
```
(unit joule 1)
```
The full system of SI base units is {kilogram, meter, second, ampere, kelvin, mole, candela}, and there is an approved set of derived units. For example:
newton = kilogram*·*meter*·*second **2
- joule = newton*·*meter
- coulomb = ampere*·*second
- watt = joule*·*second **1
- volt = watt*·*ampere **1
- ohm = volt*·*ampere **1
- siemens = ohm**<sup>1</sup>
- farad = coulomb*·*volt **1
- weber = volt*·*second
- henry = weber*·*ampere **1
- hertz = second **1
- tesla = weber*·*meter **2
- pascal = newton*·*meter **2
- **a.** Make a procedure that takes a unit description in terms of SI base units and, if possible, makes a simpler description using derived units.
- **b.** The expression of a unit description in terms of the derived units is not unique—there may be many such equivalent descriptions. This is similar to a problem of algebraic simplification, but the criterion of "simpler" is not obvious. Make a nice version that you like and explain why you like it.
- **c.** It is nice to be able to use the standard abbreviations and multipliers for the units. For example, 1 mA is the nice way to write 0*.*001 A or 1*/*1000 ampere. Design and implement a simple extensible system that allows the use of these notational conveniences for both input and output. But remember that "syntactic sugar causes cancer of the semicolon."
### **6.4 Annotating values with dependencies**
One kind of annotation that a programmer may want to deploy in some parts of a program is the tracking of dependencies. Every piece of data (or procedure) came from somewhere. Either it entered the computation as a premise that can be labeled with its external provenance, or it was created by combining other data. We can provide primitive operations of the system with annotation layers that, when processing data with justifications, can annotate the results with appropriate justifications.
Justifications can be at differing levels of detail. The simplest kind of justification is just a set of those premises that contributed to the new data. A procedure such as addition can form a sum with a justification that is just the union of the premises of the justifications of the addends that were supplied. Multiplication is similar, but a zero multiplicand is sufficient to force the product to be zero, so the justifications of the other factors do not need to be included in the justification of the zero product.
Such simple justifications can be computed and carried without much more than a constant overhead, but they can be invaluable in debugging complex processes and in the attribution of credit or blame for outcomes of computations. Just this much is sufficient to support dependency-directed backtracking. (See section 7.5.)
Externally supplied data can be annotated with a *premise* that identifies its origin. More generally, any data value can be annotated with a set of premises, which is called its *support set*. The support set annotating a datum is usually referred to as its *support*. When a support-aware procedure is applied to multiple arguments, it must combine the support sets of the arguments to represent the support of the result.
Managing support sets is a straightforward application of our layered data mechanism. We add a support layer to our generic arithmetic to handle support sets. It coexists with other layers, such as the units layer. So this is an additive feature.
On page 309 we built an arithmetic that supports layered data and procedures:
```
(define generic-with-layers
(let ((g (generic-symbolic)))
(extend-generic-arithmetic! g layered-extender)
g))
(install-arithmetic! generic-with-layers)
```
We don't need to specify what layers are to be supported by layered-extender, since it automatically uses the layers in each layered procedure's arguments. So if, say, + is called with arguments that have units, then the result will also have units. But if none of the arguments have units, then neither does the result, and the unit addition procedure is not invoked. Similarly, if the arguments have support, then the result will have support. But if the arguments do not have support, the result will not have support, and the support addition procedure is not invoked.
For example, we can define the kinetic energy of a particle with mass m and velocity v:
```
(define (KE m v)
(* 1/2 m (square v)))
```
Now we can see the result of evaluating the kinetic energy on some arguments:
```
(pp (KE (layered-datum 'm
unit-layer (unit 'kilogram 1)
support-layer (support-set 'cph))
(layered-datum 'v
unit-layer (unit 'meter 1 'second -1)
support-layer (support-set 'gjs))))
#[layered-datum (* (* 1/2 m) (square v))]
(base-layer (* (* 1/2 m) (square v)))
(unit-layer (unit kilogram 1 meter 2 second -2))
(support-layer (support-set gjs cph))
```
We supply each argument with annotations for the units layer and the support layer. For the support layer we give a set of premises (the support set). Here, each argument is supported by a single
premise, cph and gjs respectively. The value is a layered object with three layers: the base generic arithmetic layer value is the appropriate algebraic expression; the units are correct; and the support set is the set of named premises that contributed to the value.
Here we accepted the definition of KE without supplying explicit support for that procedure. More generally, we might want to add such support. For example, we may want to say that KE is supported by a premise KineticEnergy-classical. Then if we find a result of some complex computation that seems wrong, we can find out which procedures contributed to the wrong answer, as well as the numerical or symbolic input values that were used. We will attack this problem in exercise 6.2.
Not all premises that appear in the arguments to a computation need to appear in a result. For example, if a factor contributing to a product is zero, that is sufficient reason for the product to be zero, independent of any other finite factors. This is illustrated by supplying a zero mass:
```
(pp (KE (layered-datum 0
unit-layer (unit 'kilogram 1) support-
layer
(support-set 'jems))
(layered-datum 'v
unit-layer (unit 'meter 1 'second -1)
support-layer (support-set 'gjs))))
#[layered-datum 0]
(base-layer 0)
(unit-layer (unit kilogram 1 meter 2 second -2))
(support-layer (support-set jems))
```
Here the support for the numeric value of the result being zero is just the support supplied for the zero value for the mass.
#### **6.4.1 The support layer**
Now we will see how the support layer is implemented. It is somewhat different from the units layer, because units can be
combined without any reference to the base layer, whereas the support layer needs to look at the base layer for some operations.
The support layer is somewhat simpler than the units layer, because all but three of the arithmetic operators use the default: the support set of the result is the union of the support sets of the arguments.
```
(define support-layer
(make-annotation-layer 'support
(lambda (get-name has-value? get-value)
(define (get-default-value)
(support-set))
(define (get-procedure name arity)
(case name
((*) support:*)
((/) support:/)
((atan2) support:atan2)
(else support:default-procedure)))
(bundle layer?
get-name has-value? get-value
get-default-value get-procedure))))
(define support-layer-value
(layer-accessor support-layer))
(define (support:default-procedure base-value . args)
(apply support-set-union (map support-layer-value args)))
```
Multiplication is the first interesting case. The support layer needs to look at the values of the base arithmetic arguments to determine the computation of support. If either argument is zero, then the support for the result is only the support for the zero argument.
```
(define (support:* base-value arg1 arg2)
(let ((v1 (base-layer-value arg1))
(v2 (base-layer-value arg2))
(s1 (support-layer-value arg1))
(s2 (support-layer-value arg2)))
(if (exact-zero? v1)
(if (exact-zero? v2)
(if (< (length (support-set-elements s1))
(length (support-set-elements s2)))
s1
```
```
s2) ;arbitrary
s1)
(if (exact-zero? v2)
s2
(support-set-union s1 s2)))))
```
Division (and arctangent, not shown) also has to examine the base layer to deal with zero arguments. If the dividend is zero, that is sufficient to support the result that the quotient is zero. The divisor won't ever be zero because the base-layer computation will have signaled an error and this code won't be run.
```
(define (support:/ base-value arg1 arg2)
(let ((v1 (base-layer-value arg1))
(s1 (support-layer-value arg1))
(s2 (support-layer-value arg2)))
(if (exact-zero? v1)
s1
(support-set-union s1 s2))))
```
These optimizations for \* and / make sense only when we can prove that an argument is really zero, not an unsimplified symbolic expression. (But if an expression simplifies to exact zero we can use that fact!)
```
(define (exact-zero? x)
(and (n:number? x) (exact? x) (n:zero? x)))
```
The support-set abstraction is implemented as a list starting with the symbol support-set
```
(define (%make-support-set elements)
(cons 'support-set elements))
(define (support-set? object)
(and (pair? object)
(eq? 'support-set (car object))
(list? (cdr object))))
(define (support-set-elements support-set)
(cdr support-set))
```
along with a few extra utilities to complete the abstraction.
```
(define (make-support-set elements)
(if (null? elements)
%empty-support-set
(%make-support-set (delete-duplicates elements))))
(define (support-set . elements)
(if (null? elements)
%empty-support-set
(%make-support-set (delete-duplicates elements))))
(define %empty-support-set
(%make-support-set '()))
(define (support-set-empty? s)
(null? (support-set-elements s)))
```
We need to be able to compute the union of support sets and adjoin new elements to them. Since we chose to keep our elements in a list, we can use the lset library from Scheme. [4](#page-30-3)
```
(define (support-set-union . sets)
(make-support-set
(apply lset-union eqv?
(map support-set-elements sets))))
(define (support-set-adjoin set . elts)
(make-support-set
(apply lset-adjoin eqv? (support-set-elements set) elts)))
```
### **Exercise 6.2: Procedural responsibility**
The support layer based on arithmetic is extremely low level. Every primitive arithmetic operation is support-aware, and there is no way to bypass that work for common conditions. There needs to be a means of abstraction. For example, suppose we have a procedure that computes the numerical definite integral of a function. The units of the numerical value of the integral is the product of the units of the numerical value of the integrand and the units of the numerical value of the limits of integration. (The units of the upper and lower limit must be the same!) However, it is not a good idea to carry the units computation through all of the detailed arithmetic
going on in the integration process. It should be possible to annotate the integrator so that the result has the correct units without requiring every internal addition and multiplication to be a layered procedure operating on layered data.
- **a.** Make it possible to allow compound procedures that may be built out of the primitive arithmetic procedures (or possibly not) to modify the support of their results by adding a premise (such as "made by George").
- **b.** Allow compound procedures to be executed in a way that hides their bodies from the support layer. Thus, for example, a trusted library procedure may annotate its result with appropriate support, but the operations in its body will not incur the overhead of computing the support of intermediate results.
- **c.** The support layer is organized around the operators of an arithmetic system. But sometimes it is useful to distinguish the specific occurrences of an operator. For example, when dealing with numerical precision it is not very helpful to say that a loss of significance is due to subtraction of almost equal quantities. It would be more helpful to show the particular instance of subtraction that is the culprit. Is there some way to add the ability to identify instances of an operator to the support layer?
# **Exercise 6.3: Paranoid programming**
Sometimes we are not confident that a library procedure does what we expect. In that case it is prudent to "wrap" the library procedure with a test that checks its result. For example, we may be using a program solve that takes as inputs a set of equations and a set of unknowns, that may occur in the equations, producing a set of substitutions for the unknowns that satisfy the equations. We might want to wrap the solve program with a wrapper that checks that the result of substituting the outputs into the input equations indeed makes them tautologies. But we don't want such a paranoia
wrapper to appear as part of our *parti*. How can this sort of thing be implemented as a layer? Explain your design and implement it.
### **Exercise 6.4: IDE for layered programs**
This exercise is a major design project: the invention of and development of an IDE (Integrated Development Environment) for layered systems.
The idea of layered programs, using layered data and layered procedures, is a very nice idea. The goal is to be able to annotate programs with useful and executable metadata—such as type declarations, assertions, units, and support—without cluttering the text of the base program. However, the text of the program must be linked with the text of the annotations, so that as any part of the program is edited, the related layers are also edited. For example, suppose it is necessary to edit the base procedure of some layered procedure. The layers may be information like type declarations or how it handles units and support sets. It would be nice for the editor to show us these layers and how they are connected to the text of the base program, when necessary. Perhaps edits to the text of the base program entail edits to the annotation layers. Sometimes this can be done automatically, but often the programmer must edit the layers.
- **a.** Imagine what you would like to see in an IDE to support the development of layered systems. What would you like to see on a screen? How would you keep the parts that are edited synchronized?
- **b.** Emacs is a powerful infrastructure for building such an IDE. It supports multiple windows and per-window editing modes. It has syntactic support for many computer languages, including Scheme. There are Emacs subsystems, like org-mode, that have the flavor of a layered structure for documents. Can this be extended to help with layered programming? Sketch out a way to build your IDE using Emacs.
- **c.** Build a small but extensible prototype on the Emacs base, and try it out. What problems do you encounter? Did Emacs really provide a good place to start? If not, why not? Report on your experiment.
- **d.** If your prototype was promising, develop a solid system and make it into a loadable Emacs library, so we can all use your great system.
#### **6.4.2 Carrying justifications**
More complex justifications may also record the particular operations that were used to make the data. This kind of annotation can be used to provide explanations (proofs), but it is intrinsically expensive in space—potentially linear in the number of operations performed. However, sometimes it is appropriate to attach a detailed audit history describing the derivation of a data item, to allow some later process to use the derivation for some purpose or to evaluate the validity of the derivation for debugging. [5](#page-30-4)
<span id="page-26-0"></span>For many purposes, such as legal arguments, it is necessary to know the provenance of data: where it was collected, how it was collected, who collected it, how the collection was authorized, etc. The detailed derivation of a piece of evidence, giving the provenance of each contribution, may be essential to determining if it is admissable in a trial.
<span id="page-26-1"></span>The symbolic arithmetic that we built in section 3.1 is one way this can be done. In fact, if symbolic arithmetic is used as a layer on numeric arithmetic, then every numerical value is annotated with its derivation. The symbolic arithmetic annotation could be very expensive, because the symbolic expression for an application of a numerical operator includes the symbolic expressions of its inputs. However, because we need only include a pointer to each input, the space and time cost of annotating each operation is often acceptable. [6](#page-30-5) So one may overlay this kind of justification when it is necessary to provide an explanation, or even temporarily, to track a difficult-to-catch bug.
### **Exercise 6.5: Justifications**
Sketch out the issues involved in carrying justifications for data. Notice that the reason for a value depends on the values that it was derived from and the way those values were combined. What do we do if the reason for a value is some numerically weighted combination of many factors, as in a deep neural network? This is a research question that we need to address to make the systems that affect us accountable.
# **6.5 The promise of layering**
We have only scratched the surface of what can be done with an easy and convenient mechanism for layering of data and programs. It is an open area of research. The development of systems to support such layering can have huge consequence for the future.
Sensitivity analysis is an important feature that can be built using annotated data and layered procedures. For example, in mechanics, if we have a system that evolves the solution of a system of differential equations from some initial conditions, it is often valuable to understand the way a tube of trajectories that surround a reference trajectory deforms. This is usually accomplished by integrating a variational system along with the reference trajectory. Similarly, it may be possible to carry a probability distribution of values around a nominal value along with the nominal value computed in some analyses. This may be accomplished by annotating the values with distributions and providing the operations with overlaying procedures to combine the distributions, guided by the nominals, perhaps implementing Bayesian analysis. Of course, to do this well is not easy.
An even more exciting but related idea is that of perturbational programming. By analogy with the differential equations example, can we program symbolic systems to carry a "tube" of variations
around a reference trajectory, thus allowing us to consider small variations of a query? Consider, for example, the problem of doing a search. Given a set of keywords, the system does some magic that comes up with a list of documents that match the keywords. Suppose we incrementally change a single keyword. How sensitive is the search to that keyword? More important, is it possible to reuse some of the work that was done getting the previous result in the incrementally different search? We don't know the answers to these questions, but if it is possible, we want to be able to capture the methods by a kind of perturbational program, built as an overlay on the base program.
#### **Dependencies mitigate inconsistency**
Dependency annotations on data give us a powerful tool for organizing human-like computations. For example, all humans harbor mutually inconsistent beliefs: an intelligent person may be committed to the scientific method yet have a strong attachment to some superstitious or ritual practices; a person may have a strong belief in the sanctity of all human life, yet also believe that capital punishment is sometimes justified. If we were really logicians this kind of inconsistency would be fatal: if we really were to simultaneously believe both propositions P and NOT P, then we would have to believe all propositions! But somehow we manage to keep inconsistent beliefs from inhibiting all useful thought. Our personal belief systems appear to be locally consistent, in that there are no contradictions apparent. If we observe inconsistencies we do not crash; we may feel conflicted or we may chuckle.
We can attach to each proposition a set of supporting assumptions, allowing deductions to be conditional on the assumption set. Then, if a contradiction occurs, a process can determine the particular "nogood set" of inconsistent assumptions. The system can then "chuckle," realizing that no deductions based on any superset of those assumptions can be believed. This chuckling process, dependency-directed backtracking, can be used to optimize a complex search process, allowing a search to make the
best use of its mistakes. But enabling a process to simultaneously hold beliefs based on mutually inconsistent sets of assumptions without logical disaster is revolutionary.
#### **Restrictions on the use of data**
Data is often encumbered by restrictions on the ways it may be used. These encumberances may be determined by statute, by contract, by custom, or by common decency. Some of these restrictions are intended to control the diffusion of the data, while others are intended to delimit the consequences of actions predicated on that data.
The allowable uses of data may be further restricted by the sender: "I am telling you this information in confidence. You may not use it to compete with me, and you may not give it to any of my competitors." Data may also be restricted by the receiver: "I don't want to know anything about this that I may not tell my spouse."
Although the details may be quite involved, as data is passed from one individual or organization to another, the restrictions on the uses to which it may be put are changed in ways that can often be formulated as algebraic expressions. These expressions describe how the restrictions on the use of a particular data item may be computed from the history of its transmission: the encumberances that are added or deleted at each step. When parts of one data set are combined with parts of another data set, the restrictions on the ways that the extracts may be used and the restrictions on the ways that they may be combined must determine the restrictions on the combination. A formalization of this process is a *data-purpose algebra* [53] description.
Data-purpose algebra layers can be helpful in building systems that track the distribution and use of sensitive data to enable auditing and to inhibit the misuse of that data. But this kind of application is much larger than just a simple matter of layering. To make it effective requires ways of ensuring the security of the process, to prevent leakage through uncontrolled channels or
compromise of the tracking layers. There is a great deal of research to be done here.
- <span id="page-30-0"></span>[1](#page-7-0) Note that a layer's implementation for a layered procedure may itself be a generic procedure. Likewise, a handler for a generic procedure may be a layered procedure.
- <span id="page-30-1"></span>[2](#page-11-0) The procedures conjoin and complement are combinators for predicates: conjoin makes a new predicate that is the boolean and of its arguments, and complement makes a new predicate that is the negation of its argument.
- <span id="page-30-2"></span>[3](#page-12-0) Watch out! The "base units" are not to be confused with the base-layer in our layered-data system. A system of units is built on a set of base units, such as kilograms, meters, and seconds. There are derived units, such as the newton, which is a combination of the base units: 1 N = 1 kg *·* m *·* s 2
- <span id="page-30-3"></span>[4](#page-23-0) If the support sets get large we can try to represent them much more efficiently, but here we are dealing with only small sets.
- <span id="page-30-4"></span>[5](#page-26-0) In Patrick Suppes's beautiful *Introduction to Logic* [118] the proofs are written in four columns. The columns are an identifier for the line, the statement for that line, the rule that was used for deriving that line from previous lines, and the set of premises that support the line. This proof structure is actually the inspiration for the way we carry justifications and support sets.
- <span id="page-30-5"></span>[6](#page-26-1) This is not really true. The problem is that the composition of numerical operations may incur no significant memory access cost, but the construction of a symbolic expression, however small, requires access to memory. And memory access time is huge compared with the time to do arithmetic in CPU registers. Sigh...