Generics
Native generic syntax is finally available! 🎉
Here are some examples of how this powerful feature can be used:
Standalone Type Syntax
import { type } from "arktype"
const const boxOf: Generic<[["t", unknown]], {
readonly box: "t";
}, {}>
boxOf = type("<t>", { box: "t" })
// hover me!
const const schrodingersBox: Type<{
box: {
cat: {
isAlive: boolean;
};
};
}>
schrodingersBox = const boxOf: Generic
<{
readonly cat: {
readonly isAlive: "boolean";
};
}, Type<{
box: {
cat: {
isAlive: boolean;
};
};
}, {}>>(a: validateObjectLiteral<{
readonly cat: {
readonly isAlive: "boolean";
};
}, {}, bindThis<{
readonly cat: {
readonly isAlive: "boolean";
};
}>>) => Type<...>
boxOf({ cat: { isAlive: "boolean" } })
Constrained Parameters
All syntax in parameters definitions and all references to generic args are fully-type safe and autocompleted like any builtin keyword. Constraints can be used just like TS to limit what can be passed to a generic and allow that arg to be used with operators like >
.
import { type } from "arktype"
const const nonEmpty: Generic<[["arr", unknown[]]], "arr > 0", {}>
nonEmpty = type("<arr extends unknown[]>", "arr > 0")
const const nonEmptyNumberArray: Type<of<number[], MoreThanLength<0>>>
nonEmptyNumberArray = const nonEmpty: Generic
<"number[]", Type<of<number[], MoreThanLength<0>>, {}>>(a: "number[]") => Type<of<number[], MoreThanLength<0>>>
nonEmpty("number[]")
Scoped
There is a special syntax for specifying generics in a scope:
import { scope } from "arktype"
const const types: Module<{
box: bindGenericToScope<GenericAst<[["t", unknown], ["u", unknown]], {
readonly box: "t | u";
}, "$", "$">, bootstrapAliases<{
readonly "box<t, u>": {
readonly box: "t | u";
};
readonly bitBox: "box<0, 1>";
}>>;
bitBox: {
box: 0 | 1;
};
}>
types = scope({
"box<t, u>": {
box: "t | u"
},
bitBox: "box<0, 1>"
}).export()
const const out: {
box: 0 | 1;
} | ArkErrors
out = const types: Module<{
box: bindGenericToScope<GenericAst<[["t", unknown], ["u", unknown]], {
readonly box: "t | u";
}, "$", "$">, bootstrapAliases<{
readonly "box<t, u>": {
readonly box: "t | u";
};
readonly bitBox: "box<0, 1>";
}>>;
bitBox: {
box: 0 | 1;
};
}>
types.bitBox({ box: 0 })
Builtins
Record is now available as a builtin keyword.
import { type } from "arktype"
const const stringRecord: Type<Record<string, string>>
stringRecord = type("Record<string, string>")
In addition to Record
, the following generics from TS are now available in ArkType:
- Pick
- Omit
- Extract
- Exclude
These can be instantiated in one of three ways:
Syntactic Definition
import { type } from "arktype"
const const one: Type<1>
one = type("Extract<0 | 1, 1>")
Chained Definition
import { type } from "arktype"
const const user: Type<{
name: string;
isAdmin: boolean;
age?: number;
}>
user = type({
name: "string",
"age?": "number",
isAdmin: "boolean"
})
// hover me!
const const basicUser: Type<{
name: string;
age?: number;
}>
basicUser = const user: Type<{
name: string;
isAdmin: boolean;
age?: number;
}>
user.pick("name", "age")
Invoked Definition
import { type } from "arktype"
const const unfalse: Type<true, Ark>
unfalse = type.keywords.Exclude("boolean", "false")
Generic HKTs
Our new generics have been built using a new method for integrating arbitrary external types as native ArkType generics! This opens up tons of possibilities for external integrations that would otherwise not be possible. As a preview, here’s what the implementation of Partial
looks like internally:
import { generic, Hkt } from "arktype"
const const Partial: Generic<[["T", object]], PartialHkt, {}>
Partial = generic(["T", "object"])(
args => args.T.partial(),
class PartialHkt extends Hkt<[object]> {
declare body: type Partial<T> = { [P in keyof T]?: T[P]; }
Make all properties in T optionalPartial<this[0]>
}
)
Recursive and cyclic generics are also currently unavailable and will be added soon.
For more usage examples, check out the unit tests for generics here.
This feature was built to be very robust and flexible. We’re excited to see what you do with it!