Tag Archives: obscure commands

The less-familiar parts of Lisp for beginners — symbol-macrolet

Our next obscure Lisp feature is the symbol-macrolet special operator.  This operator produces a context in which one or more symbols can be rewritten prior to evaluation.  While let, and similar operators, can shadow it, outside of that exception, the symbols are substituted according to the expansions supplied.

Here is some code to demonstrate the behaviour:
symbol-macrolet.lisp

(defun demonstrate ()
  (let ((replacement-1 'my-new-fcn-1))
    (symbol-macrolet
        ((my-fcn-1 replacement-1)
         (my-fcn-2 'rpl-2))
      (format t 
              "(my-fcn-1 my-fcn-2): ~A~%"
              (list my-fcn-1 my-fcn-2))
      (format t "Now, binding to shadow my-fcn-2~%")
      (multiple-value-bind (result my-fcn-2)
          (floor 10.4 1.2)
        (format t 
                "(my-fcn-1 my-fcn-2): ~A~%"
                (list my-fcn-1 my-fcn-2))))))

with output:
*slime-repl sbcl*
CL-USER> (demonstrate)
(my-fcn-1 my-fcn-2): (MY-NEW-FCN-1 RPL-2)
Now, binding to shadow my-fcn-2
(my-fcn-1 my-fcn-2): (MY-NEW-FCN-1 0.79999924)
NIL

The less-familiar parts of Lisp for beginners — sxhash

Our next function of interest is sxhash.  This function provides an implementation-defined way to obtain a non-negative integer from a Lisp object.  The programmer can then use this in his or her own implementation of a hash table, or some similar application that requires a similar sparse mapping to positive integers.  Note that the standard-defined hash tables produced with make-hash-table cannot be made to use a custom hashing function, however check your implementation, they may have extended it to allow this.  SBCL 1.1.14, for instance, defines a &key argument to make-hash-table, hash-function, which allows a user-supplied function to replace the normal internal one.  It’s also worth noting that SBCL 1.1.14 uses sxhash internally when no hash-function is supplied by the programmer.  Finally, that version of SBCL also defines an internal implementation psxhash function, not visible to the programmer, for use when the hash-table is constructed with an equalp test instead of the default equal.

As noted above, the sxhash function is implementation defined.  The implementation is encouraged to make a good effort to return values distributed over the set of non-negative fixnums.

There are a few things to note about the implementation of sxhash, they are described in the CLHS.

  1. If two objects are equal, they must have the same hash returned by sxhash.
  2. Simple objects of type “bit vectors, characters, conses, numbers, pathnames, strings, or symbols” (to quote the CLHS), must return the same hash even from objects resident in different Lisp images.  This means, for instance, that a hash computed at compile time in the image that compiles some Lisp code must be the same as a hash recomputed on a similar object in a different image that loads the compiled code.  If this seems mysterious, you might want to review the earlier article on make-load-form.
  3. The hash code of an object must not change as long as that object is unchanged, or only changed in such a way that the object remains equal to its earlier form.
  4. As noted above, sxhash should me a good effort to produce good, spread-out values.
  5. The sxhash function should always terminate, even when presented with circular objects.

 

The less-familiar parts of Lisp for beginners — subtypep

Our next obscure function is subtypep.  This function allows the program to determine whether one type is a subtype of a second type.  The return values depend on the actual types available in the implementation, as some implementations may not support all available types, and may alias one type to another, in which case they are subtypes of one another.

The subtypep function can also determine whether a class is derived from another class.  All subclasses of a class “C”, even through multiple levels or through multiple inheritance with an unrelated class, are subtypes of class “C”.

This function is somewhat esoteric in its uses.  It can be used to ensure that a passed parameter is of the correct type for the actions that are about to be attempted on it, or in other branching contexts.  Contexts where you might use typecase could possible be amenable to the use of subtypep.

The less-familiar parts of Lisp for beginners — sublis and nsublis

Now, we look at sublis, and its relative nsublis.  You’ve probably realized by now that those functions with the ‘n’ prefix are generally destructive versions of the corresponding unprefixed function.  That is, the nsublis function is permitted to destroy its input tree.

What the sublis function does is to walk a tree and look up all subtrees and nodes as keys in the association list.  When a match is found, the value corresponding to that key is substituted.  A new tree is created that contains the substituted elements, and that may share elements with the input tree.  I will provide some examples below.

Note that the spec does not appear to provide any hard rules about the order of tree traversal, so the programmer should be careful to avoid changes that might feed back into themselves, as can happen with more complex :test functions.

Here are some examples of sublis use.  Note that if you provide your own test function it has to be able to handle receiving both sublists and nodes in its arguments.
*slime-repl sbcl*

CL-USER> (let ((swaps '((1 . "ONE")
                        (2 . "TWO")
                        (3 . "THREE")
                        (4 . "FOUR")))
               (demo-tree '((1 2)
                            (3 4 5 6)
                            (7 (8 9 10) 1))))
           (sublis swaps demo-tree))
(("ONE" "TWO") ("THREE" "FOUR" 5 6) (7 (8 9 10) "ONE"))
CL-USER> (labels
             ((same-type (x y)
                (and (numberp x)
                     (numberp y)
                     (= (mod x 2)
                        (mod y 2)))))
           (let ((types '((1 . "ODD")
                          (2 . "EVEN")))
                 (demo-tree '((1 2)
                              (3 4 5 6)
                              (7 (8 9 10) 1))))
             (pprint-linear t
                            (sublis types demo-tree 
                                    :test #'same-type))))

(("ODD" "EVEN")
 ("ODD" "EVEN" "ODD" "EVEN")
 ("ODD" ("EVEN" "ODD" "EVEN") "ODD"))
NIL