[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

copy (moderately long message)



Here's a more detailed description of one possible version of copy.  I believe this
combines most of the features people have suggested in recent mail and clarifies
most of the ambiguities people have pointed out in recent mail.



copy object &key :array :structure :hash-table :readtable :pathname :random-state
                 :gensym :fill-pointer :adjustable :level :circle

Recursively copies object as follows:

  If object is a cons copy returns a new cons (i.e. it is guaranteed that
  (and (consp object) (eq (copy object) object)) => nil) and calls itself
  recursively to fill in the car and cdr.

  If object is an array and :array is non-nil (it defaults to t) copy returns a new
  array as follows:

    The rank, dimensions, and element type are the same as object, subject to the
    restrictions regarding fill pointers below.  Even if object is displaced, the
    result is not.  The contents of the array are recursively copied.  If an element
    of the array is undefined (e. g. not initialized), it is undefined in the
    result.  In particular, if an implementation can recognize that an element
    is undefined it need not copy that element.

    Only if object has a fill pointer and :fill-pointer is non-nil will the result
    have a fill pointer.  The fill pointer is initialized to the same value as
    object's fill pointer, and all the defined elements of object, even those that
    are inactive with respect to the fill pointer, are copied.  If the object has a
    fill pointer but :fill-pointer is nil the result does not have a fill pointer,
    and the length of the resulting vector is reduced to the current value of
    object's fill pointer.  :Fill-pointer defaults to nil.

    Only if object is adjustable and :adjustable is non-nil is the result adjustable.
    :Adjustable defaults to nil.

  If object is a user defined structure and :structure is non-nil copy returns
  a new structure of the same type.  If object is named, the result acquires the
  same name (i.e. if object is a user defined, named structure then
  (eq (type-of object) (type-of (copy object)) => t).  The values of the slots
  of object are recursively copied to the result.  If the value of a slot of object
  is undefined, the value of the corresponding slot in the result is undefined.
  In particular, if an implementation can recognize that a slot's value is
  undefined it need not copy that slot.  Any slots skipped over by the
  :initial-offset option to defstruct are not recursively copied; that is, their
  values in the result are eql to their values in object.  :Structure defaults
  to t.

  If object is a hash table and :hash-table is non-nil copy returns a new hash table
  whose size is at least as large as that of object, and with the same test,
  rehash-size, and rehash-threshold.  An entry is made in the new hash table for
  each entry in object: keys are not recursively copied (i.e. the key in the new
  table is eql to that in the old) but values are recursively copied.  :Hash-table
  defaults to nil.

  If object is a readtable and :readtable is non-nil copy returns the same value as
  (copy-readtable object).  :Readtable defaults to nil.
  
  If object is a pathname and :pathname is non-nil copy returns the same value as
  (make-pathname :host      (pathname-host object)
                 :device    (pathname-device object)
                 :directory (pathname-directory object)
                 :name      (pathname-name object)
                 :type      (pathname-type object)
                 :version   (pathname-version object) )
  :Pathname defaults to t.

  If object is a random state and :random-state is non-nil copy returns the same
  value as (make-random-state object).  :Random-state defaults to t.

  If object is an uninterned symbol and :gensym is non-nil copy returns the same
  value as (copy-symbol object).  :Gensym defaults to t.

  Otherwise copy simply returns a value eql to object.

The :level argument can be used to limit the depth of recursion.  If supplied and
non-nil it should be a non-negative integer, in which case copy will only copy items
at that depth or shallower.  For example, (copy object :level 1) copies only the top
level object itself.  As a boundary case (copy object :level 0) returns object with-
out any copying.  In the case of conses the depth is considered to increase only
through the cars, and not the cdrs; that is, an entire list is considered to be at
the same level.  For example, if L is a list, then (copy L :level 1) returns the
same value as (copy-seq L).  :Level defaults to nil.

If object is circular, or contains circular substructures, copy may fail to
terminate.  If :circle is non-nil copy endeavors to detect cycles and terminate
normally, returning an isomorphic object.  :Circle defaults to nil.




NOTES:

- The basic intention is that (copy object) be similar to
  (read-from-string (write-to-string object :array t)), with objects that would be
  printed using #< notation not being copied.  Exactly what objects are printed
  using #< notation is implementation dependent, and it seems best that the value
  returned by copy be as implementation independent as possible.  Therefore some
  choices have to be made, mostly reflected in the defaults (e.g. I expect most
  implementations to print hash tables using #<, but pathnames in some readable
  format).  These choices are obviously subjective.

- The reason no mechanism for preserving displacement in copying arrays is provided
  is that displacement is typically an effort to deliberately avoid copying.

- It might seem strange to allow numeric and character keys in a #'eq hash table
  to be copied (a la eql).  However, I don't think that it can ever make sense to
  use a #'eq hash table portably with numbers and characters.

- Note that copying a readtable, pathname, or random state may be a "shallow" copy.
  However, since the structure of these items is not defined by the language it
  seems incorrect to try to define it otherwise.

- No mechanism is supplied for copying interned ids or packages as that would appear
  to raise some prohibitively awkward issues.

- No machinism is supplied for copying streams or functions, as the semantics of
  these seem to be too implementation dependent.

- I see no reason why implementations couldn't allow copying other types of objects
  by supplying more keyword arguments.  Such extensions would, of course, not be
  portable.

- Regarding :level and cdrs there seems to be a choice to be made between lists and
  trees of conses.  Lists are by far the more prevalent data structure so it seems
  best to favor them.  This also makes :level parallel the use of :level in write.

- One might expect that if there's a :level there should be a :length.  This is
  awkward for anything but lists, however.  While I would expect
  (copy '((1) (2) (3)) :length 2) to copy the first two elements but not the third,
  what do I do about (copy '#((1) (2) (3)) :length 2)?  One could say that we don't
  copy the entire surrounding structure, and, for consistency, apply the same rule
  to lists; the complexity of this seems to more than outweigh the dubious
  usefulness of :length in copy.

- I believe that (equalp x (copy x)) => t.  Indeed, the intermediate equality
  predicate that Eric Benson proposed (case sensitive equalp) (and that I agree
  is needed -- and should be supported as a hash table test) should also be true.

- I believe that this copy can easily be written in COMMON LISP, once the proposed
  functions hash-table-size, etc. have been added, except for the code dealing with
  structures. That implies to me that COMMON LISP probably needs more functionality
  for manipulating structures.  For example, I don't think that there is even a
  portable way of even telling whether or not an object *is* a user defined
  structure.

                                                - Don Morrison