Language Tour

This tour introduces Kit's core concepts in a hands-on way. Each section includes runnable examples you can try yourself.

Comments

Comments in Kit start with # and continue to the end of the line:

# This is a comment
x = 42  # Comments can also appear after code

Doc Comments

Doc comments start with ## and provide structured documentation for modules, functions, and types. They're similar to Python docstrings or JSDoc comments.

## Calculate the factorial of a number.
##
## Computes n! using recursive multiplication.
## Returns 1 for n <= 1.
##
## Parameters:
##   n - A non-negative integer
##
## Returns:
##   The factorial of n
##
## Example:
##   factorial 5  # => 120
factorial = fn(n) =>
  if n <= 1 then
    1
  else
    n * factorial (n - 1)

Module-level doc comments appear at the top of a file, before the module declaration:

## Math utilities for Kit.
##
## This module provides common mathematical functions
## including factorial, fibonacci, and prime checking.
##
## Example:
##   import Math
##   Math.factorial 5  # => 120

module Math

## Check if a number is prime.
export is-prime? = fn(n) => ...

Values and Bindings

Kit uses = to bind values to names. Bindings are immutable by default:

# Bind values to names
name = "Kit"
age = 1
price = 19.99
active? = true

# String interpolation with ${...}
message = "Hello, ${name}!"
println message  # => Hello, Kit!

Heredocs

For multi-line strings, use heredocs with <<~DELIM ... DELIM. Common indentation is automatically stripped:

# Multi-line string with automatic indentation stripping
html = <<~HTML
  <div class="container">
    <h1>Welcome</h1>
    <p>Hello, world!</p>
  </div>
HTML

# Heredocs support interpolation
user = "Alice"
greeting = <<~END
  Dear ${user},

  Welcome to Kit!
END

# Great for SQL queries
query = <<~SQL
  SELECT id, name, email
  FROM users
  WHERE active = true
  ORDER BY name
SQL

Functions

Functions are defined with fn and use => for the body:

# Single-line function
double = fn(x) => x * 2

# Multi-parameter function
sum = fn(a, b) => a + b

# Call functions
println (double 5)     # => 10
println (sum 3 4)      # => 7

Functions are first-class values and can be passed around:

# Higher-order functions
apply-twice = fn(f, x) => f (f x)

result = apply-twice double 5
println result  # => 20

The Pipe Operators

Kit has two pipe operators for chaining function calls:

  • |> data-first (thread-first) - inserts value as the first argument
  • |>> data-last (thread-last) - inserts value as the last argument

Data-First Pipe (|>)

Most list functions in Kit expect the list as the last argument, but the data-first pipe inserts into the first position:

# Without pipes (nested calls)
result1 = fold (fn(a, x) => a + x) 0 (map double (filter (fn(x) => x > 2) [1, 2, 3, 4, 5]))

# With data-first pipe (readable chain)
result2 = [1, 2, 3, 4, 5]
  |> filter (fn(x) => x > 2)   # [3, 4, 5]
  |> map double                   # [6, 8, 10]
  |> fold (fn(a, x) => a + x) 0  # 24

Data-Last Pipe (|>>)

Use the data-last pipe when you want to insert the value as the last argument. This is useful for functions like String.join:

# String.join expects: join separator list
# With data-last pipe, the list goes in as the last argument
csv = ["apple", "banana", "cherry"]
  |>> String.join ", "
# => "apple, banana, cherry"

# Compare: x |> f y z  becomes  f x y z (x is first)
#          x |>> f y z becomes  f y z x (x is last)

Lists

Lists are ordered collections of elements of the same type:

# Create lists with square brackets
numbers = [1, 2, 3, 4, 5]
names = ["Alice", "Bob", "Carol"]
empty = []

# Common list operations
println (length numbers)  # => 5
println (head numbers)    # => 1
println (tail numbers)    # => [2, 3, 4, 5]
println (reverse numbers) # => [5, 4, 3, 2, 1]

# Add elements
with-zero = cons 0 numbers  # [0, 1, 2, 3, 4, 5]

Records

Records are collections of named fields:

# Create records with curly braces
person = {name: "Alice", age: 30, active?: true}

# Access fields with dot notation
println person.name   # => Alice
println person.age    # => 30

# Nested records
user = {
  name: "Bob",
  address: {
    city: "New York",
    zip: "10001"
  }
}
println user.address.city  # => New York

Pattern Matching

Pattern matching with match is Kit's primary control flow mechanism:

# Match on values
describe = fn(n) =>
  match n
  | 0 -> "zero"
  | 1 -> "one"
  | _ -> "many"

# Match on list structure
sum-list = fn(list) =>
  match list
  | [] -> 0
  | [x | rest] -> x + sum-list rest

println (sum-list [1, 2, 3])  # => 6

Algebraic Data Types

Define custom types with type:

# Simple enumeration
type Color = Red | Green | Blue

# Parameterized types with data
type Shape = Circle Float
           | Rectangle Float Float
           | Point

# Use pattern matching with ADTs
area = fn(shape) =>
  match shape
  | Circle r -> 3.14159 * r * r
  | Rectangle w h -> w * h
  | Point -> 0.0

shape = Rectangle 4.0 5.0
println (area shape)  # => 20.0

Kit includes built-in Option and Result types for handling optional values and errors:

# Option: Some a | None (for optional values)
find-user = fn(id) =>
  if id == 1 then
    Some "Alice"
  else
    None

match (find-user 1)
| Some name -> println "Found: ${name}"
| None -> println "Not found"

# Result: Ok a | Err e (for error handling)
safe-div = fn(x, y) =>
  if y == 0 then
    Err "division by zero"
  else
    Ok (x / y)

match (safe-div 10 2)
| Ok value -> println "Result: ${value}"
| Err msg -> println "Error: ${msg}"

Conditionals

Use if-then-else expressions:

absolute = fn(n) =>
  if n < 0 then
    -n
  else
    n

grade = fn(score) =>
  if score >= 90 then
    "A"
  else if score >= 80 then
    "B"
  else if score >= 70 then
    "C"
  else
    "F"

Map, Filter, Fold

The three core list operations for functional programming:

numbers = [1, 2, 3, 4, 5]

# Map: transform each element
doubled = map (fn(x) => x * 2) numbers
# => [2, 4, 6, 8, 10]

# Filter: keep elements matching a predicate
evens = filter (fn(x) => x % 2 == 0) numbers
# => [2, 4]

# Fold: reduce list to a single value
total = fold (fn(acc, x) => acc + x) 0 numbers
# => 15

# Chain them together
result = numbers
  |> filter (fn(x) => x > 2)
  |> map (fn(x) => x * x)
  |> fold (fn(acc, x) => acc + x) 0
# => 50  (9 + 16 + 25)

Iteration

Kit has a for loop for iterating over collections:

# Basic for loop
for x in [1, 2, 3] =>
  println "Number: ${x}"

# Generate ranges with range
for n in range 1 6 =>
  println n
# Prints: 1, 2, 3, 4, 5

# Pattern matching in for loops
pairs = [(1, "one"), (2, "two"), (3, "three")]
for (num, name) in pairs =>
  println "${num} is ${name}"

The for loop is syntactic sugar that desugars to each. You can also use each directly with pipes:

# Using each with pipes
[1, 2, 3] |> each (fn(x) => println x)

# Classic FizzBuzz
fizzbuzz = fn(n) =>
  if n % 15 == 0 then
    "FizzBuzz"
  else if n % 3 == 0 then
    "Fizz"
  else if n % 5 == 0 then
    "Buzz"
  else
    to-str n

range 1 16
  |> map fizzbuzz
  |> each println

For accumulating results while iterating, use fold instead:

# Sum numbers from 1 to 100
total = range 1 101 |> fold (fn(acc, n) => acc + n) 0
println total  # => 5050

Compile-Time Macros

Kit supports user-defined macros that expand at compile time. Macros let you extend the language with new control flow constructs, create DSLs, and generate code while preserving type safety.

# Define a macro with quasiquotation
macro unless cond then-val else-val =
  `(if (not $cond) then $then-val else $else-val)

# Use the macro like a function
result = unless((1 == 2), 42, 0)  # => 42

# Arithmetic macros
macro double x = `($x + $x)
macro square x = `($x * $x)

println double(5)   # => 10
println square(4)   # => 16

Macro syntax uses:

  • `(expr) — Quasiquote: creates a template expression
  • $name — Unquote: substitutes parameter value
  • $(expr) — Unquote expression: evaluates and substitutes
# More complex macro examples
macro when-val cond val = `(if $cond then $val else 0)

macro both? a b = `($a and $b)

# Nested macro usage
macro quad x = `(double(double($x)))

println quad(3)  # => 12

Testing

Kit has built-in support for testing:

# Define tests with the test keyword
test "addition works"
  assert-eq! (2 + 3) 5

test "list operations"
  nums = [1, 2, 3]
  assert-eq! (length nums) 3
  assert-eq! (head nums) 1
  assert-true! (contains? 2 nums)

Run tests with:

kit test my-tests.kit

For property-based testing, see kit-quickcheck.

Next Steps

Now that you've seen the basics, explore more of Kit:

  • Standard Library - Built-in modules and functions
  • List - List operations and transformations
  • String - String manipulation functions
  • Packages - Extend Kit with community packages