15. Metaprogramming

Metaprogramming blurs the line between code and data by allowing programs to treat other programs or themselves as data. This enables the creation of more dynamic and flexible code that can adapt to different situations at runtime or compile-time. Metaprogramming techniques can be used to generate code, modify existing code, or create domain-specific languages (DSLs) that are tailored to specific problem domains.

Mojo supports serveral metaprogramming features, such as type_of.

15.1. type_of

The type_of function in Mojo is a built-in function that returns the type of a given expression or variable at compile-time. This can be useful for debugging, type checking, and implementing generic programming patterns.

    fn multiply(first: Scalar, second: type_of(first)) -> type_of(first):
        return first * second

Usage:

    var a: Int16 = 5
    var b: Int16 = 10
    var result_i: Int16 = multiply(a, b)
    print("Result Int:", a, "times", b, "is", result_i) # Output: Result Int: 5 times 10 is 50

    var x: Float16 = 3.5
    var y: Float16 = 2.0
    var result_f: Float16 = multiply(x, y)
    print("Result Float", x, "times", y, "is", result_f) # Output: Result Float 3.5 times 2.0 is 7.0

    # var z: Float32 = 4.0
    # print("Result Mixed: ", x, "times", z, "is", multiply(x, z)) # Uncommenting this will cause a type error due to mismatched types

The type_of function also takes expressions as arguments:

    var m: type_of(3*2.5) # Type of m is FloatLiteral[7.5]
    m = 7.5
    print("Value is: ", m)
    #m = 7.1 # Uncommenting this will cause compile-time error due to type mismatch 

In the above example, we use type_of on a multiplcation expression that evaluates to a result at compile-time. This result is part of the type of the variable. So when we try to assign a different value other than the result of the previous expression, it results in a type error.

15.2. conforms_to

The conforms_to function in Mojo is a built-in function that checks if a given type conforms to a specified trait or type constraint. This can be useful for ensuring that types meet certain requirements or for implementing generic programming patterns.

trait Animal: ...
trait CanFly: ...

struct Eagle(Animal, CanFly): 
    fn __init__(out self): ...

struct Monkey(Animal):
    fn __init__(out self): ...

fn ability[T: Animal](x: T):
    @parameter 
    if conforms_to(T, CanFly):
        print("It can fly!")
    else:
        print("It cannot fly!")

Usage:

    ability(Eagle()) # Prints: It can fly!
    ability(Monkey()) # Prints: It cannot fly!

In the above example, we define a trait CanFly and implement it for the Eagle struct. The ability function then checks if the type T conforms to the CanFly trait using the conforms_to function. Depending on whether the type conforms to the trait, it prints whether the animal can fly or not.

conforms_to can be particularly useful when creating libraries and frameworks, as it allows developers to enforce certain type constraints and ensure that their code behaves correctly with different types, while still maintaining flexibility and reusability.

15.3. trait_downcast

The trait_downcast function in Mojo is a built-in function that allows you to downcast a value of a trait type to a concrete type that implements that trait. This can be useful when you have a value of a trait type and you need to access methods or properties specific to the concrete type. It can be used in combination with conforms_to to safely downcast only when the type conforms to the desired trait.

trait Animal: ...
trait CanFly: 
    fn fly(self):
        print("Flying high!")

struct Eagle(Animal, CanFly): 
    fn __init__(out self): ...

struct Monkey(Animal):
    fn __init__(out self): ...

fn ability[T: Animal](x: T):
    @parameter 
    if conforms_to(T, CanFly):
        trait_downcast[CanFly](x).fly()
    else:
        print("It cannot fly!")

Usage:

    ability(Eagle()) # Prints: It can fly!
    ability(Monkey()) # Prints: It cannot fly!

In the above code listing, we define a trait CanFly and implement it for the Eagle struct. The ability function checks if the type T conforms to the CanFly trait using the conforms_to function. If it does, it uses trait_downcast to downcast the value to the CanFly trait type and calls the fly method. This allows us to access the specific behavior of the Eagle struct when we know it conforms to the CanFly trait.