In the post at Common Lisp class hierarchy, Rainer Joswig & Joshua Taylor carefully distinguish some of the differences between built-in Common Lisp types and classes, where classes form part of the CLOS extension to the baseline. The type/class (plus mop) distinctions are also reflected in Pfeil's comprehensive hierarchy diagram. Using the diagram, it seems possible to extract the two distinct hierarchies. In particular, I am currently most interested in the tops of the hierarchies; namely, the direct subtypes and subclasses of t (since t is both a type and a class). Here are some provisional subtypes/subclasses distilled from the diagram:
For the type hierarchy, the direct subtypes of t seem to be atom, character, random-state, hash-table, restart, readtable, package, pathname, stream, function, array, sequence, number, and condition. All other types like float or list are subtypes of one of these types. The type hierarchy is also not strictly hierarchical (since (subtype t t) => T, but this seems to be the only exception). (ps: the types symbol and structure-object are not included in the diagram, but also may be direct subtypes of t.)
For the class hierarchy, the direct subclasses of t include corresponding classes for all the types above (except atom, and perhaps structure-object which is now a subclass of standard-object(?)), plus standard-object.
The MOP extends CLOS by adding the class metaobject (plus some meta-subclasses), but does not seem to add to the direct subclasses of t.
Can someone verify if this understanding is correct, or provide additional clarifications?
Note: I've discovered at least one mistake in the type hierarchy description above. All of the listed subtypes (character, etc.) are apparently subtypes of atom, so they are not direct subtypes of t. The only other direct subtype of t seems to be sequence, since a sequence can be a cons (non-atom). Moreover, symbol is, in fact, included in the diagram, and is also a subtype of atom.
Graphing CL Subtypes of T in LispWorks:
Finding the subtypes:
(defun find-synonym-types (type1 types)
(remove-if-not (lambda (type2)
(and (not (eq type1 type2))
(subtypep type1 type2)
(subtypep type2 type1)))
types))
(defun find-all-types-in-packages (packages &key include-nil)
(let ((types nil))
(loop for package in packages
when (find-package package)
do (do-external-symbols (sym (find-package package))
(when (ignore-errors (subtypep sym t))
(let ((synonyms (find-synonym-types sym types)))
(if synonyms
(pushnew sym (get (first synonyms) :type-synonyms))
(pushnew sym types))))))
(if include-nil types (remove nil types))))
(defun direct-subtypes (supertype &key all-types)
(loop with subtypes = (remove supertype (loop for type in all-types
when (subtypep type supertype)
collect type))
for type in subtypes
when (loop for type2 in (remove type subtypes)
never (subtypep type type2))
collect type))
Graphing it:
#+capi
(defun class-color (class-name)
(typecase (find-class class-name)
(standard-class :blue)
(built-in-class :violet)
(structure-class :brown)
(otherwise :red)))
#+capi
(defun graph-subtypes-from-t ()
(let ((all-types (find-all-types-in-packages '("CL" "CLOS"))))
(capi:contain
(make-instance
'capi:graph-pane
:roots '(t)
:children-function (lambda (type)
(direct-subtypes type :all-types all-types))
:node-pane-function #'(lambda (graph-pane node)
(declare (ignore graph-pane))
(make-instance
'capi:item-pinboard-object
:text (format
nil "~{~a~^, ~}"
(cons node
(get node :type-synonyms)))
:graphics-args `(:foreground
,(if (find-class node
nil)
(class-color node)
:black))))
:edge-pane-function #'(lambda (self from to)
(declare (ignore self from to))
(make-instance
'capi:arrow-pinboard-object
:graphics-args '(:foreground
:grey))))
:title "Common Lisp Subtypes in LispWorks")))
Graph
The types with corresponding CLOS classes are written in blue and structure classes are brown. The type NIL is not drawn. Included are types/classes from the CLOS MOP. Some types have more than one name. Also note that this graph is specific to LispWorks, especially what types are actually also structures, CLOS classes, built-in class or something else.
The term direct subtype almost never makes sense.
Why do you think that character is a direct subtype of t?
But character is a subtype of atom which is a subtype of t!
Well, maybe character is a direct subtype of atom?
Nope, character is a subtype of (or character number) which is a subtype of atom.
So, maybe character is a direct subtype of (or character number)?
Nope, character is a subtype of (or character integer) which is a subtype of (or character number).
You can think of classes as integers and types as rationals - there are no integers between 3 and 4, but there are plenty of rationals between any two different rationals.
You already know that there are types and there are classes in Common Lisp.
Types categorize objects, while classes tag objects. Moreover, an object may be of many types, but it's a direct instance of a single class.
If you ask an object for its type through type-of, you may get many valid answers. A non-exhaustive example:
(type-of 1)
=> (integer 1 1)
OR => bit
OR => (unsigned-byte 1)
OR => (mod 2)
OR => (integer (0) (2))
OR => (signed-byte 1)
OR => (signed-byte 16)
OR => fixnum
OR => integer
OR => (integer * *)
OR => rational
OR => real
OR => number
OR => t
OR => atom
For typep and subtypep, things are a lot more confusing, as you can generate types at will, like (eql 1), (member 1), (or fixnum bignum), (and (integer * 1) (integer 1 *)), (not (satisfies zerop)), (satisfies oddp), etc. for true results, and likewise for false results.
However, for each object, you have exactly one class:
(class-of 1)
=> #<BUILT-IN-CLASS INTEGER>
In practice, you can't determine the whole type hierarchy. There is the notion that anything is t, nothing is nil and there might be an hierarchy for atomic standard types. But the reality is that there are compound types and user-defined types, so it's technically impossible to draw a full type hierarchy, as that would require to look at the result of subtypep for every imaginable object.
Even so, there might not exist a hierarchy between types. Taking the mistake you say you found, I believe the author did not want to litter the diagram with arrows pointing to atom for every type other than cons. But in fact, that would probably not be a correct representation at all, since some objects of type list and sequence are cons and others are atom, specifically, the empty list is atom.
However, you can determine the whole class hierarchy, even though a class may have multiple superclasses.
Some implementations return the fixnum class in the class-of example, an extension to the standard (fixnum is a type but not a class), to allow CLOS method specialization on fixnum and/or to allow optimization tricks.
From a few implementations I tried, only CLISP returned the integer class.
Related
Maybe this question is too general, nevertheless i'll try:
Is there any comprehensive guide on types in common lisp?
I'm kind of confused about this subject:
Why are non-primitive types declared in make-array's :element-type are promoted to t? Is there any possibility for compile-time or runtime checks of the real declared type?
Why are CLOS slot defined types don't work as constraints, allowing to put value of any type into the slot? Again, what about the checks?
The same for the functions' types declarations with declare.. Are they just the optimization hints to the compiler?
Also, can I use custom type specifiers, including satisfies in forementioned places for some robust checks, or they could only be used for explicit checks with typep e.t.c?
As you can see, i've got some mess in my head, so would really appreciate any neat guide (or a set of guides).
I'm on SBCL, but would also be glad to know about differences between implementations.
You need to tell the compiler to optimize for safety if you want it to actually enforce the types:
CL-USER> (declaim (optimize (safety 3)))
NIL
CL-USER> (defclass foobar () ())
#<STANDARD-CLASS COMMON-LISP-USER::FOOBAR>
CL-USER> (defun foo (a)
(make-array 1 :element-type 'foobar
:initial-contents (list a)))
FOO
CL-USER> (foo (make-instance 'foobar))
#(#<FOOBAR {1005696CE3}>)
CL-USER> (foo 12)
;=> ERROR
CL-USER> (declaim (ftype (function (integer integer) integer) quux))
(QUUX)
CL-USER> (defun quux (a b)
(+ a b))
QUUX
CL-USER> (quux 12 12)
24 (5 bits, #x18, #o30, #b11000)
CL-USER> (quux 12 "asd")
;=> ERROR
Checking the types at run-time adds some overhead (especially if it happens in a loop), and may be done multiple times for a single value, so it's not done by default.
(declaim (optimize (safety 3)))
(defun some-predicate-p (a)
(format t "~&Checking type...")
(integerp a))
(deftype foo () `(satisfies some-predicate-p))
(defclass bar ()
((foo :type foo :initarg :foo)))
(declaim (ftype (function (foo) list) qwerty))
(defun qwerty (foo)
(loop repeat 10 collecting (make-instance 'bar :foo foo)))
(qwerty 12)
; Checking type...
; Checking type...
; Checking type...
; Checking type...
; Checking type...
; Checking type...
; Checking type...
; Checking type...
; Checking type...
; Checking type...
; Checking type...
;=> (#<BAR {1003BCA213}> #<BAR {1003BCA263}> #<BAR {1003BCA2B3}>
; #<BAR {1003BCA303}> #<BAR {1003BCA353}> #<BAR {1003BCA3A3}>
; #<BAR {1003BCA3F3}> #<BAR {1003BCA443}> #<BAR {1003BCA493}>
; #<BAR {1003BCA4E3}>)
If you want a function to always check the type of a place, regardless of the optimization settings, you should use CHECK-TYPE manually.
Why are non-primitive types declared in make-array's :element-type are promoted to t? Is there any possibility for compile-time or runtime checks of the real declared type?
The :element-type parameter is there that an implementation can choose an optimized memory layout for an array - mostly for saving memory space. This is typically useful with primitive types. For other types most Common Lisp runtimes will have no optimized storage implementation and thus the declaration will have no useful effect.
Why are CLOS slot defined types don't work as constraints, allowing to put value of any type into the slot? Again, what about the checks?
An implementation may do that.
Clozure CL:
? (defclass foo () ((bar :type integer :initform 0 :initarg :bar)))
#<STANDARD-CLASS FOO>
? (make-instance 'foo :bar "baz")
> Error: The value "baz", derived from the initarg :BAR,
can not be used to set the value of the slot BAR in
#<FOO #x302000D3EC3D>, because it is not of type INTEGER.
The same for the functions' types declarations with declare.. Are they just the optimization hints to the compiler?
The type declarations with declare can be ignored - for example in Symbolics Genera most declarations will be ignored. Implementations are not required to process them. Most implementations will at least interpret them as assurance that some object will be of that type and create optimized code for that - possibly without runtime checks and/or specialized code for that type. But it's usually necessary to set corresponding optimization levels (speed, safety, debug, ...)
Additionally compilers derived from CMUCL's compiler (SBCL, ...) may use them for some compile time checks.
But none of the effects is specified in the ANSI CL standard. The standard provides the declarations and leaves the interpretation to the implementations.
How types are handled during compilation is defined by implementations. In the case of SBCL, types are generally treated as assertions, but the actual behavior depends on optimisation levels.
Types as assertions means that if a function takes a number n and produces a string s, you generally don't assume that n is a number. Instead, what you have is a guarantee that if the function returns, then n effectively was a number and s is now a string. But if you reuse s, your compiler has an opportunity to skip a check for s being a string. This is generally what you want because your functions are available globally and can thus be called from anywhere. Since functions are responsible for checking their inputs, it is normal that you always check for n being a number first.
Yet, type declarations for functions can help you in case you call a function in a context where it can be proved that types will certainly mismatch at runtime (the intersection of types is empty). In order to trust type assertions blindly, you have to lower the safety levels.
Note: I originally posted it at a comment, but in order to avoid it being deleted, here is a link to a nice graphics representing relationships between types in CL:
http://sellout.github.io/2012/03/03/common-lisp-type-hierarchy
Is there a way to define alongside a (typed) structure a contract for the entire structure in Typed Racket? In my particular case, I have a structure that takes two lists as fields, and I want to require the lists to be the same length.
I've looked at:
make-struct-type, which allows specification of a "guard" that moderates constructor calls. I could pass a procedure that raises an exception if the lengths don't match, but I don't know what to do with the values returned by make-struct-type.
struct/c and the struct form of contract-out, both of which produce structure contracts from contracts on individual fields. This seems unhelpful here.
Ideally I would like to bind the contract to the structure immediately (as in define/contract), but I'm open to adding the contract when I provide the type's procedures. However, I currently provide the recognizer and accessor procedures individually rather than using struct-out (so that I can exclude or rename individual procedures), and I'd like to keep that flexibility.
Right now I have something like this:
(provide
(rename-out
[MyStruct my-struct]
[MyStruct? my-struct?]
[MyStruct-foo my-struct-foo]
[MyStruct-bar my-struct-bar]
)
)
(struct MyStruct (
[foo : (Listof Symbol)]
[bar : (Listof Any)]
))
Wow. I'm surprised how difficult it was to do that in Typed Racket. In plain (untyped) Racket its as simple as adding a #:guard when making you struct. Unfortunately, the struct form in Typed Racket doesn't support it.
So, to deal with this, I would generate a structure type with a private (to the module) constructor name, and then make your own constructor function that actually does the contract you wanted it to check.
This will end up looking something like:
(struct env ([keys : (Listof Symbol)]
[values : (Listof Any)])
#:constructor-name internal-env)
(: make-env (-> (Listof Symbol) (Listof Any) env))
(define (make-env k v)
(unless (= (length k) (length v))
(raise-arguments-error 'env
"env key and value counts don't match"
"keys" k
"values" v))
(internal-env k v))
Now, when you provide the struct, simply don't provide internal-env, but do provide the make-env function:
(provide (except-out (struct-out env)
internal-env)
make-env)
Now, when I construct an env, I get a (dynamic) check to ensure the list lengths match:
> (make-env '() '())
#<env>
> (make-env '(a) '(1))
#<env>
> (make-env '(a) '())
env: env key and value counts don't match
keys: '(a)
values: '()
You may find defining and providing the structure definitions with guard functions from Racket, then require/typed-ing and type annotating the structures in Typed Racket a simpler solution than attempting to achieve the same effect purely in Typed Racket.
I'm learning about generic functions in CLOS.
Because of the type of examples I find in textbooks and online, I'm getting very confused. The examples always use the fact that there is multiple dispatch. Based on the argument type, a different calculation is performed. However, why are the arguments themselves never used in the examples?
Example code from Wikipedia
; declare the common argument structure prototype
(defgeneric f (x y))
; define an implementation for (f integer t), where t matches all types
(defmethod f ((x integer) y) 1)
(f 1 2.0) => 1
; define an implementation for (f integer real)
(defmethod f ((x integer) (y real)) 2)
(f 1 2.0) => 2 ; dispatch changed at runtime
In the examples above, you can see the methods themselves never actually use the x or y variables. Is it a coincidence that all of these examples never use the variables? Can they be used?
Also, it's written on Wikipedia:
Methods are defined separately from classes, and they have no special access (e.g. "this", "self", or "protected") to class slots.
Alright, so methods do not have a "this" because they do not belong to a class. But why can generic-function methods have a receiver then? Isn't the receiver similar to the 'this' in a class?
Sure you can access the variables from the parameter list. The wikipedia example is only there to illustrate which method returns the value.
But why can generic-function methods have a receiver then? Isn't the receiver similar to the 'this' in a class?
CLOS generic functions don't have a single receiver, thus it makes no sense to use the word receiver. The implementation you mention likely does not implement full CLOS, but a variant without multiple dispatch.
CLOS Example:
CL-USER 8 > (defmethod plus ((s1 string) (s2 string))
(concatenate 'string s1 s2))
#<STANDARD-METHOD PLUS NIL (STRING STRING) 4020001E6B>
CL-USER 9 > (plus "foo" "bar")
"foobar"
You see that the both variables s1 and s2 are used. It just makes no sense to name one of them receiver.
But you can name the variables as you like and when your application only uses dispatch over the first argument, you might want to call that variable receiver, but the name has no semantic for CLOS. It is just another name.
Generally for CLOS code it is preferred to give the arguments useful names.
This is misleading, because we are not doing message passing in CLOS:
(defmethod plus ((receiver string) (argument string))
(concatenate 'string receiver argument))
This is more useful:
(defmethod plus ((string-1 string) (string-2 string))
(concatenate 'string string-1 string-2))
The examples are just showing how dispatching works based on types, so they don't bother to use the variables. But you certainly could, e.g.
(defmethod f ((x integer) (y symbol))
(* x x))
(f 3 'foo) => 9
The use of a receiver is just a convention. If you want to use CLOS similarly to other OOP languages, you might just dispatch on the type of the first argument. And you could call it this or self to make the intent clear.
The code below implements a macro call with an implicit optional parameter--implicit because it is buried in the &rest parameter. Is there a better way to code the macro (and its supporting function)--perhaps by making the optional parameter explicit with an &optional keyword (or maybe some other way)? It is preferred that the first parameter is required, the second optional, and the numerous remaining parameters required. Also prefer to keep the macro definition simple, with the work done by the supporting function, if possible (but looking to learn more advanced approaches too):
(defstruct action
name duration slot1 slot2 slot3)
(defmacro define-action (name &rest rest)
`(install-action ',name ',rest))
(defun install-action (name &rest rest)
(let* ((slots (first rest))
(duration (if (numberp (first slots))
(prog1 (first slots)
(setf slots (cdr slots)))
0))
(action (make-action :name name :duration duration
:slot1 (first slots) :slot2 (second slots)
:slot3 (third slots))))
(print action)))
(define-action move a b c) ;first example no duration
(define-action move 1 a b c) ;second example with duration=1
#S(ACTION :NAME MOVE :DURATION 0 :SLOT1 A :SLOT2 B :SLOT3 C)
#S(ACTION :NAME MOVE :DURATION 1 :SLOT1 A :SLOT2 B :SLOT3 C)
Additional point of clarification: The slot values above are really various specifications represented as (sometimes deeply nested) lisp trees. The function install-action interprets the specs and installs their semantic content into a database.
Parameter and argument lists: style
It is useful to have explicit parameter lists. Common Lisp provides extensive support for it. But even then not every parameter list idea can be supported. As jkiiski notes in his comment, it is always helpful to have a speaking parameter list: it helps the developer, the compiler can catch some errors at compile time and Lisp can provide better debugging information.
One of the style rules: optional arguments should be at the end of the argument list. Common Lisp itself violates this at least one place (can only remember one function right now) and it is always painful and error prone.
Fix the arglist of INSTALL-ACTION:
(defun install-action (name slots &optional (duration 0))
...)
Fix the arglist of the macro, too
Use it like:
(define-action move (a b c) 1)
A list of things might better be grouped in the macro interface.
(defmacro define-action (name slots &optional duration)
...)
Or even use a keyword named arguments:
(define-action move :slots (a b c) :duration 1)
It gets longer, but readability is greatly improved.
Side question: do we need a macro DEFINE-ACTION and why?
The main reasons for such a macro are usually:
less quoting
special syntax
compile-time side-effects
expansion into other macro calls
I'm at the early stages of designing a framework and am fooling around with typed/racket. Suppose I have the following types:
(define-type Calculate-with-one-number (-> Number Number))
(define-type Calculate-with-two-numbers (-> Number Number Number))
And I want to have a function that dispatches on type:
(: dispatcher (-> (U Calculate-with-one-number Calculate-with-two-numbers) Number))
(define (dispatcher f args)
(cond [(Calculate-with-one-number? f)
(do-something args)]
[(Calculate-with-two-numbers? f)
(do-something-else args)]
[else 42]))
How do I create the type-predicates Calculate-with-one-number? and Calculate-with-two-numbers? in Typed/Racket? For non-function predicates I can use define-predicate. But it's not clear how to implement predicates for function types.
Since I am self answering, I'm taking the liberty to clarify the gist of my question in light of the discussion of arity as a solution. The difference in arity was due to my not considering its implications when specifying the question.
The Problem
In #lang typed/racket as in many Lisps, functions, or more properly: procedures, are first class dataypes.
By default, #lang racket types procedures by arity and any additional specificity in argument types must be done by contract. In #lang typed/racket procedures are typed both by arity and by the types of their arguments and return values due to the language's "baked-in contracts".
Math as an example
The Typed Racket Guide provides an example using define-type to define a procedure type:
(define-type NN (-> Number Number))
This allows specifying a procedure more succinctly:
;; Takes two numbers, returns a number
(define-type 2NN (-> Number Number Number))
(: trigFunction1 2NN)
(define (trigFunction1 x s)
(* s (cos x)))
(: quadraticFunction1 2NN)
(define (quadraticFunction1 x b)
(let ((x1 x))
(+ b (* x1 x1))))
The Goal
In a domain like mathematics, it would be nice to work with more abstract procedure types because knowing that a function is cyclical between upper and lower bounds (like cos) versus having only one bound (e.g. our quadratic function) versus asymptotic (e.g. a hyperbolic function) provides for clearer reasoning about the problem domain. It would be nice to have access to abstractions something like:
(define-type Cyclic2NN (-> Number Number Number))
(define-type SingleBound2NN (-> Number Number Number))
(: trigFunction1 Cyclic2NN)
(define (trigFunction1 x s)
(* s (cos x)))
(: quadraticFunction1 SingleBound2NN)
(define (quadraticFunction1 x b)
(let ((x1 x))
(+ b (* x1 x1))))
(: playTone (-> Cyclic2NN))
(define (playTone waveform)
...)
(: rabbitsOnFarmGraph (-> SingleBound2NN)
(define (rabbitsOnFarmGraph populationSize)
...)
Alas, define-type does not deliver this level of granularity when it comes to procedures. Even moreover, the brief false hope that we might easily wring such type differentiation for procedures manually using define-predicate is dashed by:
Evaluates to a predicate for the type t, with the type (Any -> Boolean : t). t may not contain function types, or types that may refer to mutable data such as (Vectorof Integer).
Fundamentally, types have uses beyond static checking and contracts. As first class members of the language, we want to be able to dispatch our finer grained procedure types. Conceptually, what is needed are predicates along the lines of Cyclic2NN? and SingleBound2NN?. Having only arity for dispatch using case-lambda just isn't enough.
Guidance from Untyped Racket
Fortunately, Lisps are domain specific languages for writing Lisps once we peal back the curtain to reveal the wizard, and in the end we can get what we want. The key is to come at the issue the other way and ask "How canwe use the predicates typed/racket gives us for procedures?"
Structures are Racket's user defined data types and are the basis for extending it's type system. Structures are so powerful that even in the class based object system, "classes and objects are implemented in terms of structure types."
In #lang racket structures can be applied as procedures giving the #:property keyword using prop:procedure followed by a procedure for it's value. The documentation provides two examples:
The first example specifies a field of the structure to be applied as a procedure. Obviously, at least once it has been pointed out, that field must hold a value that evaluates to a procedure.
> ;; #lang racket
> (struct annotated-proc (base note)
#:property prop:procedure
(struct-field-index base))
> (define plus1 (annotated-proc
(lambda (x) (+ x 1))
"adds 1 to its argument"))
> (procedure? plus1)
#t
> (annotated-proc? plus1)
#t
> (plus1 10)
11
> (annotated-proc-note plus1)
"adds 1 to its argument"
In the second example an anonymous procedure [lambda] is provided directly as part of the property value. The lambda takes an operand in the first position which is resolved to the value of the structure being used as a procedure. This allows accessing any value stored in any field of the structure including those which evaluate to procedures.
> ;; #lang racket
> (struct greeter (name)
#:property prop:procedure
(lambda (self other)
(string-append
"Hi " other
", I'm " (greeter-name self))))
> (define joe-greet (greeter "Joe"))
> (greeter-name joe-greet)
"Joe"
> (joe-greet "Mary")
"Hi Mary, I'm Joe"
> (joe-greet "John")
"Hi John, I'm Joe
Applying it to typed/racket
Alas, neither syntax works with struct as implemented in typed/racket. The problem it seems is that the static type checker as currently implemented cannot both define the structure and resolve its signature as a procedure at the same time. The right information does not appear to be available at the right phase when using typed/racket's struct special form.
To get around this, typed/racket provides define-struct/exec which roughly corresponds to the second syntactic form from #lang racket less the keyword argument and property definition:
(define-struct/exec name-spec ([f : t] ...) [e : proc-t])
name-spec = name
| (name parent)
Like define-struct, but defines a procedural structure. The procdure e is used as the value for prop:procedure, and must have type proc-t.
Not only does it give us strongly typed procedural forms, it's a bit more elegant than the keyword syntax found in #lang racket. Example code to resolve the question as restated here in this answer is:
#lang typed/racket
(define-type 2NN (-> Number Number Number))
(define-struct/exec Cyclic2NN
((f : 2NN))
((lambda(self x s)
((Cyclic2NN-f self) x s))
: (-> Cyclic2NN Number Number Number)))
(define-struct/exec SingleBound2NN
((f : 2NN))
((lambda(self x s)
((SingleBound2NN-f self) x s))
: (-> SingleBound2NN Number Number Number)))
(define trigFunction1
(Cyclic2NN
(lambda(x s)
(* s (cos x)))))
(define quadraticFunction1
(SingleBound2NN
(lambda (x b)
(let ((x1 x))
(+ b (* x1 x1)))))
The defined procedures are strongly typed in the sense that:
> (SingleBound2NN? trigFunction1)
- : Boolean
#f
> (SingleBound2NN? quadraticFunction1)
- : Boolean
#t
All that remains is writing a macro to simplify specification.
In the general case, what you want is impossible due to how types are implemented in Racket. Racket has contracts, which are run-time wrappers that guard parts of a program from other parts. A function contract is a wrapper that treats the function as a black box - a contract of the form (-> number? number?) can wrap any function and the new wrapper function first checks that it receives one number? and then passes it to the wrapped function, then checks that the wrapped function returns a number?. This is all done dynamically, every single time the function is called. Typed Racket adds a notion of types that are statically checked, but since it can provide and require values to and from untyped modules, those values are guarded with contracts that represent their type.
In your function dispatcher, you accept a function f dynamically, at run time and then want to do something based on what kind of function you got. But functions are black boxes - contracts don't actually know anything about the functions they wrap, they just check that they behave properly. There's no way to tell if dispatcher was given a function of the form (-> number? number?) or a function of the form (-> string? string?). Since dispatcher can accept any possible function, the functions are black boxes with no information about what they accept or promise. dispatcher can only assume the function is correct with a contract and try to use it. This is also why define-type doesn't make a predicate automatically for function types - there's no way to prove a function has the type dynamically, you can only wrap it in a contract and assume it behaves.
The exception to this is arity information - all functions know how many arguments they accept. The procedure-arity function will give you this information. So while you can't dispatch on function types at run-time in general, you can dispatch on function arity. This is what case-lambda does - it makes a function that dispatches based on the number of arguments it receives:
(: dispatcher (case-> [-> Calculate-with-one-number Number Void]
[-> Calculate-with-two-numbers Number Number Void]))
(define dispatcher
(case-lambda
[([f : Calculate-with-one-number]
[arg : Number])
(do-something arg)]
[([f : Calculate-with-two-numbers]
[arg1 : Number]
[arg2 : Number])
(do-something-else arg1 arg2)]
[else 42]))