What is the nature of designators?
Asked Answered
T

3

5

Svante just blew my mind by showing string designators in another answer do this:

(string= :& "&") -> T

Looking at CLHS, they say A designator is an object that denotes another object. which is fine but as these are different objects some kind of coercion needs to happen somewhere. By which I mean if the following list designator can be satisfied by a 'non-nil atom' some logic exists somewhere for handling this.

list designator n. a designator for a list of objects; that is, an object that denotes a list and that is one of: a non-nil atom (denoting a singleton list whose element is that non-nil atom) or a proper list (denoting itself).

I thought designators just could be a concept resulting from, for example, generic functions.. but the following line from CLHS...

Except as otherwise noted, in a situation where the denoted object might be used multiple times, it is implementation-dependent whether the object is coerced only once or whether the coercion occurs each time the object must be used.

... makes then seem very concrete.

So my questions

  • What is an example of how designators could be implemented in an implementation?
  • Is this mechanism extensible in any way by users?
  • Is this mechanism consistent across designators? (looking in clhs it seems there are 18 kinds of designator)

Cheers

Tiossem answered 11/12, 2014 at 16:38 Comment(0)
A
7

A designator is nothing more (or less) than an object that designates another. There's nothing special in the language about them; the concept of designators is just one that makes certain programming practices easier. The glossary says:

designator n. an object that denotes another object. In the dictionary entry for an operator if a parameter is described as a designator for a type, the description of the operator is written in a way that assumes that appropriate coercion to that type has already occurred; that is, that the parameter is already of the denoted type. For more detailed information, see Section 1.4.1.5 (Designators).

The link to that section is helpful:

1.4.1.5 Designators

A designator is an object that denotes another object.

Where a parameter of an operator is described as a designator, the description of the operator is written in a way that assumes that the value of the parameter is the denoted object; that is, that the parameter is already of the denoted type. (The specific nature of the object denoted by a “«type» designator” or a “designator for a «type»” can be found in the Glossary entry for “«type» designator.”)

Being able to look for things in the glossary helps. For instance, a string designator is something that can stand for a string:

string designator n. a designator for a string; that is, an object that denotes a string and that is one of: a character (denoting a singleton string that has the character as its only element), a symbol (denoting the string that is its name), or a string (denoting itself). The intent is that this term be consistent with the behavior of string; implementations that extend string must extend the meaning of this term in a compatible way.

The standard also happens to define the function string that gets the string designated by a string designator:

Returns a string described by x; specifically:

  • If x is a string, it is returned.
  • If x is a symbol, its name is returned.
  • If x is a character, then a string containing that one character is returned. string might perform additional, implementation-defined conversions.

This simplifies the implementation of functions that have to work with strings and string like things. For instance, you can define a make-person function takes a string designator:

(defun make-person (name)
  "Return a person with the name designated by NAME."
  (list :name (string name)))

(defun person-name (person)
  "Return the name of a person (a string)."
  (getf person :name))

The concept of designator isn't anything but a programming convention that makes defining flexible APIs easier. Common Lisp was defined as a language to unite a bunch of existing Lisps, and it may have been one of the easier ways to unify the behavior of different implementations.

There's a concept of list designator that gets used in case

list designator n. a designator for a list of objects; that is, an object that denotes a list and that is one of: a non-nil atom (denoting a singleton list whose element is that non-nil atom) or a proper list (denoting itself).

case keyform {normal-clause}* [otherwise-clause] => result*

normal-clause::= (keys form*)

keys—a designator for a list of objects. In the case of case, the symbols t and otherwise may not be used as the keys designator. To refer to these symbols by themselves as keys, the designators (t) and (otherwise), respectively, must be used instead.

I don't know of a function that returns the list designated by a list designator, but it's easy enough to write (this doesn't handle the special behavior of t and otherwise that case needs, but it handles list designators in general):

(defun to-list (x)
  "Return the list designated by x."
  (if (listp x) x
    (list x)))

Conventions like these can be useful in your own code sometimes, especially if you're defining things where there's a "registry" of things. E.g., if you have written either of:

(defmacro deftransform (name &rest args)
  `(setf (gethash ',name *transforms*)
         (make-transform ,@args)))

(defmacro deftransform (name &rest args)
  (setf (get ',name 'transform) (make-transform ,@args)))

Then you can define the concept of a transform designator as either a transform object, or a symbol (which designates the value for the symbol in the *transforms* table, or the value of transform property on the symbol). E.g.:

(defun transform (x)
  (if (transformp x) x
    (gethash name *transforms*)))

(defun transform (x)
  (if (transformp x) x
    (get x 'transform)))

That might make parts of your code easier to use. Function designators are similar

Atonic answered 11/12, 2014 at 19:10 Comment(2)
Wow, outstanding, both the answers have got me to the point of understanding this now. The details of the history and general completeness are really appreciated. Thanks to everyone!Tiossem
@Tiossem Yeah, once you understand the idea, it's both impressive and underwhelming. If you make good use of designators, your APIs feel very nice to use, which is great, but it is really a pretty simple concept, too, so you find your self asking 'was this so important that we actually needed a name for it?' (I found myself saying that when I finally read up about 'constructor based dependency injection'; I say "oh, is that all?")Atonic
I
6

An object designator is usually something that includes multiple kinds of objects to represent or name the intended object.

The good kind of designator definition doesn't include nil (i.e. you're allowed to not specify an object), has disjoint designator types, and the designators are usually a name to or an object that points to the intended object.

Note: In my opinion, designators are unnecessary shortcuts, mostly useful when using the REPL or prototyping something.

Here follows non-exhaustive lists of subjectively categorized designators.

Good kind of designators (essentially, non-nil, naming or container, designators):

Not so good designators:

  • external file format designator

    Its definition is intentionally open ended and implementation dependent, so it's up to the implementation to try to define good designators. Other than that, it's OK.

  • package designator

    Its definition is based on the string designator. Other than that, it's OK.

  • pathname designator

    I have mixed feelings about this one, mainly because streams are not reliable pathname designators. Only file streams, and synonym streams to such streams, are.

    For a given function that can take a stream or open one itself, sometimes it's useful to pass an already open stream to read from or write to and optionally leave it open, and sometimes it's useful to pass a pathname to be used in with-open-file (or similar), so the confluence here is lost.

    However, such a function should probably be split in two, one which explicitly takes a stream designator and one which explicitly takes a pathname designator.

  • string designator

    A string contains a character, not the other way around. However, a singleton string can be named by its single character, so I guess this is not too bad.

    One particular situation where this designator is not good is in destructive operations, which only take actual strings. This is expected, but it's a detail you must be aware of e.g. if you switch from string-upcase to nstring-upcase.

Bad designators:

  • list designator

    You must be aware that there's no designator for (nil).

  • readtable designator

    nil stands for the standard readtable, you cannot not specify a readtable. Or otherwise, an inconspicuous nil won't signal an error.

  • stream designator

    nil used as a context-sensitive designator, you cannot not specify a stream in such cases. Or otherwise, an inconspicuous nil won't signal an error.

  • stream variable designator

    Same reasons as stream designator.

Incantatory answered 17/12, 2014 at 10:39 Comment(0)
A
4

What

A designator for an object is something which is not necessarily the object, but from which the object can be inferred. Any object trivially designates itself.

E.g., a character is not a (subtype of a) string, but a character can sometimes be used instead of a string because it denotes a one-char string.

How: System-level

Implementations do things differently, look at their respective source code. You can find things like the use-level code below or something like

(defun human-address (human)
  (etypecase human
    (human ...)
    (string (human-address (gethash human *humanity*)))))

or even

(defun human-address (human)
  (when (stringp human)
    (setq human (gethash human *humanity*)))
  (unless (human-p human)
    (error ...))
  ...)

if defgeneric is not yet available due to bootstrapping issues or is being avoided as an optimization.

How: User-level

System-level designators are not user-extensible. I.e., you cannot define your own package or string designators.

However, you can define your own designators for your own types, e.g.:

(defclass human ...)
(defvar *humanity* (make-hash-table ...))
(defgeneric human-address (human)
  (:method ((human human))
     ...)
  (:method ((name string))
    (human-address (gethash name *humanity*))))

Here a string serves as a designator for human.

Admirable answered 11/12, 2014 at 17:20 Comment(4)
Cheers, seems clear enough though I may be missing something. If I have is straight the designators are standardized terminology for saying 'we also have logic for these cases'. Is that about right? Designators are a concept not a 'thing'. Also I hadn't seen that style of defining methods inside defgeneric so thanks for that! I guess I'm still wondering why, if methods specialization in methods serves as a designator, the clhs doesn't mention the term in defmethod or defgeneric, they are normally so rigorousTiossem
This doesn't seem to answer the question. I still don't know what designators are.Heterodoxy
@Marcin: "what designator is" was actually explained in the original question :-) I added a "what" section though.Admirable
"A designator is something which is not the object" It's not necessarily the object. Some objects designate themselves.Atonic

© 2022 - 2024 — McMap. All rights reserved.