Examples

A collection of XS code showing off various features.

FizzBuzz

The classic, with pattern matching.

fn fizzbuzz(n) {
  match (n % 3, n % 5) {
    (0, 0) => "FizzBuzz"
    (0, _) => "Fizz"
    (_, 0) => "Buzz"
    _      => "{n}"
  }
}

fn main() {
  for i in 1..=100 {
    println(fizzbuzz(i))
  }
}

Generators

Lazy sequences with fn* and yield.

fn* fibonacci() {
  var a = 0
  var b = 1
  loop {
    yield a
    let tmp = a
    a = b
    b = tmp + b
  }
}

fn main() {
  for n in fibonacci() {
    if n > 100 { break }
    println(n)
  }
}

Tagged blocks

User-defined control structures.

-- retry a block up to n times
tag retry(n) {
  var attempts = 0
  loop {
    try {
      let result = yield
      return result
    } catch e {
      attempts = attempts + 1
      if attempts >= n {
        throw "failed after {n} attempts: {e}"
      }
    }
  }
}

retry(3) {
  http.get("https://flaky-api.com")
}

-- measure execution time
tag timed() {
  import time
  let start = time.clock()
  let result = yield
  println("took {time.clock() - start}s")
  return result
}

timed() {
  heavy_computation()
}

Error handling with effects

Recoverable errors without exceptions or Result types.

effect Fail {
  fn fail(msg)
}

fn parse_int(s) {
  for ch in s.chars() {
    if not ch.is_digit() {
      perform Fail.fail("not a number: {s}")
    }
  }
  return s.parse_int()
}

fn main() {
  let result = handle {
    let n = parse_int("42")
    let m = parse_int("abc")
    n + m
  } {
    Fail.fail(msg) => {
      println("error: {msg}")
      resume(0)
    }
  }
  println(result)  -- 42
}

Structs and operator overloading

Custom types with operators.

struct Vec2 { x, y }

impl Vec2 {
  static fn new(x, y) {
    return Vec2 { x: x, y: y }
  }

  fn +(self, other) {
    return Vec2 { x: self.x + other.x, y: self.y + other.y }
  }

  fn magnitude(self) {
    return sqrt(self.x * self.x + self.y * self.y)
  }
}

fn main() {
  let a = Vec2.new(3.0, 4.0)
  let b = Vec2.new(1.0, 2.0)
  let c = a + b
  println("magnitude: {c.magnitude()}")
}

List comprehensions

Concise collection transformations.

-- squares
let squares = [x * x for x in 0..10]
println(squares)  -- [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

-- filtered
let evens = [x for x in 0..20 if x % 2 == 0]
println(evens)    -- [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]

-- map comprehension
let sq = #{x: x * x for x in [1, 2, 3, 4, 5]}
println(sq)       -- {1: 1, 2: 4, 3: 9, 4: 16, 5: 25}

-- nested with destructuring
let pairs = [(x, y) for x in 0..3 for y in 0..3 if x != y]

Inline C

Drop to C when you need raw performance.

fn fast_hash(data) {
  inline c {
    uint64_t h = 0x525201;
    const char *s = xs_to_cstr(args[0]);
    while (*s) h = h * 31 + *s++;
    xs_return_int(h);
  }
  return 0  -- fallback for interpreter mode
}

fn main() {
  println(fast_hash("hello world"))
}

Nurseries

Structured concurrency with guaranteed cleanup.

let pipe = channel()
var output = []

nursery {
  -- producer
  spawn {
    for i in 1..=5 {
      pipe.send(i * 10)
    }
  }

  -- consumer
  spawn {
    for i in 0..5 {
      output.push(pipe.recv())
    }
  }
}

-- all tasks complete before we get here
println(output)  -- [10, 20, 30, 40, 50]

Reactive signals

Observable values that propagate changes.

let count = signal(0)

let doubled = derived(fn() { count.get() * 2 })

count.subscribe(fn(val) {
  println("count changed to {val}")
})

count.set(5)
println(doubled.get())  -- 10

count.set(10)
println(doubled.get())  -- 20