Language Basics
This guide covers the core features of the Mesh programming language. After reading this, you will understand how to work with variables, types, functions, pattern matching, control flow, the pipe operator, error handling, and modules.
Variables
Variables in Mesh are created with let bindings and are immutable by default:
fn main() do
let name = "Mesh"
let age = 30
let pi = 3.14
let active = true
println("Hello, ${name}!")
endYou can add type annotations with :: to be explicit about a variable's type:
fn main() do
let x :: Int = 42
let greeting :: String = "hello"
println("${x}: ${greeting}")
endType annotations are optional -- the compiler infers types from context. Use annotations when you want to be explicit or when the compiler needs a hint.
Since variables are immutable, you cannot reassign them. Instead, you create a new binding with the same name (shadowing):
fn main() do
let x = 1
let x = x + 1
println("${x}")
endBasic Types
Mesh has the following built-in types:
| Type | Description | Examples |
|---|---|---|
Int | Integer numbers | 42, 0, -5 |
Float | Floating-point numbers | 3.14, 0.5 |
String | Text strings | "hello", "Mesh" |
Bool | Boolean values | true, false |
String Interpolation
Strings support interpolation with ${}. Any expression inside the braces is evaluated and converted to a string:
fn main() do
let name = "Mesh"
let val = 42
println("Hello, ${name}!")
println("The answer is ${val}")
println("Double: ${val * 2}")
endType Inference
The Mesh compiler infers types from how values are used. You rarely need to write type annotations for local variables:
fn main() do
let x = 42 # inferred as Int
let name = "Mesh" # inferred as String
let flag = true # inferred as Bool
println("${x} ${name} ${flag}")
endBoolean Logic
Boolean values support and, or, and not operators:
fn main() do
let t = true
let f = false
if t and not f do
println("logic works")
end
endFunctions
Functions are declared with the fn keyword, followed by the name, parameters, and a do...end body:
fn add(a :: Int, b :: Int) -> Int do
a + b
end
fn greet(name :: String) -> String do
"Hello, ${name}!"
end
fn main() do
println("${add(10, 20)}")
println(greet("Mesh"))
endThe last expression in a function body is the return value -- there is no need for an explicit return keyword (though return is available for early exits).
One-Line Functions
For simple functions, you can use the concise = syntax:
fn double(x) = x * 2
fn square(x :: Int) -> Int = x * x
fn main() do
println("${double(21)}")
println("${square(6)}")
endMulti-Clause Functions
Functions can have multiple clauses that pattern match on their arguments, similar to Elixir:
fn fib(0) = 0
fn fib(1) = 1
fn fib(n) = fib(n - 1) + fib(n - 2)
fn to_string(true) = "yes"
fn to_string(false) = "no"
fn main() do
println("${fib(10)}")
println(to_string(true))
println(to_string(false))
endThe compiler tries each clause in order and uses the first one that matches.
Guard Clauses
Multi-clause functions can include when guards for additional conditions:
fn abs(n) when n < 0 = -n
fn abs(n) = n
fn classify(n) when n > 0 = "positive"
fn classify(n) when n < 0 = "negative"
fn classify(n) = "zero"
fn main() do
println("${abs(-5)}")
println(classify(10))
println(classify(-3))
println(classify(0))
endClosures
Anonymous functions (closures) are created with fn...end:
fn main() do
let factor = 3
let triple = fn(x :: Int) -> x * factor end
println("${triple(7)}")
println("${triple(10)}")
endClosures capture variables from their surrounding scope. There are two syntax forms:
- Arrow syntax for one-line closures:
fn x -> x * 2 end - Do-end syntax for multi-line closures:
fn x do ... end
fn main() do
let list = [1, 2, 3, 4, 5]
# Arrow syntax
let doubled = list |> map(fn x -> x * 2 end)
# Do-end syntax for multi-line bodies
let processed = map(list, fn x do
let doubled = x * 2
let incremented = doubled + 1
incremented
end)
println("${doubled}")
println("${processed}")
endPattern Matching
The case expression matches a value against patterns and executes the first matching branch:
fn describe(x :: Int) -> String do
case x do
0 -> "zero"
1 -> "one"
_ -> "other"
end
end
fn main() do
println(describe(0))
println(describe(1))
println(describe(42))
endThe _ pattern is a wildcard that matches anything.
Matching on Constructors
You can match on sum type constructors and destructure their contents:
type Color do
Red
Green
Blue
end
fn color_name(c :: Color) -> String do
case c do
Red -> "red"
Green -> "green"
Blue -> "blue"
end
end
fn main() do
let c = Red
println(color_name(c))
endMatching on Results
Pattern matching works naturally with Ok and Err result types:
fn safe_divide(a :: Int, b :: Int) -> Int!String do
if b == 0 do
return Err("division by zero")
end
Ok(a / b)
end
fn main() do
let r = safe_divide(10, 2)
case r do
Ok(val) -> println("Result: ${val}")
Err(msg) -> println("Error: ${msg}")
end
endSee the Error Handling section below for more on result types.
Control Flow
If/Else
The if/else expression evaluates a condition and runs the corresponding branch:
fn max(a :: Int, b :: Int) -> Int do
if a > b do
a
else
b
end
end
fn main() do
println("${max(10, 20)}")
endif is an expression in Mesh, so it returns a value. The else branch is optional when the result is not used.
For Loops
The for...in loop iterates over ranges and collections:
fn main() do
# Iterate over a range (0 through 4)
for i in 0..5 do
println("${i}")
end
endFor loops can also iterate over lists:
fn main() do
let names = ["Alice", "Bob", "Charlie"]
for name in names do
println("Hello, ${name}!")
end
endFilter Clauses
Add a when clause to filter elements during iteration:
fn main() do
let evens = for i in 0..10 when i % 2 == 0 do
i
end
for e in evens do
println("${e}")
end
endThe for expression with a body returns a list of the results, making it work like a list comprehension.
Map Iteration
Iterate over map entries with destructuring:
fn main() do
let m = Map.new()
let m = Map.put(m, 1, 10)
let m = Map.put(m, 2, 20)
let m = Map.put(m, 3, 30)
let vals = for {k, v} in m do
v
end
let total = List.length(vals)
println("${total}")
endWhile Loops
The while loop repeats its body as long as the condition is true:
fn main() do
while true do
println("loop ran")
break
end
println("after loop")
endBreak and Continue
Use break to exit a loop early and continue to skip to the next iteration:
fn main() do
# break exits the loop
while true do
println("before break")
break
end
println("after loop")
# continue skips the rest of the current iteration
let result = for x in [1, 2, 3, 4, 5] when x > 1 do
if x == 3 do
continue
end
x
end
for r in result do
println("${r}")
end
endPipe Operator
The pipe operator |> passes the result of the left-hand expression as the first argument to the right-hand function. It turns nested calls into readable left-to-right chains:
fn double(x :: Int) -> Int do
x * 2
end
fn add_one(x :: Int) -> Int do
x + 1
end
fn main() do
# Without pipes (nested, reads inside-out)
let a = add_one(double(5))
# With pipes (chained, reads left-to-right)
let b = 5 |> double |> add_one
println("${a}")
println("${b}")
endBoth a and b equal 11. The pipe version reads naturally: "take 5, double it, add one."
Pipes with Closures
Pipes work well with higher-order functions like map, filter, and reduce:
fn main() do
let list = [1, 2, 3, 4, 5]
let doubled = list |> map(fn x -> x * 2 end)
let filtered = doubled |> filter(fn x -> x > 4 end)
let sum = reduce(filtered, 0, fn acc, x -> acc + x end)
println("${sum}")
endError Handling
Mesh uses result types for error handling. A function that can fail returns T!E, where T is the success type and E is the error type:
fn safe_divide(a :: Int, b :: Int) -> Int!String do
if b == 0 do
return Err("division by zero")
end
Ok(a / b)
endOk(value)wraps a successful resultErr(error)wraps an error- The return type
Int!Stringmeans "returns anInton success or aStringerror on failure"
The Try Operator
The ? operator unwraps a result if it is Ok, or propagates the error if it is Err:
fn step1(x :: Int) -> Int!String do
if x < 0 do
return Err("negative input")
end
Ok(x * 2)
end
fn step2(x :: Int) -> Int!String do
if x > 100 do
return Err("too large")
end
Ok(x + 1)
end
fn pipeline(x :: Int) -> Int!String do
let a = step1(x)?
let b = step2(a)?
Ok(b)
end
fn main() do
let r = pipeline(10)
case r do
Ok(val) -> println("${val}")
Err(msg) -> println(msg)
end
endThe ? after step1(x) means: if step1 returns Ok(value), bind value to a and continue; if it returns Err(e), immediately return Err(e) from the current function. This keeps error handling concise without deeply nested pattern matches.
Handling Results with Pattern Matching
Use case to handle both success and error cases:
fn safe_divide(a :: Int, b :: Int) -> Int!String do
if b == 0 do
return Err("division by zero")
end
Ok(a / b)
end
fn main() do
let r = safe_divide(10, 0)
case r do
Ok(val) -> println("Result: ${val}")
Err(msg) -> println("Error: ${msg}")
end
endModules
Mesh organizes code into modules. The standard library provides built-in modules like String, List, and Map, accessed with dot notation:
import String
fn main() do
let n = String.length("test")
println("${n}")
endThe import statement makes a module available. You can also import specific functions directly:
from String import length
fn main() do
let n = length("test")
println("${n}")
endStandard Library Modules
Mesh includes several built-in modules:
| Module | Purpose | Example |
|---|---|---|
List | List operations | List.length(xs), List.get(xs, 0) |
Map | Key-value maps | Map.new(), Map.put(m, k, v) |
Set | Unique value sets | Set.new(), Set.add(s, v) |
String | String manipulation | String.length(s) |
Working with Lists
Lists are a core data structure. You can create them with literal syntax or the List module:
fn main() do
# List literal
let xs = [1, 2, 3]
let len = List.length(xs)
println("${len}")
# Access by index
let first = List.get(xs, 0)
println("${first}")
endWorking with Maps
Maps are key-value collections:
fn main() do
let m = Map.new()
let m = Map.put(m, 1, 10)
let m = Map.put(m, 2, 20)
let m = Map.put(m, 3, 30)
for {k, v} in m do
println("${k}: ${v}")
end
endNote that Map.put returns a new map -- all collections in Mesh are immutable.
What's Next?
You now have a solid foundation in the Mesh language. Continue with:
- Type System -- structs, sum types, deriving, and advanced type features
- Concurrency -- actors, message passing, supervision trees, and services
- Syntax Cheatsheet -- quick reference for all Mesh syntax