Syntax
If you can read TypeScript, you can read Lumina. The keywords are words (function, struct, match), types follow a colon, and lambdas use =>. There are no semicolons, no let/const noise and no lifetimes.
A complete program
struct Point {
x: Integer
y: Integer
public function sum(self): Integer = self.x + self.y
public function scaled(self, k: Integer): Point = Point { x: self.x * k, y: self.y * k }
}
function greet(name: String, greeting: String = "Hello"): String = `{greeting}, {name}!`
function classify(n: Integer): String =
if n < 0 { "negative" } else if n == 0 { "zero" } else { "positive" }
function main(console: Console): Integer {
p = Point { x: 3, y: 4 }
console.println(`sum = {p.scaled(2).sum()}`)
console.println(greet("Ada"))
console.println(greet("Linus", "Hi"))
numbers = [1, 2, 3, 4, 5, 6]
evens = numbers.filter(n => n % 2 == 0)
squares = evens.map(n => n * n)
console.println(`squares = {squares}`)
mut total = 0
for i in 1..=5 {
total += i
}
console.println(`total = {total}, {classify(-2)}`)
return 0
}
sum = 14
Hello, Ada!
Hi, Linus!
squares = [4, 16, 36]
total = 15, negative
Here is what is going on.
Variables
name = "Ada" // immutable, type inferred as String
mut counter = 0 // mutable
age: Integer = 36 // explicit type annotation
A binding without mut cannot be reassigned. This is the default, so the common case needs no keyword. Mutability covers the details.
Types
The primitive types have full names: Integer (64-bit), Float, String, Boolean, Char and Void. Collections are generic: Array<Integer>, written as a literal [1, 2, 3]. A tuple type is written (Integer, Integer). A ? after a type makes it optional (User?), and ! attaches an error type (Integer!ParseError). See Errors & Types.
Char is its own type. It does not silently convert to Integer.
Functions
function add(a: Integer, b: Integer): Integer {
return a + b
}
function add2(a: Integer, b: Integer): Integer = a + b // expression body
- The return type goes after
:. - A function whose body is a single expression can use
= expr. The linter (W127) suggests this form when a block contains only areturn. - Parameters can have defaults:
greeting: String = "Hello". maindeclares the capabilities it needs as parameters.function main(console: Console)is the program entry point. Capabilities explains why I/O arrives this way.
Structs and methods
Methods live inside the struct body. They take self (read-only) or mut self (may modify fields):
struct Counter {
public count: Integer
public function increment(mut self): Void {
self.count = self.count + 1
}
}
Fields and methods are private by default. If you read c.count from outside a struct whose field is not public, you get error E1301, and the message tells you to write public count: .... You build a struct with a literal, Point { x: 3, y: 4 }.
Strings
Backtick strings interpolate any expression in {}:
greeting = `Hello {name}, you have {items.length()} items`
Ordinary "double quoted" strings are plain literals. Common methods include length(), split(), substring() and toInteger(). The last one returns an Integer?, because parsing can fail.
Control flow
if/else if/elseis an expression, sox = if c { a } else { b }works.for i in 1..=5iterates an inclusive range;1..5excludes the end.for item in arrayiterates an array.while condition { ... }works as usual.matchis an expression with exhaustiveness checking. See Errors & Types.
Lambdas and collections
evens = numbers.filter(n => n % 2 == 0)
squares = evens.map(n => n * n)
Lambdas use =>, as in TypeScript. Arrays have map, filter, push, length and indexing (xs[0]). Indexing is bounds-checked, and an out-of-range index stops the program with an error message instead of reading garbage.
Tuples and destructuring
function minMax(xs: Array<Integer>): (Integer, Integer) {
mut lo = xs[0]
mut hi = xs[0]
for x in xs {
if x < lo { lo = x }
if x > hi { hi = x }
}
return (lo, hi)
}
function norm1({ x, y }: Point): Integer = x + y
(lo, hi) = minMax([4, 9, 1, 7]) // 1..9
A function can return several values as a tuple, and you can destructure a struct right in the parameter list.
Optional chaining
city = u.address?.city ?? "unknown"
?. walks through optional fields, and ?? supplies a default. Both are explained in Errors & Types.
Next: Mutability