typocomath/src/interfaces/type.ts

67 lines
3.1 KiB
TypeScript

// Every typocomath type has some associated types; they need
// to be published as in the following interface. The key is the
// name of the type, and within the subinterface for that key,
// the type of the 'type' property is the actual TypeScript type
// we are associating the other properties to. Note the interface
// is generic with one parameter, corresponding to the fact that
// typocomath currently only allows types with a single generic parameter.
// This way, AssociatedTypes<SubType> can give the associated types
// for a generic type instantiated with SubType. That's not necessary for
// the 'undefined' type (or if you look in the `numbers` subdirectory,
// the 'number' type either) or any concrete type, but that's OK, the
// generic parameter doesn't hurt in those cases.
export interface AssociatedTypes<T> {
undefined: {
type: undefined
zero: undefined
one: undefined
nan: undefined
real: undefined
}
}
type AssociatedTypeNames = keyof AssociatedTypes<unknown>['undefined']
type ALookup<T, Name extends AssociatedTypeNames> = {
[K in keyof AssociatedTypes<T>]:
T extends AssociatedTypes<T>[K]['type'] ? AssociatedTypes<T>[K][Name] : never
}[keyof AssociatedTypes<T>]
export type ZeroType<T> = ALookup<T, 'zero'>
export type OneType<T> = ALookup<T, 'one'>
export type WithConstants<T> = T | ZeroType<T> | OneType<T>
export type NaNType<T> = ALookup<T, 'nan'>
export type RealType<T> = ALookup<T, 'real'>
// The global signature patterns for all operations need to be published in the
// following interface. Each key is the name of an operation (but note that
// the Dispatcher will automatically merge operations that have the same
// name when the first underscore `_` and everything thereafter is stripped).
// The type of each key should be an interface with two properties: 'params'
// whose type is the type of the parameter list for the operation, and
// 'returns' whose type is the return type of the operation on those
// parameters. These types are generic in a parameter type T which should
// be interpreted as the type that the operation is supposed to "primarily"
// operate on, although note that some of the parameters and/or return types
// may depend on T rather than be exactly T.
// So note that the example 're' below provides essentially the same
// information that e.g.
// `type ReOp<T> = (t: T) => RealType<T>`
// would, but in a way that is much easier to manipulate in TypeScript,
// and it records the name of the operation as 're' also by virtue of the
// key 're' in the interface.
export interface Operations<T> {
zero: {params: [WithConstants<T>], returns: ZeroType<T>}
one: {params: [WithConstants<T>], returns: OneType<T>}
nan: {params: [T | NaNType<T>], returns: NaNType<T>}
re: {params: [T], returns: RealType<T>}
}
type OpKey = keyof Operations<unknown>
export type OpReturns<Name extends OpKey, T> = Operations<T>[Name]['returns']
export type OpType<Name extends OpKey, T> =
(...args: Operations<T>[Name]['params']) => OpReturns<Name, T>
export type Dependencies<Name extends OpKey, T> = {[K in Name]: OpType<K, T>}