Type Manipulation

Inspired by the TypeScript Type Manipulation Handbook.

Type

Like in Zig, THP has the Type datatype, which represents a Type at compile time. Those can be created, inspected, manipulated with THP code at compile time.

Type identity

fun Identity(Type t) -> Type {
╰╴No statement matched
return t } Type str_1 = String Type str_2 = Identity(String) assert_eq(str_1, str_2) Identity(String) name = "John"
thp

@typeName

The builtin @typeName returns the name of a type as a string.

val name = @typeName(Int)
╰╴No statement matched
assert_eq("Int", name)
thp
@typeName :: (Type) -> String
╰╴No statement matched
thp

@TypeOf

The builtin @TypeOf returns the type of a value

val value = 322
╰╴No statement matched
val value_type = @TypeOf(value) assert_eq(Int, value_type)
thp
@TypeOf :: (Any) -> Type
╰╴No statement matched
thp

@ReturnType

The builtin @ReturnType returns the return type of a function.

fun to_float(Int) -> Float {
╰╴No statement matched
// ... } val return_type = @ReturnType(to_float) assert_eq(Float, value_type)
thp
@ReturnType :: [A](Fun(Any, A)) -> A
╰╴No statement matched
thp

Generics

fun identity[A](A arg) -> A {
╰╴No statement matched
return A } fun logging_identity[A](Array[A] arg) -> Array[A] { print(arg.length) return arg } fun get_property[Type, Key](Type obj, Key key) -> Key where Key -> @KeyOf(Type) {}
thp

@KeyOf

Given a map type, the @KeyOf builtin returns the names of its keys as a Sum type.

That sum type contains every key as a string.

struct Point = {
╰╴No statement matched
Int age, Int salary, } type PointKeys = @KeyOf(Point) type ExpectedType = "age" | "salary" assert_eq(PointKeys, ExpectedType)
thp

Indexed Access Types

struct Person {
╰╴No statement matched
Int age, String name, Bool alive, } type Age = @PropertyType(Person, "age") Age = Int
thp
type NumberArray = Array[Person]
╰╴No statement matched
type PersonT = NumberArray[Int] PersonT = Person type Age = NumberArray[Int]["age"] Age = Int
thp

Conditional Types

interface Animal {
╰╴No statement matched
live: () -> Void } interface Dog -> Animal { woof: () -> Void } type Example1 = if @Extends(Dog, Animal) { Int } else { String } Example1 = Int type Example2 = if @Extends(RegExp, Animal) { Int } else { String } Example2 = String fun NameOrId(Type t) Type { if @Extends(t, Int) { return IdLabel } else { return NameLabel } } fun create_label[A](A id_or_name) -> NameOrId(A) where A -> Int|String { // ... } val a = create_label("hello") a :: NameLabel val b = create_label(322) b :: IdLabel fun Flatten(Type t) Type { if @TypeOf(t) == Array { t[Int] } else { t } } type Str = Flatten(Array[String]) Str = String type Num = Flatten(Number) Num = Number
thp

Mapped Types

fun OptionFlags(Type t) Map[String, Bool] {
╰╴No statement matched
return Map[@Property(t), Bool] } struct Person { String name Int age } fun capitalize(String s) = ... fun Getter(Type t) Map[String, (Void) -> Type] { for #(type, type_name) in @Properties(t) { @Map(Fun([], type), "get" ++ capitalize(prop)) } } type PersonGetters = Getter(Person) // @Properties(Person) -> [#(String, "name"), #(Int, "age")] // #(String, "name") // @Map(Fun([], String), "get" ++ capitalize("name")) // @Map(Fun([], String), "getName") // { () -> (String) getName, () -> (Int) getAge, } fun Setter(Type t) Map[String, Fun([Type], Void)] { for #(type, type_name) in @Properties(t) { @Map(Fun([type], Void), "set" ++ capitalize(prop)) } } type PersonSetters = Setter(Person) PersonSetters = { (String) -> (Void) setName, (Int) -> (Void) setAge, }
thp