Tutorial
Calcium Tutorial
Learn Calcium step by step, from basics to advanced features.
Contents
- Basics - Variables, types, and operators
- Functions - Defining and using functions
- Pipelines - The pipeline operator
- Pattern Matching - Match expressions
- Collections - Arrays and hashes
- Effects - Side effects and Result types
- Constraints - Value validation
- Modules - Organizing code
1. Basics
Variables
Variables are bound using =. Once bound, they cannot be reassigned.
name = "Calcium";
age = 1;
active = true;
pi = 3.14159;
Basic Types
| Type | Example | Description |
|---|---|---|
| Integer | 42, -17 |
Whole numbers |
| Float | 3.14, -0.5 |
Decimal numbers |
| String | "hello", 'world' |
Text |
| Boolean | true, false |
Logical values |
| Array | [1, 2, 3] |
Ordered collection |
| Hash | {name: "Alice"} |
Key-value pairs |
| Null | null |
Absence of value |
Operators
// Arithmetic
5 + 3; // 8
10 - 4; // 6
3 * 4; // 12
15 / 3; // 5
17 % 5; // 2
// Comparison
5 == 5; // true
5 != 3; // true
5 > 3; // true
5 >= 5; // true
3 < 5; // true
3 <= 3; // true
// Chained comparisons
0 <= x <= 100; // true if x is between 0 and 100
// Logical
true && false; // false
true || false; // true
!true; // false
// String concatenation
"Hello, " + "World!"; // "Hello, World!"
String Interpolation
name = "World";
greeting = "Hello, ${name}!"; // "Hello, World!"
x = 10;
y = 20;
result = "${x} + ${y} = ${x + y}"; // "10 + 20 = 30"
2. Functions
Named Functions
func double(x) = x * 2;
func add(a, b) = a + b;
func greet(name) = "Hello, " + name + "!";
Lambda Expressions
// Single parameter (no parentheses needed)
double = x => x * 2;
// Multiple parameters
add = (a, b) => a + b;
// Using lambdas with higher-order functions
numbers = [1, 2, 3, 4, 5];
doubled = map(numbers, x => x * 2); // [2, 4, 6, 8, 10]
Closures
Functions capture variables from their enclosing scope:
func make_adder(n) = x => x + n;
add5 = make_adder(5);
add5(10); // 15
add5(20); // 25
Recursion
func factorial(n) = match n
0 => 1
_ => n * factorial(n - 1);
factorial(5); // 120
3. Pipelines
The Pipeline Operator (|>)
The pipeline operator passes the left value as the first argument to the right function:
// These are equivalent:
double(5);
5 |> double;
// Chain multiple operations
[1, 2, 3, 4, 5]
|> filter(x => x % 2 == 1) // [1, 3, 5]
|> map(x => x * x) // [1, 9, 25]
|> reduce((a, b) => a + b, 0); // 35
The Effect Pipeline (!>)
For functions with side effects, use !>:
use core.io!;
"Hello, World!" !> io.println;
// Chain effects
result = compute_something()
!> log_result
!> save_to_file;
4. Pattern Matching
Basic Match
func describe(n) = match n
0 => "zero"
1 => "one"
_ => "many";
describe(0); // "zero"
describe(1); // "one"
describe(5); // "many"
Matching with Conditions
func fizzbuzz(n) = match [n % 3, n % 5]
[0, 0] => "FizzBuzz"
[0, _] => "Fizz"
[_, 0] => "Buzz"
_ => to_string(n);
Result Matching
result = some_operation();
value = result !? {
success(v) => v
failure(e) => default_value
};
5. Collections
Arrays
numbers = [1, 2, 3, 4, 5];
// Access elements
numbers[0]; // 1
numbers[2]; // 3
// Built-in functions
len(numbers); // 5
head(numbers); // 1
tail(numbers); // [2, 3, 4, 5]
push(numbers, 6); // [1, 2, 3, 4, 5, 6]
concat([1, 2], [3, 4]); // [1, 2, 3, 4]
range(1, 6); // [1, 2, 3, 4, 5]
// Higher-order functions
map(numbers, x => x * 2); // [2, 4, 6, 8, 10]
filter(numbers, x => x > 2); // [3, 4, 5]
reduce(numbers, (a, b) => a + b, 0); // 15
Array Destructuring
[a, b, c] = [10, 20, 30];
// a = 10, b = 20, c = 30
[first | rest] = [1, 2, 3, 4, 5];
// first = 1, rest = [2, 3, 4, 5]
Hashes
person = {name: "Alice", age: 30, city: "Tokyo"};
// Access
person.name; // "Alice"
person["age"]; // 30
// Dynamic key access
key = "city";
person[key]; // "Tokyo"
// Built-in functions
keys(person); // ["name", "age", "city"]
values(person); // ["Alice", 30, "Tokyo"]
has(person, "name"); // true
6. Effects
Effect Functions
Functions with side effects are marked with !:
use core.io!;
// Define an effect function
func! greet(name) = io.println("Hello, " + name);
// Call effect functions
greet("World");
Result Types
Operations that can fail return Result types:
// Success and failure
success(42); // Wraps a successful value
failure("error"); // Wraps an error
// Pattern match on results
result = some_operation();
result !? {
success(value) => handle_success(value)
failure(error) => handle_error(error)
};
HTTP Example
use core.io!;
use core.http!;
result = http.get("https://api.example.com/data", {});
result !? {
success(response) => io.println(response.body)
failure(error) => io.println("Error: " + error)
};
7. Constraints
Defining Constraints
constraint Positive(n) = n > 0;
constraint InRange(n) = 0 <= n <= 100;
constraint NonEmpty(s) = len(s) > 0;
Using Constraints
// Check with pipe
10 |> Positive?; // success(10)
-5 |> Positive?; // failure(-5)
// In function parameters
func safe_divide(x, y: Positive?) = x / y;
safe_divide(10, 2); // success(5)
safe_divide(10, 0); // failure(0)
safe_divide(10, -1); // failure(-1)
8. Modules
Using Standard Library
use core.io!;
use core.math;
use core.string;
use core.array;
io.println(math.sqrt(16)); // 4
io.println(string.upper("hello")); // HELLO
io.println(array.reverse([1,2,3])); // [3, 2, 1]
Using External Modules
use ytnobody/json;
data = {name: "test"};
json_str = json.stringify(data);
Creating Modules
// mymodule/mod.ca
namespace mymodule;
func hello() = "Hello from mymodule!";
func add(a, b) = a + b;
// main.ca
use mymodule;
mymodule.hello(); // "Hello from mymodule!"
mymodule.add(2, 3); // 5
Next Steps
- Modules Guide - Learn about bone and Boneyard
- Language Reference - Complete specification