Reflection

Ki has some basic, but powerful support for reflection.

Basics

Reflection is accessed using :.

Types, sizes and bytes

struct Person {
    var name: string
    var age: u8
}

fn main() {
    var charlie: Person("Charlie", 33)

    echo("charlie is a {{charlie:type}}")
    echo("A Person is {{Person:size}} bytes")
    echo("charlie is {{charlie:type:size}} bytes")

    # `:bytes` gets an array of an instance's bytes
    var bytes: charlie:bytes

    for (n, byte in bytes) {
        echo("Byte {{n + 1}}: 0x{{hex(byte)}}")
    }
}

This program will print (on a 64-bit system):

charlie is a Person
A Person is 9 bytes
charlie is 9 bytes
Byte 1: 0xed
Byte 2: 0xe0
Byte 3: 0x20
Byte 4: 0x95
Byte 5: 0xb1
Byte 6: 0x06
Byte 7: 0x45
Byte 8: 0xc2
Byte 9: 0x21

More type information

In a type, the base can be accessed using :base:

type PersonCount(uint) {
    fn announce() {
        echo("I have counted {{:base}} people")
    }

    fn has_enough(needed: uint): (bool) {
        return(:base >= needed)
    }
}

Operator overloading

Ki uses reflection to implement operator overloading:

struct PersonStruct {
    var name: string
    var age: u8
}

type Person(PersonStruct) {
    fn :eq(p: &Person) {
        return(self.name == p.name && self.age == p.age)
    }

    fn :gt(p: &Person) {
        return(self.age > p.age)
    }

    fn :lt(p: &Person) {
        return(self.age < p.age)
    }

    fn :add(p: &Person): (Person) {
        return(Person("{{self.name and {{p.name}}", self.age + p.age))
    }
}

Overloadable operators

Containers

  • :iterate and next: Used with for-in loops
  • :in: Used for in checks
  • :getitem and :setitem: Used with []

Misc

  • :string: Returns a string representation
  • :hash: Returns an integer suitable for hashing
  • :enter and :exit: Creates a context for use with with

Comparisons

  • :eq, :ne, :lt, :lte, :gt, :gte: ==, !=, <, <=, >, >=
  • :eq: ==
  • :is: ===
  • :ne: !=
  • :isnot: !==
  • :lt: <
  • :lte: <=
  • :gt: >
  • :gte: >=

Like Python, the only related comparison operators are :eq and :ne. For example, :gte won't use :eq and :gt even if you override them; you have to override :gte specifically.

Math

  • :add: +
  • :sub: -
  • :mul: *
  • :matmul: @
  • :div: /
  • :rem: %
  • :exp: **
  • :and: &
  • :or: |
  • :xor: ^
  • :lshift: <<
  • :rshift: >>
  • :not: ~ (unary)
  • :neg: - (unary)
  • :pos: + (unary)

Constants

Reflection can be used when defining constants.

range DayOfWeek [1..7]

struct WeekDayStruct {
    var name: string
    var number: DayOfWeek
}

type WeekDay(WeekDayStruct) {
    pred is_weekend_day {
        return(self.number == 1 || self.number == 7)
    }
}

const WeekDays(WeekDay) {
    Sunday(:name, 1)
    Monday(:name, 2)
    Tuesday(:name, 3)
    Wednesday(:name, 4)
    Thursday(:name, 5)
    Friday(:name, 6)
    Saturday(:name, 7)
}

If you wanted, you could use :count instead of integer literals in WeekDays:

const WeekDays(WeekDay) {
    Sunday(:name, :count)
    Monday(:name, :count)
    Tuesday(:name, :count)
    Wednesday(:name, :count)
    Thursday(:name, :count)
    Friday(:name, :count)
    Saturday(:name, :count)
}

In this instance it's a little less accurate however. A WeekDay's ordinal shouldn't change depending on its ordering in WeekDays; it should be constant. A better example might be:

struct Weapon {
    var name: string
    var slot: uint
}

const Weapons(Weapon) {
    Fist(:name, :count)
    Chainsaw(:name, :count)
    Pistol(:name, :count)
    Shotgun(:name, :count)
    SuperShotgun(:name, :count)
    Chaingun(:name, :count)
    RocketLauncher(:name, :count)
    PlasmaRifle(:name, :count)
    BFG(:name, :count)
}

A Weapon's slot doesn't particularly matter; it just needs an index. const can provide that easily.