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:
| Method | List | Map | String | Set |
|---|---|---|---|---|
| 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:
bool→integerinteger→realreal→complex- 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.
Comparison with other languages:
- Python/MATLAB-like behaviour: Automatic promotion during binary operations (
1 + 2.5→3.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.
= | bool | integer | real | complex |
|---|---|---|---|---|
| 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.
! | bool | integer | real | complex |
|---|---|---|---|---|
| ✅ | ✅ →bool | ✅ →bool | ✅ →bool |
Examples:
!true // false
!false // true
!0 // true
!42 // false
!0.0 // true
!3.14 // false
!(0+0i) // true
!(1+2i) // false
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.
==, != | bool | integer | real | complex |
|---|---|---|---|---|
| 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
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.
<, <=, >, >= | bool | integer | real | complex |
|---|---|---|---|---|
| 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.
+, - | bool | integer | real | complex |
|---|---|---|---|---|
| ✅→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.
+, -, *, /, ** | bool | integer | real | complex |
|---|---|---|---|---|
| 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)
Integer Division: Division between two integers performs integer division (truncation). For floating-point division, at least one operand must be real:
5 / 2→2(integer division)5 / 2.0→2.5(floating-point division)
Compound Arithmetic Assignment (+=, -=, *=, /=, **=)
Unlike basic arithmetic, compound assignment operations enforce strict type compatibility to prevent unintended type changes.
+=, -=, *=, /= | bool | integer | real | complex |
|---|---|---|---|---|
| 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)
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.
++, -- | bool | integer | real | complex |
|---|---|---|---|---|
| ✅ |
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
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.
% | bool | integer | real | complex |
|---|---|---|---|---|
| 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 (%=)
%= | bool | integer | real | complex |
|---|---|---|---|---|
| bool | ||||
| integer | ✅ | ✅ | ||
| real | ||||
| complex |
Bitwise Operations
Bitwise operations treat numbers as sequences of bits and are only meaningful for bool and integer types.
For bitwise operations, it is useful to know that integer is a 64-bit signed integer.
Unary bitwise complement (~)
~ | bool | integer | real | complex |
|---|---|---|---|---|
| ✅ | ✅ |
Binary bitwise operations (&, ^, |, <<, >>)
&, ^, |, <<, >> | bool | integer | real | complex |
|---|---|---|---|---|
| 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.
&=, |=, ^=, <<=, >>= | bool | integer | real | complex |
|---|---|---|---|---|
| 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)
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.5→3.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.