• Top
    • Documentation
    • Books
    • Recursion-and-induction
    • Boolean-reasoning
    • Projects
    • Debugging
    • Std
    • Proof-automation
    • Macro-libraries
      • B*
      • Defunc
      • Fty
        • Deftagsum
        • Defprod
          • Defflexsum
          • Defbitstruct
          • Deflist
          • Defalist
          • Defbyte
          • Defresult
          • Deffixtype
          • Deffixequiv
          • Fty-discipline
          • Defoption
          • Fty-extensions
          • Defsubtype
          • Deftypes
          • Defflatsum
          • Deflist-of-len
          • Defbytelist
          • Defset
          • Fty::basetypes
          • Specific-types
          • Defvisitors
          • Deffixtype-alias
          • Defomap
          • Deffixequiv-sk
          • Defunit
          • Deffixequiv-mutual
          • Fty::baselists
          • Defmap
        • Std/util
        • Apt
        • Defdata
        • Defrstobj
        • Seq
        • Match-tree
        • Defrstobj
        • With-supporters
        • Def-partial-measure
        • Template-subst
        • Soft
        • Defthm-domain
        • Event-macros
        • Def-universal-equiv
        • Def-saved-obligs
        • With-supporters-after
        • Definec
        • Sig
        • Outer-local
        • Data-structures
      • ACL2
      • Interfacing-tools
      • Hardware-verification
      • Software-verification
      • Testing-utilities
      • Math
    • Fty
    • Deftypes

    Defprod

    Define a new product type, like a struct in C, following the fty-discipline.

    Defprod is a macro for introducing struct-like types. It can be used to conveniently define a recognizer, constructor, accessors, fixing function, and equivalence relation, and other supporting macros and documentation for a new struct-like type. It automatically arranges so that these new definitions follow the fty-discipline.

    Defprod can be used in a standalone fashion to introduce simple (non mutually-recursive) product types. But it is also compatible with deftypes, so it can be used to create products that are mutually-recursive with other deftypes compatible type generators.

    As with all deftypes-compatible type generators, its field types must be types that are ``known'' to FTY. That is, they must either refer to types that have been introduced with deffixtype or that have been produced by other FTY type generating macros. (Fields can also be completely untyped.) See also basetypes for some base types with fixing functions.

    Basic Example

    (defprod sandwich
      ((bread   symbolp :default 'sourdough)
       (coldcut meatp)
       (spread  condimentp)))

    This example would produce:

    • A recognizer sandwich-p.
    • A fixing function sandwich-fix.
    • An equivalence relation sandwich-equiv.
    • A constructor (sandwich bread coldcut spread).
    • A constructor macro (make-sandwich :bread bread ...), which simply expands to a constructor call but uses the given defaults.
    • A changer macro (change-sandwich x :bread bread ...).
    • An accessor for each field, e.g., sandwich->bread.
    • A b* binder sandwich, like those in std::defaggregate.

    Much of this—the make/change macros, accessor names, b* binders—is nearly identical to std::defaggregate. If you have used defaggregate, switching to defprod should be very comfortable.

    General Syntax:

    (defprod prodname
       (list-of-fields)
       keyword-options)

    The fields are std::extended-formals, except that the guard must be either simply a predicate or the call of a unary predicate on the field name. Acceptable keyword arguments for each field are:

    • :default: default value of the field in its constructor macro
    • :rule-classes: rule-classes for the return type theorem of the accessor.

    Name/Documentation Options

    :pred
    :fix
    :equiv
    :count
    These allow you to override the default function names, which are (respectively) name-p, name-fix, name-equiv, and name-count.
    As a special case, :count may be nil, meaning no count function is produced. (A count function is only produced when this is mutually-recursive with other type generators.)
    :parents
    :short
    :long
    These let you customize the xdoc documentation produced for this type. The documentation generated for products will automatically list the fields and link to their types; it's often convenient to put additional notes directly in the fields, e.g.,
    (defprod monster
      :parents (game)
      :short "A monster to fight."
      ((name   stringp "Name of the monster")
       (health natp    "How many hit points does the monster have?")
       ...)
      :long "<p>More details about monsters could go here.</p>")

    Performance/Efficiency Options

    :tag
    Defaults to nil, meaning that the product is untagged. Otherwise it must be a keyword symbol and this symbol will be consed onto every occurrence of the object.
    Having tags on your objects adds some execution/memory overhead, but provides a reasonably nice way to distinguish different kinds of objects from one another at runtime.
    :layout
    Defaults to :alist, but might instead be set to :tree, :fulltree or :list. This determines how the fields are laid out in the object's representation.
    The :alist format provides the best readability/debuggability but is the worst layout for execution/memory efficiency. This layout represents instances of your product type using an alist-like format where the name of each field is next to its value. When printing such an object you can easily see the fields and their values, but creating these objects requires additional consing to put the field names on, etc.
    The :tree or :fulltree layouts provides the best efficiency and worst readability. They pack the fields into a compact tree structure, without their names. In :tree mode, any (nil . nil) pairs are compressed into just nil. In :fulltree mode this compression doesn't happen, which might marginally save time if you know your fields will never be in pairs of nils. Tree-based structures require minimal consing, and each accessor simply follows some minimal, fixed car/cdr path into the object. The objects print as horrible blobs of conses that can be hard to inspect.
    The :list layout strikes a middle ground, with the fields of the object laid out as a plain list. Accessing the fields of such a structure may require more cdr operations than for a :tree layout, but at least when you print them it is still pretty easy to tell what the fields are.
    Example: a tagless product with 5 fields could be laid out as follows:
    `((,a . ,b) . (,c . (,d . ,e)))                  ;; :tree
    `(,a ,b ,c ,d ,e)                                ;; :list
    `((a . ,a) (b . ,b) (c . ,c) (d . ,d) (e . ,e))  ;; :alist
    :hons
    NIL by default. When T, the constructor is defined using hons rather than cons, so your structures will always be structure shared. This may improve memory efficiency in certain cases but is probably not a good idea for most structures.
    :inline
    Default: (:acc :fix), meaning that the accessors and fixing function, which for execution purposes is just the identity, will be defined as an inline function. This argument may also contain :xtor, which causes the constructor to be inlined as well, but this is typically less useful as the constructor requires some amount of consing. The option :all (not in a list) is also possible.

    Other Options

    :measure
    A measure is only necessary in the mutually-recursive case, but is probably necessary then. The default measure is (acl2-count x), but this may not work in the mutually-recursive case because of the possibility that x could be (say) an atom, in which case the acl2-count of x will be no greater than the acl2-count of a field. Often something like (two-nats-measure (acl2-count x) 5) is a good measure for the product, where the other mutually-recursive types have a similar measure with smaller second component.
    :require
    :reqfix
    This adds a dependent type requirement; see the section on this feature below.

    Experimental Dependent Type Option

    The top-level keyword :require can add a requirement that the fields satisfy some relation. Using this option requires that one or more fields be given a :reqfix option; it must be a theorem that applying the regular fixing functions followed by the :reqfix of each field independently yields fields that satisfy the requirement. It should also be the case that applying the reqfixes to fields already satisfying the requirement leaves them unchanged. For example:

    (defprod sizednum
      ((size natp)
       (bits natp :reqfix (loghead size bits)))
      :require (unsigned-byte-p size bits))

    If there is more than one field with a :reqfix option, these reqfixes are applied to each field independently, after applying all of their types' fixing functions. For example, for the following to succeed:

    (defprod foo
      ((a atype :reqfix (afix a b c))
       (b btype :reqfix (bfix a b c))
       (c       :reqfix (cfix a b c)))
      :require (foo-req a b c))

    the following must be a theorem (assuming afix and bfix are the fixing functions for atype and btype, respectively):

    (let ((a (afix a))
          (b (bfix b)))
      (let ((a (afix a b c))
            (b (bfix a b c))
            (c (cfix a b c)))
        (foo-req a b c)))

    Notice the LET, rather than LET*, binding the fields to their reqfixes. It would NOT be sufficient for this to be true with a LET*.