Skip to main content

Aleph 101

This guide walks you through Aleph’s core features, allowing you to jump in and start writing code. No prior knowledge of Aleph is required — though we assume you already know how to code.

Aleph is:

  • Interpreted and dynamic
  • Strongly typed at runtime
  • Familiar to users of Python, JavaScript, C++, and Julia
  • Easy to get started with — no boilerplate, just code

Get Started

Mandatory "Hello, world!":

print("Hello, world!") //-> Hello, world!

Statements, Scopes, and Comments

Aleph uses semicolons (;) as statement separators. As in JavaScript, they can be omitted at the end of a line.

var x = "Hello Aleph"; print(x) //-> Hello Aleph

Scopes are delimited using curly brackets ({}), as in C-style languages.

{
var a = 1
print(a)
}

{
var a = 2
print(a)
}

Aleph supports C-style comments:

// A single line of comment

/*
This is a multiline comment.
*/

Variables and Types

Aleph uses var for local variable declarations and global for global ones.

var x = 42
var name = "Aleph"
global counter = 0

Variables are dynamically typed at declaration, but not dynamically re-typed. Once a type is assigned, it’s fixed.

var value = 3.14;
value = 1.0;
value = "oops"; //-> Error: Error: "Unable to find appropriate'=' operator." With parameters: (real, const string)

Five primitive types are supported:

var a_boolean = true     //-> true
var a_string = "foo" //-> foo
var an_integer = 5 //-> 5
var a_real = 3.14 //-> 3.14
var a_complex = 1 + 2.4i //-> 1 + 2.4i

Conversion functions start with to_:

integer("42") //-> 42
real("3.15") //-> 3.15
integer(56.2) //-> 56

Variables are copied by default. References must be explicitly used (by prefixing the variable name with &).

var a = 4                //-> 4
var b = a; // Copy //-> 4
a = 6 //-> 6
b //-> 4

var c = 4 //-> 4
var &d = c; // Reference //-> 4
c = 6 //-> 6
d //-> 6

Null values exist but are not encouraged. To check for null values use is_var_null() function:

global g_var;
g_var.is_var_null() //-> true

Resource management and behaviour

Aleph does not have a garbage collector, instead it follows the pattern of "resource acquisition is initialization" or RAII. This gives Aleph data types well defined deterministic lifetimes. When a variable is declared (and given a type) memory is allocated for it. The memory is automatically released when the object goes out of scope (unless it is a global variable).

{
var a = 1;
global b = 2;
}
print(b)
print(a) // -> error, object never existed in this scope.

Control Flow

if, else, switch

var x = 2

if (x > 10) {
print("Large")
}
else if(x>5 && x <=10)
{
print("Medium")
}
else
{
print("Small")
}

switch (x) {
case (1) {
print("One")
break
}
case (2) {
print("Two")
break
}
default {
print("Other")
}
}

for, while, break

// C-style for loop
for (var i = 0; i < 5; ++i) {
print(i)
}
// Python-style ranged for
for (i : [10, 20, 30]) {
print(i)
}
// While loop
var n = 3;
while (n > 0) {
print(n)
--n
}
// Breaking out - supported by all loops
for (i : [1, 2, 3, 4, 5]) {
if (i == 3) {
break
}
print(i)
}

Collections and containers

Strings

Strings are concatenated using +. Many types support a string() method for typical string representation.

"foo" + "bar"          //-> foobar
"One " + 1.string() //-> One 1
Timer().string() //-> Timer(elapsed=4.167 µs)

Strings support indexing, reverse indexing, and a Python-like slice operator:

var s = "Hello, world!" //-> Hello, world!
s[3] //-> l
s[-2] //-> d
s[1:4] //-> ell

\n represents new lines.

Aleph also supports interpolated strings using ${...}.

var x = 10;
"The value is: ${x} units. It is ${x > 5 ? "more" : "less"} than five." //-> The value is: 10 units. It is more than five.

Lists

Aleph supports heterogenous, mutable lists:

var things = [1, "two", 3.0]        //-> [1, two, 3]
things[1] //-> two
things.push_back(4)
things.front() //-> 1
things.back() //-> 4
things.insert_at(2, "foo")
things.erase_at(2)
things.erase(3.0) //-> true
"How many ones: ${things.count(1)}" //-> How many ones: 1

var numbers = [4,2,5,1] //-> [4, 2, 5, 1]
numbers.sort() //-> [1, 2, 4, 5]
numbers //-> [1, 2, 4, 5]
numbers.reverse() //-> [5, 4, 2, 1]
numbers //-> [5, 4, 2, 1]
numbers == [5,4,2,1] //-> true
numbers.extend([9, 10]) //-> [5, 4, 2, 1, 9, 10]
numbers[-1] //-> 10
numbers[1::2] //-> [4, 1, 10]

Ranges

Aleph supports range literals for generating sequences of integers. The syntax is: [start .. end] where:

  • start: first integer in the sequence
  • end: exclusive upper bound (the sequence stops just before this value)
var result = 0
for(i: [1..6]) {
result += i
}
result //-> 15

You can also slice ranges like lists:

[0..10][::3] //-> [0, 3, 6, 9]

Maps

Associative maps with string keys can also be used:

var m = ["foo": 3, "bar": 6] //-> [<bar, 6>, <foo, 3>]
m["foo"] //-> 3
m.insert(MapPair("k",6))
m.keys() //-> [bar, foo, k]
m.values() //-> [6, 3, 6]
m.items() //-> [<bar, 6>, <foo, 3>, <k, 6>]
m.get("not_in_map", 0) //-> 0

Sets

Unique sets of values can be created for primitive types only.

var s = Set([1,3,5,1,3]) //-> [5, 3, 1]
s.insert(3) //-> [5, 3, 1]
s //-> [5, 3, 1]
s.contains(5) //-> true

Pairs

var p = Pair(1,2)                        //-> <1, 2>
"First: ${p.first}, Second: ${p.second}" //-> First: 1, Second: 2
p == Pair(1,2) //-> true

Summary

Here is a summary of available methods for collections and containers:

MethodListMapStringSet
back
capacity
clear
contains
count
empty
erase
erase_at
extend
front
get
index
index_max
index_min
insert
insert_at
is_sorted
max
min
pop_back
push_back
replicate
reserve
resize
reverse
reversed
size
sort
sorted

Functions

Use def to define functions. Argument types are optional but recommended.

Use return to stop and return the value, otherwise the last statement of the function is implicitly returned.

// Without argument types
def greet(name) {
"Hello ${name}"
}

// With argument types
def add(integer a, integer b) {
return a + b
}

Use fun to define anonymous functions (aka lambdas).

var square = fun(x) { return x * x; }

var greet = fun(name) { print("Hello, " + name); }
greet("Aleph")

// Lambdas can be passed as arguments
def apply_twice(f, val) {
return f(f(val))
}

// Use [] to capture variables in the parent scope (a la C++):
var my_val = 3
var add = fun[my_val](integer x) { return my_val + x; }
add(5)

Function arguments are passed by reference:

def assign_arg(integer arg1) {
arg1 = 5
}

var my_var = 4
assign_arg(my_var)

Functions and methods are equivalent - all functions can be invoked by using the first argument before the . operator:

var sentence = "My name is Bob"  //-> My name is Bob
replace(sentence, "Bob", "John") //-> My name is John
sentence.replace("Bob", "John") //-> My name is John

Use bind to create a new function with some arguments fixed ahead of time. The underscore (_) acts as a placeholder for values you will supply later when calling the bound function, and the resulting function’s arity equals the number of placeholders you left.

def add(integer x, integer y) {
return x + y
}

var add2 = bind(add, 2, _) // first arg fixed, second arg comes later
add2(4) //-> 6

You can pre-fill any position and even leave several placeholders:

def sum(integer x, integer y, integer z) {
return x + y + z
}

var a_sum = bind(sum, 2, _, 3) // middle arg is provided later
a_sum(4) //-> 9

Math operations

Many math operations are available:

2**3        //-> 8
cos(3.14) //-> -0.99999873172754
sinh(1+2i) //-> -0.489056259041294 + 1.40311925062204i
round(4.56) //-> 5
abs(-4.0) //-> 4
log10(2.0) //-> 0.301029995663981

Dynamic objects

Quickly define objects on-the-go:

var obj = DynamicObject()
obj.say_hello = fun() { "Hello, ${this.name}" }
obj.name = "Aleph" //-> Aleph
obj.say_hello() //-> Hello, Aleph

Classes

Simple classes can be created - no inheritance support.


class MyClass {
var value

def MyClass(v) {
this.value = v
}

def get_value() {
return this.value + 4
}
}

var x = MyClass(5)
"Variable x has a value of ${x.get_value()} and is of type ${x.type_name()}" //-> Variable x has a value of 9 and is of type MyClass

Monkey patching

You can add new methods to existing types:

def string::shout() { this.to_uppercase() + "!" }
"hello".shout() //-> HELLO!

Errors

Errors are typically thrown at runtime, similar to Python or JavaScript. Try to write safe code and use control flow to validate inputs.

To throw an exception, use the throw() method. Any object can be thrown, but we recommend the usage of RuntimeError. Use try/catch/finally to control exception flow.

try {
throw(RuntimeError("unknown condition met!"))
print("Never reached!")
} catch (err) {
print(err.what())
print(get_type_info(err).name)
} finally {
print("this is the end.")
}

Include other files

Other Aleph files can be included - so code can be organized and split between different files:

include "my_file2.aleph"
include "path/to/another_file.aleph"

Path must be a static string and cannot be computed at runtime.

Timing

There are utilities to get the current epoch, or benchmark your code:

epoch()             //-> 1755280515
epoch_ms() //-> 1755280515891

var timer = Timer() //-> Timer(elapsed=11.667 µs)
var fn = fun () { for(i:[0..100]) { i + 1; } }
timer.elapsed() //-> 371.375 µs

// Or:
time(fn) //-> 3.290 ms

// Even better:
benchmark(fn, 10) //-> BenchmarkResult(total=32.288 ms, mean=3.229 ms, best=3.220 ms, worst=3.247 ms, stddev=7.382 µs)

Introspection

Get and check type information:

var type_info = get_type_info("hello")
type_info.name
type_match("hello", "world")
type_match("hello", 3)
help(string) // online help

Numbers and Operations

Aleph supports four numeric types: bool, integer, real, and complex. The language's numeric system is designed to be intuitive and mathematically consistent, with behaviour similar to well-known languages like MATLAB and Python. A notable exception is compound assignment operations, where Aleph enforces stronger type safety.

Type Promotion Rules

Aleph follows a clear hierarchy for automatic type promotion that enables seamless operations between different numeric types. Binary operations are well-defined between like-types, but mixed-type operations require promotion rules to determine the result type.

Consider the unary operation ! (logical NOT). While it's clearly defined for booleans (!true is false and !false is true), what should !2 or !1i return? Similarly, what happens in compound assignments like:

var x = 0
x += 3.5

Should the fractional part be truncated, or should x be promoted to store the full result?

Aleph's promotion system follows these fundamental rules:

Promotion Hierarchy:

  • boolinteger
  • integerreal
  • realcomplex
  • Variables cannot change type

Type Promotion in Detail:

bool to integer promotion: Boolean values false and true are equivalent to 0 and 1, respectively. This allows operations like -true, which returns the integer -1. Since the result cannot be represented as a boolean, the type is promoted to integer. For consistency, -false returns 0 (not false), even though mathematically -0 = 0.

integer to real promotion: Promotion is transitive, so bool can also be promoted to real. This enables seamless mixed-type arithmetic: 1 + 2.5 returns 3.5 (real), and true + 3.2 returns 4.2 (real).

real to complex promotion: All numeric types can ultimately promote to complex, enabling operations like 3.5 + 2i to return 3.5 + 2i (complex).

Variables maintain their declared type: This is the key difference from languages like Python or MATLAB. Consider:

var x = 2       // x is an integer
x += 3.5 // Error: cannot store real result in integer variable

Here, x is declared as an integer. The operation x += 3.5 would naturally produce 5.5 (a real), but since x cannot change its type from integer to real, this operation throws an error.

This differs from Python, where x would be reassigned to hold the float result, and from MATLAB (which doesn't implement += but allows x = x + 3.5), where x would seamlessly become a double. Aleph's approach provides stronger type safety by preventing unintended type changes.

note

Comparison with other languages:

  • Python/MATLAB-like behaviour: Automatic promotion during binary operations (1 + 2.53.5)
  • Aleph's stricter approach: Variables maintain their declared type, preventing implicit type changes in assignments

Operations

The following sections detail all operations supported on Aleph's numeric types.

Assignment (=)

Basic assignment follows type compatibility rules. A variable can only be assigned values that are compatible with its declared type.

=boolintegerrealcomplex
bool
integer
real
complex

The way to read this table is: the left column indicates the type of the variable being assigned to, and the top row indicates the type of the value being assigned. A check mark (✅) indicates that the assignment is valid, while a blank space indicates that it is not.

Examples:

var b = false     // bool variable
b = true // bool ← bool
// b = 1 // bool cannot store integer

var i = 42 // integer variable
i = true // integer ← bool (promoted to 1)
i = 99 // integer ← integer
// i = 3.14 // integer cannot store real

var r = 3.14 // real variable
r = false // real ← bool (promoted to 0.0)
r = 42 // real ← integer (promoted to 42.0)
r = 2.71 // real ← real

var c = 1+2i // complex variable (accepts all types)
c = true // complex ← bool (promoted to 1+0i)
c = 42 // complex ← integer (promoted to 42+0i)
c = 3.14 // complex ← real (promoted to 3.14+0i)
c = 2+3i // complex ← complex

Logical Operations

Logical NOT (!)

The logical NOT operator treats any non-zero value as "true" and zero as "false", returning a boolean result.

!boolintegerrealcomplex
✅ →bool✅ →bool✅ →bool

Examples:

!true         // false
!false // true
!0 // true
!42 // false
!0.0 // true
!3.14 // false
!(0+0i) // true
!(1+2i) // false
warning

Using logical NOT on floating-point numbers can be unsafe due to precision issues. For example, !(0.1 + 0.2 - 0.3) might unexpectedly return false due to floating-point representation errors.

Comparison Operations

Equality and Inequality (==, !=)

Equality comparison is supported between all numeric types with automatic promotion.

==, !=boolintegerrealcomplex
bool
integer
real
complex

Examples:

true == 1         // true (bool promoted to integer)
42 == 42.0 // true (integer promoted to real)
3.0 == 3+0i // true (real promoted to complex)
1+2i != 2+1i // true
warning

Comparing floating-point numbers for exact equality is often unreliable due to precision limitations. Consider using a condition with a certain tolerance when working with real or complex numbers.

Ordering Operations (<, <=, >, >=)

Ordering comparisons are only defined for types that have a natural ordering. Complex numbers cannot be ordered.

<, <=, >, >=boolintegerrealcomplex
bool
integer
real
complex

Examples:

false < true      // true (false=0, true=1)
-5 <= 10 // true
3.14 > 2 // true (integer promoted to real)
// 1+2i < 2+1i // Error: no natural ordering for complex numbers

Arithmetic Operations

Unary Arithmetic (+, -)

Unary plus and minus are defined for all numeric types. Unary minus on bool is promoted to integer.

+, -boolintegerrealcomplex
✅→int

Examples:

+true         // 1 (promoted to integer)
-false // 0 (promoted to integer)
+42 // 42
-3.14 // -3.14
-(2+3i) // -2-3i

Binary Arithmetic (+, -, *, /, **)

All arithmetic operations are supported between any numeric types with automatic promotion to the "higher" type.

+, -, *, /, **boolintegerrealcomplex
bool
integer
real
complex

Examples:

true + 5      // 6 (bool→integer)
2 * 3.5 // 7.0 (integer→real)
3.0 + 2i // 3+2i (real→complex)
2**3 // 8 (integer exponentiation)
(1+1i)**2 // 0+2i (complex exponentiation)
warning

Integer Division: Division between two integers performs integer division (truncation). For floating-point division, at least one operand must be real:

  • 5 / 22 (integer division)
  • 5 / 2.02.5 (floating-point division)

Compound Arithmetic Assignment (+=, -=, *=, /=, **=)

Unlike basic arithmetic, compound assignment operations enforce strict type compatibility to prevent unintended type changes.

+=, -=, *=, /=boolintegerrealcomplex
bool
integer
real
complex

Examples:

var i = 10
i += 5 // 15 (integer += integer)
i += true // 16 (bool promoted to integer)
// i += 2.5 // Error: cannot store real in integer variable

var r = 3.5
r += 2 // 5.5 (integer promoted to real)
r *= 1.5 // 8.25 (real *= real)

var c = 1+2i
c += 3 // 4+2i (integer promoted to complex)
c *= 1+1i // 2+6i (complex multiplication)
note

Key Difference from Python/MATLAB: Compound assignments maintain the variable's original type. This prevents bugs where a variable unexpectedly changes type during computation.

Integer-Specific Operations

Increment and Decrement (++, --)

Pre-increment and pre-decrement operators are only defined for integer types.

++, --boolintegerrealcomplex

Examples:

var count = 10
++count // 11 (pre-increment)
--count // 10 (pre-decrement)
// ++3.14 // Error: not defined for real
// ++true // Error: not defined for bool
note

Pre-increment and pre-decrement operators are not defined for bool as it is ambiguous what --false or ++true should be. Indeed, should they wrap around, or remain unchanged? Note that they cannot be promoted to integer in this context since the operation happens on the variable itself.

Modulus (%)

The modulus operator returns the remainder of integer division and is only defined between bool and integer types.

%boolintegerrealcomplex
bool
integer
real
complex

Examples:

7 % 3         // 1
true % 2 // 1 (bool promoted to integer)
10 % false // Error: division by zero
// 7.5 % 2.0 // Error: not defined for real numbers

Compound Modulus Assignment (%=)

%=boolintegerrealcomplex
bool
integer
real
complex

Bitwise Operations

Bitwise operations treat numbers as sequences of bits and are only meaningful for bool and integer types.

info

For bitwise operations, it is useful to know that integer is a 64-bit signed integer.

Unary bitwise complement (~)

~boolintegerrealcomplex

Binary bitwise operations (&, ^, |, <<, >>)

&, ^, |, <<, >>boolintegerrealcomplex
bool
integer
real
complex

Examples:

~5            // -6 (bitwise complement)
5 & 3 // 1 (0101 & 0011 = 0001)
5 ^ 3 // 6 (0101 ^ 0011 = 0110)
5 | 3 // 7 (0101 | 0011 = 0111)
5 << 1 // 10 (left shift by 1 bit)
10 >> 1 // 5 (right shift by 1 bit)
true & 3 // 1 (bool promoted to integer)

Compound Bitwise Assignment (&=, |=, ^=, <<=, >>=)

Compound bitwise operations are only defined when the left-hand side is an integer, since the result is always an integer.

&=, |=, ^=, <<=, >>=boolintegerrealcomplex
bool
integer
real
complex

Examples:

var flags = 0b1010
flags &= 0b1100 // 0b1000 (bitwise AND assignment)
flags |= 0b0001 // 0b1001 (bitwise OR assignment)
flags <<= 2 // 0b100100 (left shift assignment)
note

Bitwise Operation Results: All bitwise operations return integer values, which is why compound assignment is only allowed when the target variable is also an integer.

Summary

Aleph's numeric system balances mathematical intuition with type safety:

Similarities to Python/MATLAB:

  • Automatic type promotion in expressions (1 + 2.53.5)
  • Rich set of arithmetic and comparison operations
  • Support for complex numbers with seamless promotion

Aleph's Type Safety Advantages:

  • Variables maintain their declared type throughout their lifetime
  • Compound assignments prevent accidental type changes
  • Clear error messages when type incompatibilities occur

This design helps catch type-related bugs early while maintaining the mathematical expressiveness developers expect from scientific computing languages.

Examples

FizzBuzz

for (i: [1..16]) {
if (i % 15 == 0) {
print("FizzBuzz");
} else if (i % 3 == 0) {
print("Fizz");
} else if (i % 5 == 0) {
print("Buzz");
} else {
print(i);
}
}
// Output:
1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
FizzBuzz

Fibonacci

def fib(n) {
if(n <= 1) {
return n
}
return fib(n - 1) + fib(n - 2)
}

"fib(10): ${fib(10)}" //-> fib(10): 55

Projectile Motion

global g = 9.81

def projectile_range(real v, real angle_deg) {
var angle_rad = angle_deg * pi / 180
return v**2 * sin(2 * angle_rad) / g
}

"Range for 30° at 20 m/s: ${projectile_range(20.0, 30.0)} meters" //-> Range for 30° at 20 m/s: 35.3119430697019 meters

Acknowledgements

Aleph began as a fork of ChaiScript, created by Jason Turner and Jonathan Turner.
We continue to build on their work and honor the project's BSD-3-Clause license.

Documentation Contributors

Julien Blin

Vincent Michaud-Rioux

Jonathon Riddell