Ki

Ki is a compiled, static, strongly-typed programming language that focuses on safety.

Safe

  • Aggressive compile-time checks
  • No undefined behavior
  • No uninitialized variables
  • No NULL pointers
  • No pointers to uninitialized memory
  • No out-of-range values (including checked [un]signed integer overflow)
  • No aliasing
  • Immutability by default

Powerful

  • M:N parallelism support with fibers (coroutines)
  • 1:1 parallelism support with threads
  • Generators
  • Strong, extendable type system
    • Generics
    • Traits
    • Operator overloading
  • C FFI library

Fast

  • Compiled to machine code using LLVM
  • Static (types are defined at compile-time)
  • No built-in GC

Motivation

The major problem with software in our era is safety: values can be out of range, memory can be corrupted, control flow can be rerouted, execution can deadlock, etc. etc. etc.

The problem isn't programmers. Programmers are in general some of the most fastidious people there are. A single character out of place may render years of work into nothing but a bag of random bytes. A single math mistake may silently corrupt data or undermine cryptography. Many programmers are amazed that anything works at all.

The problem is that there are too many hazards and not enough tools for dealing with them. We have to use C or C++ in order to write the fastest software, and while the differences between the two are vast, what they share are myriad ways to invoke undefined behavior, dereference invalid pointers, write past the ends of our data structures, accept out-of-range values, and generally cause mayhem. Some prominent members of the tech community, and accomplished coders themselves, have publicly wondered whether or not it's even possible to write programs that don't have security bugs.

I don't know the answer. I do know we can make better tools so that, by giving up a very small amount of speed and capability, we can gain a wealth of robustness and expressive power.


Greetings

Ki is a very simple language to start out in. Here is "Hello World":

fn main() {
    echo("Hello World!")
}

Built-In Functions

Ki provides four built-in functions.

print prints formatted output to standard output. echo is just like print, but it appends a newline to the output. fail is just like echo except it throws an error to the outer scope. die is also just like echo except it exits the program after it finishes printing the message. There's no coming back once you die, so you'd better be sure!

fn add(x: int, y: int): (int) {
    return(x + y)
}

fn main() {
    print("5 + 8 = {{add(5, 8)}}\n")
    echo("5 + 8 = {{add(5, 8)}}")
    die("5 + 8 = {{add(5, 8)}}... oh I'm much too tired now; I quit")
}

Importing Modules

Importing modules is very simple.

import math

fn sqrt_minus_one(x: int): (int) {
    return(math.sqrt(x) - 1)
}

fn main() {
    echo("Root 9 - 1 = {{sqrt_minus_one(9)}}.  Super easy, right?")
}

Control Flow

Ki uses traditional control flow.

Conditionals

if/else should be very familiar.

fn main() {
    name: "Charlie"

    if (name == "Charlie") {
        echo("Welcome back!")
    }
    else if (name == "Hillary Clinton") {
        echo("Hello Madam!")
    }
    else {
        echo("Hello, nice to meet you ${name}!")
    }
}

Importantly, only boolean expressions are allowed. Other languages are more flexible: Python returns False for empty lists, None, and so on; C returns false for 0 and true for anything else, etc. Ki only handles boolean expressions in its conditionals.

Loops

Ki provides three loop types: for-in, while, and loop.

This is a while loop:

import math

fn print_until_seven() {
    var x: math.rand()

    while (x != 7) {
        echo("{{x}}")
        x = math.rand()
    }
}

Ki has two other types of loops: for-in and loop.

fn print_vowels(input: string) {
    for (r in input) {
        if (r in "aeiouy") {
            echo("Rune {{r}} is a vowel")
        }
    }
}

You can also enumerate what you're iterating over:

fn print_vowels(input: string) {
    for (i, r in input) {
        if (r in "aeiouy") {
            echo("Rune {{r}} at index {{i}} is a vowel")
        }
    }
}

Use caution when indexing into strings. In Ki, string types are sequences of 32-bit Unicode codepoints encoded in UTF-8, and indexing into them yields 32-bit Unicode codepoints. Ki has a few types devoted to strings:

  • rune: a 32-bit Unicode codepoint
  • str: Equivalent to C's const char[] and stored in read-only process space (usually)
  • string: Equivalent to C's char*, read/write, but not resizable
  • dynstring: No true C equivalent, fundamentally a char*, read/write, resizable

str, string, and dynstring are encoded using UTF-8.

Note: iteration is more complicated than it appears here; see here for more information.

fn print_vowels(input: string) {
    for (i, r in input) {
        if (r in "aeiouy") {
            echo("Rune {{r}} at index {{i}} is a vowel")
        }
    }
}
fn print_seven_times() {
    loop (7) {
        print("Hello!")
    }
}

loop takes an optional argument for how many times to iterate. If this is omitted, it will loop forever.

You can also use break to break out of these loops, and continue to start back at the top of the loop again.

With a little effort, you could achieve the functionality of any loop type with any other loop type. Regardless, Ki provides these three in order to enhance semantic clarity: for-in should be used to iterate over a series of values; while should be used to iterate until a certain condition is met; loop should be used to loop a certain number of times, including an infinite number of times.