#readme
The Yz compiler is work in progress. All examples and features described here represent the intended design.
// Factorial in Yz
factorial: { n Int
n > 0 ? { n * factorial(n - 1) }, // ? is conditional: true-branch, false-branch
{ 1 }
}
print("${factorial(5)}") // prints 120The initial goal for the language was to explore how much can be expressed without using keywords (which has to be admitted is silly goal on its own), so far the language has only 4 keywords and a handful of reserved symbols, (see Yz Language Design for more details).
Yz is a programming language built around a single construct: the block of code (boc). Variables, functions, objects, types, modules, concurrent execution are all blocks.
A block is a series of expressions between { and }, and the same block can act as data, be executed, or both:
// As data
person: {
name: "Alice"
age: 30
}
print("${person.name}")
// As behaviour
greet: {
name String
print("Hello, ${name}!")
}
greet("World")
// As both
counter: {
count: 0
increment: { count = count + 1 }
}
counter.increment()
print("${counter.count}")// Single line comment
/*
Multiline comment
*/// Long form declaration
message String = "Hello"
// Short form with type inference
name: "World"
// Type declaration without initialization
age IntBoth "double" and 'single' quotes create strings; they are interchangeable:
a: "Hello"
b: 'Hello' // identicalUse ${...} inside a string literal for interpolation:
name: "Alice"
greeting: "Hello, ${name}!" // "Hello, Alice!"
greeting: 'Hello, ${name}!' // same// A simple block
{
a: 1
b: 2
a + b // Last expression(s) are the "return value"
}calculator: {
a: 0
b: 0
add: {
a + b
}
}Use () to execute a block:
result: calculator() // Executes the block
calculator.a = 5 // Access variables
calculator.b = 3 // Access variables
sum: calculator.add() // Call methodsBlock variables can be accessed using . notation and modified before execution:
greet: {
message String = "Hello"
to_whom: "World"
print("${message}, ${to_whom}")
}
// Change variables before execution
greet.to_whom = "Everybody"
greet() // prints "Hello, Everybody!"
// Variables can be accessed even after execution
greet.message // returns "Hello"In Yz there is no separate concept of "parameter" or "return value" — they are just variables. A variable declared without a value is a required input; one declared with a value is optional (defaults apply). The last expression(s) in the body are the output.
greet: {
message String // required — caller must provide
to_whom: "World" // optional — defaults to "World"
"${message}, ${to_whom}!" // return value
}
greet("Hello") // "Hello, World!"
greet("Hi", "Alice") // "Hi, Alice!"
greet(to_whom: "Bob", message: "Hey") // named args, any orderBecause parameters are fields, they are accessible before and after the call:
greet.to_whom = "Everyone"
greet("Hello") // "Hello, Everyone!"
greet.message // "Hello" — readable after callThe last N expressions are the return values — no return keyword needed:
swap: {
a String
b String
b // second-to-last — first return value
a // last — second return value
}
x, y = swap("hello", "world") // x = "world", y = "hello"A #(...) is a boc signature — the type and interface of a boc. It is useful for declaring boc parameters, and structural constraints:
hello_world #(String) // a boc that returns a StringThe block body has to be assigned to use the boc:
// Boc declaration: signature + body together (no = needed)
greet #(message String, to_whom String, String) {
"${message}, ${to_whom}!"
}
// Boc expanded form: = separates signature from body; body re-declares params
greet #(message String, to_whom String, String) = {
message String
to_whom String
"${message}, ${to_whom}!"
}
// Declare signature only — assign implementation later
greet #(message String, to_whom String, String)
greet = {
message String
to_whom String
message
}The concurrency model is an adaptation of the Behaviour-Oriented Concurrency model.
Every block call is asynchronous. The value is resolved by the time it is used:
// These run concurrently
fetch_user("alice")
fetch_orders("alice")
user: fetch_user("alice")
print(user) // blocks here only if fetch_user hasn't completed yetA boc does not complete until all bocs it spawned have completed:
process_data: {
// Both operations start concurrently
img: fetch_image("123")
usr: fetch_user("alice")
// process_data will not complete until create_profile completes
create_profile(img, usr)
}Every value in Yz is a protected concurrent resource. Only one running boc can hold a resource at a time; all others queue behind it. Resources are acquired atomically: a boc that needs multiple resources gets all of them at once or waits until it can.
Account: {
balance Int
balance+= #(amount Int) { balance = balance + amount }
balance-= #(amount Int) { balance = balance - amount }
}
// transfer acquires src and dst atomically before running
transfer #(src Account, dst Account, amount Int) {
src.balance >= amount ? {
src.balance-=(amount)
dst.balance+=(amount)
}, {
print("insufficient funds")
}
}
main: {
alice: Account(100)
bob: Account(0)
transfer(alice, bob, 30) // acquires alice + bob
transfer(bob, alice, 10) // waits — bob is taken by the first transfer
}Two bocs that need different resources run in parallel automatically. Two bocs that share a resource serialize in the order they were spawned. No locks, no synchronized, no async/await.
// Numbers
n Int = 42
m : -1
pi Decimal = 3.14
// Strings
message String = "Hello"
name: "World" // Type inferred
// Booleans
flag Bool = true
// Arrays
numbers [Int] = [1, 2, 3]
words: ["hello", "world"]
// Dictionaries
ages [String:Int] = ["Alice": 30, "Bob": 25]
// Bocs
greet: { msg String
"Hello ${msg}"
}
hi: { 42 }Uppercase names define new types:
Person : {
name String
age Int
greet: {
print("Hello, I'm ${name}")
}
}alice: Person(name: "Alice", age: 30)
// or
bob: Person("Bob", 25)
alice.greet() // "Hello, I'm Alice"// Explicit signature
Point #(x Int, y Int) {
// `secret` is not part of the
// signature and thus "private"
secret: {
sqrt(x * x + y * y)
}
}Single uppercase letters represent generic types:
Box: {
data T // T is generic
}
int_box: Box(42) // T becomes Int
string_box: Box("Hi") // T becomes Stringidentity: {
value T
value // Returns whatever type was passed in
}
number: identity(Int, 42) // number: Int
text: identity("hi") // text: StringConstraints are inferred from usage by default:
printable: {
value T // T must have a print method — inferred from usage below
value.print()
}
Person: {
name String
print: {
print("My name is ${name}")
}
}
printable(Person("Yz"))
printable("oh oh") // error: String doesn't have a `print` blockConstraints can also be declared explicitly as an optional annotation:
serialize: {
value T Serializable // T must satisfy the Serializable interface
value.to_json()
}An explicit constraint is checked at the call site; an inferred constraint is checked at usage inside the body. Both forms are valid.
Type variants provide sum type functionality:
Option: {
T
Some(value T),
None()
}
maybe_number: Option.Some(42)
nothing: Option.None()
// Pattern matching with match
result: match maybe_number {
Some => "Got value: ${maybe_number.value}"
}, {
None => "No value"
}Result: {
T, E
Ok(value T),
Err(error E)
}
NetworkResponse: {
Success(data String),
Failure(error String),
Timeout()
}
handle_response: {
response NetworkResponse
match response {
Success => print("Data: ${response.data}")
}, {
Failure => print("Error: ${response.error}")
}, {
Timeout => print("Request timed out")
}
}Yz uses structural typing - types match based on structure, not names:
Point: {
x Int
y Int
}
Vector: {
x Int
y Int
}
process_coordinates: {
coords #(x Int, y Int) // Any type with x, y Int fields
coords.x + coords.y
}
p: Point(3, 4)
v: Vector(1, 2)
process_coordinates(p) // Works - Point has x, y Int
process_coordinates(v) // Works - Vector has x, y Int// Type declaration
a [Int]
// initialization
a = [1, 2, 3]
// decl + init
a [Int] = [1, 2, 3]
// short declr + init
a : [1, 2, 3]
// empty decl + init
a [Int] = [Int]() // Is an empty array
// short declr + init
a : [Int]() // empty array of ints
// Generic
a [T] = [1, 2, 3]
a : [T]()
// Array operations
a << 'Hello' // or a.add('Hello')
print(a[0]) // access element 0 of the array
a[0] = "Hola"// Type
[Key_Type : Value_Type]
// declaration
d [String:Int]
// initialization
d = [ "one": 1, "two": 2]
// decl + init
e [String:Int] = ["one":1, "two":2]
// short decl + init
f : ["one":1, "two":2 ]
// empty
g2 [String:Int] = [String:Int]()
// short decl + init empty
g1 : [String:Int]()
// generic + initialization
g3 [K:V] = [String:Int]()
g4 [K:V]
g4["hello":1]
// Dictionary access returns Optional(V)
d : [ 1 : 2, 3: 4] // [Int: Int]
d[1] // Some(2)
d[5] // None()Yz uses Result and Option types for error handling:
divide: {
a Int
b Int
b == 0 ? {
Result.Err("Division by zero")
}, {
Result.Ok(a / b)
}
}
result: divide(10, 2).or_else({
error Result.Error
print("Error: ${error}")
0 // Default value
})process_file: {
filename String
// read_file returns `Result(String,Error)`
read_file(filename)
// .and_then is a Result method
.and_then { content String; parse_content(content) }
.and_then { data Data; validate_data(data) }
.or_else { error Error; print("Processing failed: ${error}") }
}// ? is a method on Bool — true-branch, false-branch
max: {
a Int
b Int
a > b ? { a }, { b }
}
// match — first branch whose condition is true runs
describe: {
n Int
match {
n < 0 => "negative"
}, {
n == 0 => "zero"
}, {
n > 0 => "positive"
}
}
// match on a type variant
x Option(String) = ...
match x {
Option.Some => print("Got ${x.value}")
}, {
Option.None => print("Nothing")
}
// iteration
1.to(10).each { i Int; print("${i}") }
names: ["Alice", "Bob", "Charlie"]
names.each { name String; print("Hello, ${name}!") }
while({ current > 0 }, { current = current - 1 })
// return, break, continue work as in most languages
check: {
age Int
age < 21 ? { return }
print("Welcome")
}When the only argument to a method is a block literal, the parentheses can be omitted. Write the block directly after the method name on the same line:
// Both are identical
list.filter({ item Int; item > 10 })
list.filter { item Int; item > 10 }
// Chaining
[1, 2, 3, 10, 20]
.filter { n Int; n > 5 }
.each { n Int; print(n) }The { must appear on the same line as the method name (a newline causes ASI to insert a semicolon, and the block becomes a separate statement).
When boc name is non-word, we can invoke it without . ident () as long as it has at least one parameter.
Example: {
// the "<<" variable is a non-word identifier
<< : {
n Int
printnln(n)
}
}
e : Example()
e << 1 // same as e.<<(1)An info string is a boc body delimited by backticks placed immediately before a definition. Its content is valid Yz — compiled but never executed — and can be used at compile time to augment or extend the language:
`
compile_time: [JSON, Embed]
`
Movie : {
title String
`json: "release_date"`
year Int
`json: "ignore"`
internal_id String
`embed: "icon.png"`
image Image
}compile_time lists the extensions to run on the annotated boc. Each extension reads only the variable it owns (json, embed, …). Referenced names are resolved at compile time — a typo inside the definition is a compile error.
Counter: {
count Int = 0
increment: {
count = count + 1
}
decrement: {
count = count - 1
}
get: { count }
}
counter: Counter()
counter.increment()
counter.increment()
print(counter.get()) // prints 2Concurrent transfers. Some share accounts (serialized), others don't (parallel). No locks written anywhere.
Account: {
balance Int
balance+= #(amount Int) { balance = balance + amount }
balance-= #(amount Int) { balance = balance - amount }
}
transfer #(src Account, dst Account, amount Int) {
src.balance >= amount ? {
src.balance-=(amount)
dst.balance+=(amount)
}, {
print("insufficient funds: need ${amount}, have ${src.balance}")
}
}
main: {
alice: Account(100)
bob: Account(0)
carol: Account(50)
daniel: Account(50)
transfer(alice, bob, 30) // alice + bob
transfer(bob, alice, 10) // serialized after above
transfer(daniel, carol, 20) // run's freely
}Tree: {
T
Empty(),
Node(value T, left Tree(T), right Tree(T))
insert: {
value T
match {
Empty() => Node(value, Empty(), Empty())
}, {
Node() => value < self.value ? {
Node(value, left.insert(value), right)
} {
Node(value, left, right.insert(value))
}
}
}
}
tree: Tree.Empty()
tree = tree.insert(5).insert(3).insert(7)
`
compile_time: [http.HttpServer]
port: 8080
`
Server: {
`route: "/hello"`
hello #(r Request, w Response) {
Response(body: "Hello, World!")
}
`route: "/users/{id}"`
get_user #(r Request, w Response) {
id: r.params.id
user: find_user(id)
Response(body: "User: ${user.name}")
}
`route: "/users"; method: http.Post`
create_user #(r Request, w Response) {
Response(body: "Created")
}
}
server: Server()
server.listen()In Yz, almost anything can be part of an identifier, except for the following reserved words and symbols:
break
continue
return
match
=>
:
`
'
"
[]
{}
()
, ; . #
= might be part of an identifier, but there are also = and == operators.
docs/— Additional documentation, design notes, and implementation decisions.compiler/— Go implementation of the Yz compiler. Includes the lexer, parser, AST, lowerer, and code generator. Emits Go source and invokesgo buildto produce binaries.spec/— Language specification split across numbered sections (01–11), describing syntax, semantics, and type system.