Language tour

This tour is a quick, practical overview of mu’s core syntax and semantics.

Every snippet below is a complete program. Paste one into the playground to run it, and look any name up in the library reference.

Hello, mu

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

main()

Variables and control flow

x := 10
if x > 5 {
  print("x is big")
} else {
  print("x is small")
}

i := 0
while i < 3 {
  print(i)
  i = i + 1
}

Functions and closures

A closure captures the variable, not a copy of its value, so it and the enclosing function see each other’s writes:

fn make_counter() {
  n := 0
  return fn() {
    n = n + 1
    return n
  }
}

counter := make_counter()
print(counter())
print(counter())

That also means a callback can accumulate into a local of the function that created it, and a lambda can refer to itself:

fn totalise(items) {
  total := 0
  i := 0
  while i < len(items) {
    total = total + items[i]
    i = i + 1
  }
  return total
}

fact := nil
fact = fn(n) {
  if n <= 1 { return 1 }
  return n * fact(n - 1)
}

Lists and maps

nums := [1, 2, 3]
nums[1] = 42
print(nums)

user := {"name": "ada", "age": 42}
print(user.name)

Numbers

Integers are 64-bit. 3.14 is a decimal literal — exact fixed-point, not binary floating point — so the sum that goes wrong in most languages comes out right here:

print(0.1 + 0.2)   // 0.3    exactly
print(19.99 * 3)   // 59.97
print(3.14 + 6)    // 9.14   mixes with integers, either order

Division has to be told how many digits to keep, since a quotient like 22/7 does not terminate. A bare / picks a default; decimal.ratio lets you choose the scale and the rounding mode:

import "decimal"

print(1.0 / 3)                               // 0.333333333333333
print(decimal.ratio(22, 7, 5, "half_even"))  // 3.14286

Operators on your own types

A map carrying an __ops table defines what the operators mean for its own values, and __type names it:

Money := fn(cents) {
  return {
    "__type": "Money",
    "cents": cents,
    "__ops": {
      "+": fn(a, b) { return Money(a.cents + b.cents) },
      "str": fn(a) { return "$" + str(a.cents / 100) },
    },
  }
}

print(str(Money(500) + Money(250)))   // $7
print(type(Money(500)))               // Money

This is the only __dunder__ convention in mu, and it is opt-in. The host consults __ops only where an operation would otherwise have failed, so ordinary arithmetic pays nothing for it. Decimals are built entirely from this — the language itself knows nothing about them.

Errors are values

import "io"

data := io.read_file("missing.txt")
if type(data) == "ERROR" {
  print("read failed:", data)
}

Truthiness

Falsey values include false, nil, 0, empty strings, empty lists/maps, and empty buffers.

status := if "" { "truthy" } else { "falsey" }
print(status)

A type with an __ops["bool"] entry answers for itself, which is why 0.0 is falsey rather than “a non-empty map, therefore true”.

Next steps