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