9 Combiners

There are two types of combiners in Kernel, operative and applicative. Both types are encapsulated. All combiners are immutable. Two applicatives are eq? iff their underlying combiners are eq?. However, eq?-ness of operatives is only constrained by the general rules for eq?, which leave considerable leeway for variation between implementations. klisp only considers eq? those operatives constructed by the same call to a constructor (e.g. $vau). Two combiners are equal? iff they are eq?.

— Applicative: operative? (operative? . objects)

The primitive type predicate for type operative. operative? returns true iff all the objects in objects are of type operative.

— Applicative: applicative? (applicative? . objects)

The primitive type predicate for type applicative. applicative? returns true iff all the objects in objects are of type applicative.

— Operative: $vau ($vau <formals> <eformal> . <objects>)

<formals> should be a formal parameter tree; <eformal> should be either a symbol or #ignore. If <formals> does not have the correct form for a formal parameter tree, or if <eformal> is a symbol that also occurs in <formals>, an error is signaled.

vau expression evaluates to an operative; an operative created in this way is said to be compound. The environment in which the vau expression was evaluated is remembered as part of the compound operative, called the compound operative’s static environment. <formals> and <objects> are copied as by copy-es-immutable and the copies are stored as part of the operative being constructed. This avoids problem if these structures are later mutated.

When the compound operative created by $vau is later called with an object and an environment, here called respectively the operand tree and the dynamic environment, the following happens:

  1. A new, initially empty environment is created, with the static environment as its parent. This will be called the local environment.
  2. A stored copy of the formal parameter tree formals is matched in the local environment to the operand tree, locally binding the symbols of formals to the corresponding parts of the operand tree. eformal is matched to the dynamic environment; that is, if eformal is a symbol then that symbol is bound in the local environment to the dynamic environment.
  3. A stored copy of the expressions is evaluated sequentially from left to right, with the last (if any) evaluated as a tail context, or if the list of expressions is empty, the result is inert.

NOTE: Because compound operatives are not a distinct type in Kernel, they are covered by the encapsulation of type operative. In particular, an implementation of Kernel cannot provide a feature that supports extracting the static environment of any given compound operative, nor that supports determining whether or not a given operative is compound.

— Applicative: wrap (wrap combiner)

The wrap applicative returns an applicative whose underlying combiner is combiner.

— Applicative: unwrap (unwrap applicative)

The unwrap applicative returns the underlying combiner of applicative.

— Operative: $lambda ($lambda <formals> . <objects>)

<formals> should be a formal parameter tree.

The $lambda operative is defined by the following equivalence:

          ($lambda formals . objects) ==
            (wrap ($vau formals #ignore . objects))
— Applicative: apply (apply applicative object [environment])

Applicative apply combines the underlying combiner of applicative with object in a tail context with dynamic environment environment (if the long form is used) or in an empty environment (if the short form is used).

The following equivalences hold:

          (apply applicative object environment) ==
            (eval (cons (unwrap applicative) object) environment)
          
          (apply applicative object) ==
            (apply applicative object (make-environment))
— Applicative: map (map applicative . lists)

lists must be a nonempty list of lists; if there are two or more, they must all have the same length. If lists is empty, or if all of its elements are not lists of the same length, an error is signaled.

The map applicative applies applicative element-wise to the elements of the lists in lists (i.e., applies it to a list of the first elements of the lists, to a list of the second elements of the lists, etc.), using the dynamic environment from which map was called, and returns a list of the results, in order. The applications may be performed in any order, as long as their results occur in the resultant list in the order of their arguments in the original lists. If lists is a cyclic list, each argument list to which applicative is applied is structurally isomorphic to lists. If any of the elements of lists is a cyclic list, they all must be, or they wouldn’t all have the same length. Let a1...an be their acyclic prefix lengths, and c1...cn be their cycle lengths. The acyclic prefix length a of the resultant list will be the maximum of the ak, while the cycle length c of the resultant list will be the least common multiple of the ck. In the construction of the result, applicative is called exactly a + c times.

— Applicative: string-map (string-map applicative . strings)
— Applicative: vector-map (vector-map applicative . vectors)
— Applicative: bytevector-map (bytevector-map applicative . bytevectors)

stringsvectors, or bytevectors should be non-empty lists of the corresponding type and all elements should be of the same length.

These applicatives behave as map except that the list of elements passed to applicative are the n-th chars, objects, or uint8s of the strings, vectors or bytevectors passed as arguments.

SOURCE NOTE: These are taken from r7rs.

— Applicative: combiner? (combiner? . objects)

The primitive type predicate for type combiner. combiner? returns true iff all the objects in objects are of type combiner (i.e. applicative or operative).