So I thought to look at how Lisp handles types. (You'd think I'd have looked earlier.)
Function Argument Type Checking (not there)
First thing to note, they don't have function argument type checking.
"function arguments in Lisp do not have declared data types, as they do in other programming languages. It is therefore up to the individual function to test whether each actual argument belongs to a type that the function can use."
While Rebol does have type checking, it's extremely limited. It's a frustrating design point...because it's so limited, and because it is poked into a strange compacted place. When people ask "why only 64 data types?" that gets at some of the strangeness of this feature.
When you try to build reflective abilities for the built-in type checking feature, you get into questions of how to expose these 64 bits. That's done via TYPESET!, which isn't very easy to process, and doesn't seem like the foundation of a broader answer. See "the TYPESET! representation problem"
Lisp Type Representation
They seem basically comfortable with the idea of using "WORD!" to name types, e.g. ensure 'integer 1. (Although their version of ENSURE is called THE, as a type annotation. the 'integer 1 That's shorter...same length as "non", and would free ENSURE for other uses. I can't tell if it makes total sense or not.)
"Types are not objects in Common Lisp. There is no object that corresponds to the type
integer, for example. What we get from a function like
type-of, and give as an argument to a function like
typep, is not a type, but a type specifier. A type specifier is the name of a type."
They don't use the equality operator to check the result of "TYPE OF" against anything, because they get back additional "stuff, and I gather that "stuff" can vary from one implementation to another:
* (type-of 1234) (INTEGER 0 4611686018427387903)
So it seems they lean toward using functions to ask if types match. They call these "predicates" and end them in P. If we were to do a similar thing, it might look like:
>> type? 10 'integer == #[true] ; knew to disregard stuff that `'integer = type of 1` wouldn't
Their notion of typeclasses is somewhat concretized. Their ANY-VALUE! is just
t. So they do not try and express the notion of "anything" as an explicit union of all known types, the way that Rebol has. It's inherently not extensible if you say
any-value!: make typeset! [integer! text! ...] and then add a new datatype later.
If you scroll down a bit you get to some examples:
;; The universal type “t”, has everything as its value. (typep 'x 't) ;; ⇒ true (typep 12 't) ;; ⇒ true ;; The empty type: nil (typep 'x 'nil) ;; ⇒ false; nil has no values. ;; The type “null” contains the one value “nil”. (typep nil 'null) ;; ⇒ true (typep () 'null) ;; ⇒ true ;; “(eql x)” is the singelton type consisting of only x. (typep 3 '(eql 3)) ;; ⇒ true (typep 4 '(eql 3)) ;; ⇒ false ;; “(member x₀ … xₙ)” denotes the enumerated type consisting of only the xᵢ. (typep 3 '(member 3 x "c")) ;; ⇒ true (typep 'x '(member 3 x "c")) ;; ⇒ true (typep 'y '(member 3 x "c")) ;; ⇒ false ;; “(satisfies p)” is the type of values that satisfy predicate p. (typep 12 '(satisfies (lambda (x) (oddp x)))) ;; ⇒ false (typep 12 '(satisfies evenp) ) ;; ⇒ true ;; Computation rule for comprehension types. ;; (typep x '(satisfies p)) ≈ (if (p x) t nil)
Why Isn't Everything Just a Function?
It seems like SATISFIES is the sort of generic way to hook up any function as a type check. Like you could do arbitrary calculation to check to see if a number was prime...or something like that.
So why isn't this the only kind of type check? Maybe something to do with the limits of what can be checked at compile time.
It seems that SBCL requires you to go through a symbol to use SATISFIES. Whether that's on purpose or not, that it forces you to name your predicate is helpful as documentation.
Stipulating Variables Match A Type
There is a feature of saying that a word is only assigned particular types (declaim)...and though it didn't work for me in
"clisp" it did in
Were we to have such a feature, we'd presumably give it a better name, but:
>> declaim 'x text! >> x: 10 ** Error: you said that x had to be TEXT!
When you think about languages arising like TypeScript, being able to annotate a variable to say that it has a certain type is a popular feature. It's not clear how to do this inexpensively with complex type checks.
Does This Point To Any Answers?
The main thing to notice here is that the process isn't driven by "typesets", but rather by a kind of "type matching dialect"...that covers a few kinds of combining operations (including AND, OR), and if you can't get exactly what you want from the built-in bits, you can supply an open-ended function.
It goes in the direction I've been thinking about, where the core function application code doesn't really know what type checking is. So it's layers above (like FUNC/FUNCTION) that provide the type checking, and define the type checking language.
Nothing particularly new, just thought I'd look.