Language Basics
Variables, control flow, functions, and data types
Equana is a language designed for scientific computing. It uses 1-based indexing, multiple dispatch, and a clean syntax that reads like math notation.
This tutorial covers the core syntax you need to start writing Equana code. Every code block below is interactive — edit it and run it to experiment.
Variables & Assignment
Variables are created by assigning a value with =. No declaration keyword is needed — just pick a name and assign:
x = 42
name = "Alice"
pi_approx = 3.14159
println(x)
println(name)
println(pi_approx)Variables are dynamically typed — you can reassign to a different type at any time:
x = 10
println(x)
x = "now a string"
println(x)Numbers
Equana has two primary numeric types: Int64 (integers) and Float64 (floating-point numbers). Arithmetic works as you'd expect:
# Integer arithmetic
a = 10 + 3
b = 10 - 3
c = 10 * 3
d = 10 / 3
e = 10 % 3
f = 2 ^ 10
println(a)
println(b)
println(c)
println(d)
println(e)
println(f)Special numeric values are available:
println(Inf)
println(-Inf)
println(NaN)
println(1.0 / 0.0)
println(0.0 / 0.0)The nothing value represents the absence of a value:
x = nothing
println(x)
println(x == nothing)Booleans & Comparisons
Boolean values are true and false. Comparison operators return booleans:
# Comparison operators
println(3 > 2)
println(3 < 2)
println(3 == 3)
println(3 != 4)
println(3 >= 3)
println(3 <= 2)Logical operators combine boolean values:
# Logical AND, OR, NOT
println(true && false)
println(true || false)
println(!true)
# Combining comparisons
x = 15
println(x > 10 && x < 20)Strings
Create strings with double quotes. Use ${} for interpolation and + for concatenation:
greeting = "Hello"
name = "World"
# Concatenation
println(greeting + ", " + name + "!")
# String interpolation
age = 28
println("${name} is ${age} years old")
println("2 + 2 = ${2 + 2}")For more on strings — template literals, escape sequences, and string functions — see the Working with Strings tutorial.
Arrays
Arrays use square brackets with comma-separated values. Indexing is 1-based (the first element is at index 1):
arr = [10, 20, 30, 40, 50]
# Access elements (1-based)
println(arr[1])
println(arr[3])
# Length
println(length(arr))Numeric arrays are backed by typed memory (NDArrays) — you can update elements in place:
arr = [1, 2, 3]
arr[2] = 99
println(arr)Non-numeric arrays (like strings) are growable — use push to append:
fruits = ["apple", "banana"]
push(fruits, "cherry")
println(fruits)Slicing
Use ranges to extract sub-arrays:
arr = [10, 20, 30, 40, 50]
# Slice from index 2 to 4
println(arr[2:4])
# Every other element
println(arr[1:2:5])Tuples
Tuples are fixed-size collections that can hold mixed types. Create them with parentheses:
t = (1, "hello", 3.14)
# Access by index (1-based)
println(t[1])
println(t[2])
println(t[3])
println(length(t))Destructure tuples into individual variables:
point = (3.0, 4.0)
(x, y) = point
println(x)
println(y)Ranges
Ranges represent sequences of numbers. The syntax is start:stop or start:step:stop:
# Basic range
r = 1:5
println(r)
println(length(r))
# Range with step
r2 = 0:2:10
println(r2)
# Check if a value is in a range
println(includes(1:10, 5))
println(includes(1:10, 15))Ranges are commonly used for iteration and array slicing (shown in later sections).
Control Flow
if / else if / else
Conditional branching uses if, else if, and else with curly-brace blocks:
x = 15
if x > 20 {
println("large")
} else if x > 10 {
println("medium")
} else {
println("small")
}for Loops
Iterate over ranges, arrays, or any collection with for ... in:
# Loop over a range
for i in 1:5 {
println(i)
}# Loop over an array
fruits = ["apple", "banana", "cherry"]
for fruit in fruits {
println("I like ${fruit}")
}Use enumerate to get both the index and value:
colors = ["red", "green", "blue"]
for pair in enumerate(colors) {
(i, color) = pair
println("${i}: ${color}")
}break & continue
break exits a loop early. continue skips to the next iteration:
# Find the first multiple of 7
for i in 1:100 {
if i % 7 == 0 {
println("First multiple of 7: ${i}")
break
}
}# Print only odd numbers
for i in 1:10 {
if i % 2 == 0 {
continue
}
println(i)
}switch / case
Pattern match on values with switch:
day = 3
switch day {
case 1 {
println("Monday")
}
case 2 {
println("Tuesday")
}
case 3 {
println("Wednesday")
}
default {
println("Another day")
}
}Cases can match multiple values:
x = 6
switch x % 3 {
case 0 {
println("divisible by 3")
}
case 1, 2 {
println("not divisible by 3")
}
}try / catch
Handle errors gracefully with try and catch:
try {
x = 1 / 0
println(x)
} catch e {
println("Error: " + e.message)
}Functions
Named Functions
Functions use function r = name(params) { } syntax. The return value is assigned to the return variable (r by convention):
function r = square(x) {
r = x * x
}
function r = greet(name) {
r = "Hello, ${name}!"
}
println(square(5))
println(greet("Alice"))Functions can return multiple values using tuple syntax:
function (q, r) = divmod(a, b) {
q = int64(a / b)
r = a % b
}
(quotient, remainder) = divmod(17, 5)
println("17 / 5 = ${quotient} remainder ${remainder}")Arrow Functions
Arrow functions provide a concise syntax for short functions:
double = x -> x * 2
add = (a, b) -> a + b
println(double(21))
println(add(3, 4))Arrow functions with multi-line bodies use curly braces:
classify = x -> {
if x > 0 {
"positive"
} else if x < 0 {
"negative"
} else {
"zero"
}
}
println(classify(5))
println(classify(-3))
println(classify(0))Higher-Order Functions
Pass functions as arguments to map, filter, and reduce:
arr = [1, 2, 3, 4, 5]
# Square each element
println(map(arr, x -> x ^ 2))
# Keep only even numbers
println(filter(arr, x -> x % 2 == 0))
# Sum all elements
println(reduce(arr, (a, b) -> a + b, 0))Comments & Block Syntax
Single-line comments start with #:
# This is a comment
x = 42 # inline comment
println(x)Blocks use curly braces { }. Alternatively, you can use do ... end or begin ... end as equivalent block delimiters:
# These are equivalent:
function r = add_braces(a, b) {
r = a + b
}
function r = add_doend(a, b) do
r = a + b
end
println(add_braces(1, 2))
println(add_doend(3, 4))Natural-Language Syntax
Equana has several syntax features designed to make code read more like natural language. These are all optional — you can always use the conventional forms — but they can make code more expressive and approachable.
let and be
The keyword let is purely cosmetic — it has no semantic effect but improves readability. The keyword be is an alias for =, and equals works too:
# All of these are equivalent:
x = 42
let x = 42
let x be 42
let x equals 42
x equals 42
println(x)This extends to typed declarations. The keyword in is an alias for ::
# These are equivalent:
# x: Float64 = 3.0
# let x: Float64 = 3.0
let x in Float64 be 3.0
println(x)
println(typeof(x))mapsto — Arrow Function Alias
The keyword mapsto is an alias for ->, letting you write lambdas that read like math notation ("x maps to x squared"):
# These are equivalent:
square = x -> x^2
let square be x mapsto x^2
println(square(5))Combined with let and be, function definitions read like English:
let double be x mapsto 2 * x
let add be (a, b) mapsto a + b
println(double(21))
println(add(3, 4))Bracket-Free Function Calls
Any known function can be called without parentheses — just write the function name followed by its argument:
# These are equivalent:
println(sqrt(16.0))
println sqrt(16.0)
# Calls chain right-to-left:
# sin cos 0.0 means sin(cos(0.0))
println(sin(cos(0.0)))Implicit Multiplication
A numeric literal followed by a variable or expression is treated as multiplication — no * needed:
x = 5
println(2x)
println(3x^2)
pi_approx = 3.14159
r = 10.0
println(2pi_approx * r)Infix Function Calls
Built-in binary functions like add, sub, mul, div, mod, pow, and the comparison/logical operators (eq, neq, lt, gt, le, ge, and, or) are automatically available as infix operators with precedences matching their symbolic counterparts:
# These work out of the box — no @infix needed
println(3 add 4)
println(10 sub 3)
println(5 mul 6)
println(10 div 3)
println(2 pow 8)
println(3 lt 5)
println(true and false)For your own functions, use the @infix(n) decorator to make them infix, where n is the precedence (higher binds tighter):
@infix(10)
function r = cross(a, b) {
r = a * b
}
# Standard call
println(cross(3, 4))
# Infix call — same result
println(3 cross 4)You can also override the default precedence of a built-in infix operator by redeclaring it with @infix(N). For example, to make add bind tighter than mul:
# By default: add has precedence 10, mul has 12
# So `2 add 3 mul 4` means `2 add (3 mul 4)` = 14
println(2 add 3 mul 4)
# Override add to precedence 14 (tighter than mul)
@infix(14)
function r = add(a, b) {
r = a + b
}
# Now `2 add 3 mul 4` means `(2 add 3) mul 4` = 20
println(2 add 3 mul 4)Method Syntax
Any function can be called as a method on its first argument using dot notation:
function r = double(x) {
r = x * 2
}
# Standard call
println(double(5))
# Method syntax — same result
println(5.double())begin / end Blocks
Beyond do ... end, you can also use begin ... end as block delimiters. All three styles are interchangeable:
function result = increment(x) begin
result = x + 1
end
println(increment(10))Combining It All
These features combine naturally. Here's the same logic written in conventional and natural-language style:
# Conventional style:
f = x -> x^2
result = f(3) + f(4)
println(result)
# Natural-language style:
let f be x mapsto x^2
let result be (f(3)) add (f(4))
println(result)Useful Built-in Functions
Here's a quick reference of commonly used built-in functions:
# Math
println(sqrt(16.0))
println(abs(-5))
println(min(3, 7))
println(max(3, 7))# Arrays
arr = [3, 1, 4, 1, 5, 9]
println(sort(arr))
println(reverse(arr))
println(sum(arr))
println(min(arr))
println(max(arr))# Type inspection
println(typeof(42))
println(typeof(3.14))
println(typeof("hello"))
println(typeof(true))
println(typeof(nothing))Summary
Quick Reference
| Concept | Syntax |
|---|---|
| Assignment | x = 42 |
| String interpolation | "value is ${x}" |
| Array | [1, 2, 3] |
| Tuple | (1, "a", 3.0) |
| Range | 1:10 or 1:2:10 |
| if/else | if cond { } else { } |
| for loop | for i in 1:10 { } |
| switch | switch val { case 1 { } default { } } |
| Named function | function r = f(x) { r = x * 2 } |
| Arrow function | f = x -> x * 2 |
| Natural assignment | let x be 42 or x equals 42 |
| Mapsto lambda | let f be x mapsto x^2 |
| Bracket-free call | sin x instead of sin(x) |
| Implicit multiply | 2x instead of 2 * x |
| Infix call | a add b (built-ins) or @infix(N) for custom functions |
| Method syntax | x.double() instead of double(x) |
| Comment | # comment |
What's Next
- Types & Multiple Dispatch — Define structs, abstract types, typed functions, and operator overloading
- Working with Strings — Deep dive into string manipulation and formatting