Quick Start¶
This guide covers the essential features of Onion to get you productive quickly.
Variables and Types¶
Onion is statically typed. Local val / var declarations can include a type annotation, or omit it when an initializer is present:
// Type annotation
val name: String = "Alice"
val age: Int = 30
val price: Double = 19.99
// Type inference (local only)
val count = 42 // Inferred as Int
var greeting = "Hello" // Inferred as String
Control Flow¶
If-Else¶
While Loop¶
For Loop¶
Foreach Loop¶
val names: java.util.List[String] = ["Alice", "Bob", "Charlie"]
foreach name: String in names {
println("Hello, " + name)
}
Select Statement (Pattern Matching)¶
val score: Int = 85
select score {
case 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100:
println("Grade: A")
case 80, 81, 82, 83, 84, 85, 86, 87, 88, 89:
println("Grade: B")
case 70, 71, 72, 73, 74, 75, 76, 77, 78, 79:
println("Grade: C")
else:
println("Grade: F")
}
Functions¶
Basic Function¶
def greet(name: String): String = "Hello, " + name + "!"
val message: String = greet("World")
println(message)
Lambda Expressions¶
// Lambda syntax: (params) -> { body }
val add: (Int, Int) -> Int = (x: Int, y: Int) -> { return x + y; }
val result: Int = add.call(5, 3) // 8
println(result)
Function with Multiple Parameters¶
Lists and Maps¶
val colors = ["red", "green", "blue"]
val ages = ["alice": 30, "bob": 25]
println(colors[0]) // "red"
println(colors.size) // 3
println(ages["alice"]) // 30
foreach color: String in colors {
println(color)
}
Pipelines are built in, so you rarely need an index:
val odds = [1, 2, 3, 4, 5].filter { n => n % 2 == 1 } // [1, 3, 5]
val tens = odds.map { n => n * 10 } // [10, 30, 50]
The standard library takes and returns lists. Arrays exist for talking to Java —
main(args: String[]), byte[] — and use .length where a list uses .size.
Classes and Objects¶
Basic Class¶
class Person {
val name: String
var age: Int
public:
def this(n: String, a: Int) {
this.name = n
this.age = a
}
def greet: String = "Hello, I'm " + this.name
}
val person: Person = new Person("Alice", 30)
println(person.greet)
Key points:
- Fields are accessed via this.field
- Members are private by default
- Use public: to mark public members
- def this declares a constructor
Inheritance¶
interface Logger {
def log(message: String): void
def count(): Int
}
class BasicLogger conforms Logger {
var n: Int
public:
def this { this.n = 0 }
def log(message: String): void {
this.n = this.n + 1
println(message)
}
def count(): Int = n
}
// `forward` auto-implements the Logger interface by delegating to `delegate`.
class PrefixLogger conforms Logger {
forward val delegate: Logger
public:
def this(delegate: Logger) {
this.delegate = delegate
}
}
Syntax:
- class Child extends Parent - extends a class
- class Impl conforms Interface - implements an interface
- class Multi extends Parent conforms Interface - both
- forward val m: Interface - auto-implement Interface by delegating to m
Java Interoperability¶
Onion has seamless access to Java libraries:
import {
java.io.File;
javax.swing.JFrame;
javax.swing.JButton;
}
val frame: JFrame = new JFrame("My Window")
frame.setSize(400, 300)
frame.setVisible(true)
val file: File = new File("data.txt")
if file.exists {
println("File exists!")
}
Common Java Classes¶
// Math operations
val random: Double = Math::random()
val sqrt: Double = Math::sqrt(16.0)
// String operations
val upper: String = "hello".toUpperCase()
val length: Int = "hello".length
// Parsing
val num: Int = JInteger::parseInt("42")
Exception Handling¶
try {
val value: Int = JInteger::parseInt("not a number")
println(value)
} catch e :NumberFormatException {
println("Invalid number format: " + e.getMessage)
}
Type Casting¶
Use the as keyword for type casting. When chaining with method calls, use parentheses:
val random: Double = Math::random()
val randomInt: Int = (random * 100) as Int
val obj: Object = "Hello"
val str: String = obj as String
// When chaining method calls, parentheses are required
val btn: Object = getButton()
val text: String = (btn as JButton).getText()
Module System¶
// Import Java classes
import {
java.util.ArrayList;
java.util.HashMap;
java.io.File;
}
// Use fully qualified names without import
val list: java.util.ArrayList[String] = new java.util.ArrayList[String]()
Shapes — reading messy data¶
This is the part of Onion that is not like the languages above. A shape clause
describes how text and a record correspond, and both directions come from that one
description:
record Pt(x: Int, y: Int)
shape text = re"(-?\d+),(-?\d+)"
shape doc = json
law roundtrip(p: Pt) { Pt::text().parse(Pt::text().print(p)).get() == p }
Reading returns an Outcome — a value, or every reason there is not one:
val o = Pt::text().parse("3,x")
if o.isOk() { println(o.get().x()) }
else { println(o.describe()) }
// <input>:1: : expected match of /(-?\d+),(-?\d+)/, found 3,x
A failure knows where it came from and what it wanted. null cannot tell you either.
And reading many lines keeps the ones that failed instead of dropping them:
val each = file"points.txt".eachLine(Pt::text())
println(Outcome::values(each).size + " read, " + Outcome::defects(each).size + " not")
That law is not a comment — the compiler runs it during the build. See the
Shapes guide.
Next Steps¶
- Shapes - The part that is different: boundaries,
Outcome, reversibility - Language Guide - Deep dive into language features
- Examples - More complete program examples
- Tools Reference - Learn about compiler options