Equana

Back to Tutorials

Language Basics

Variables, control flow, functions, and data types

Run AllReset

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:

Code [1]Run
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:

Code [2]Run
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:

Code [3]Run
# 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:

Code [4]Run
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:

Code [5]Run
x = nothing
println(x)
println(x == nothing)

Booleans & Comparisons

Boolean values are true and false. Comparison operators return booleans:

Code [6]Run
# 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:

Code [7]Run
# 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:

Code [8]Run
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):

Code [9]Run
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:

Code [10]Run
arr = [1, 2, 3]
arr[2] = 99
println(arr)

Non-numeric arrays (like strings) are growable — use push to append:

Code [11]Run
fruits = ["apple", "banana"]
push(fruits, "cherry")
println(fruits)

Slicing

Use ranges to extract sub-arrays:

Code [12]Run
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:

Code [13]Run
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:

Code [14]Run
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:

Code [15]Run
# 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:

Code [16]Run
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:

Code [17]Run
# Loop over a range
for i in 1:5 {
  println(i)
}
Code [18]Run
# 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:

Code [19]Run
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:

Code [20]Run
# Find the first multiple of 7
for i in 1:100 {
  if i % 7 == 0 {
    println("First multiple of 7: ${i}")
    break
  }
}
Code [21]Run
# Print only odd numbers
for i in 1:10 {
  if i % 2 == 0 {
    continue
  }
  println(i)
}

switch / case

Pattern match on values with switch:

Code [22]Run
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:

Code [23]Run
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:

Code [24]Run
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):

Code [25]Run
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:

Code [26]Run
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:

Code [27]Run
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:

Code [28]Run
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:

Code [29]Run
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 #:

Code [30]Run
# 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:

Code [31]Run
# 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:

Code [32]Run
# 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 ::

Code [33]Run
# 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"):

Code [34]Run
# 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:

Code [35]Run
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:

Code [36]Run
# 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:

Code [37]Run
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:

Code [38]Run
# 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):

Code [39]Run
@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:

Code [40]Run
# 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:

Code [41]Run
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:

Code [42]Run
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:

Code [43]Run
# 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:

Code [44]Run
# Math
println(sqrt(16.0))
println(abs(-5))
println(min(3, 7))
println(max(3, 7))
Code [45]Run
# Arrays
arr = [3, 1, 4, 1, 5, 9]
println(sort(arr))
println(reverse(arr))
println(sum(arr))
println(min(arr))
println(max(arr))
Code [46]Run
# Type inspection
println(typeof(42))
println(typeof(3.14))
println(typeof("hello"))
println(typeof(true))
println(typeof(nothing))

Summary

Quick Reference

ConceptSyntax
Assignmentx = 42
String interpolation"value is ${x}"
Array[1, 2, 3]
Tuple(1, "a", 3.0)
Range1:10 or 1:2:10
if/elseif cond { } else { }
for loopfor i in 1:10 { }
switchswitch val { case 1 { } default { } }
Named functionfunction r = f(x) { r = x * 2 }
Arrow functionf = x -> x * 2
Natural assignmentlet x be 42 or x equals 42
Mapsto lambdalet f be x mapsto x^2
Bracket-free callsin x instead of sin(x)
Implicit multiply2x instead of 2 * x
Infix calla add b (built-ins) or @infix(N) for custom functions
Method syntaxx.double() instead of double(x)
Comment# comment

What's Next

Workbench

Clear
No variables in workbench

Next Steps