Skip to content

Jac Basics (from Python)

Learn Jac syntax and fundamentals, especially if you’re coming from Python.

Prerequisites

  • Completed: Hello World
  • Familiar with: Python basics
  • Time: ~30 minutes

Jac supersets Python with new paradigms — familiar Python concepts all apply. The main syntactic differences from Python are:

PythonJac
Indentation blocks{ } braces
No semicolons; required
def func():def func() { }
if x:if x { }
class Foo:obj Foo { }

with entry {
# Type inference (like Python)
name = "Alice";
age = 30;
pi = 3.14159;
active = True;
# Explicit type annotations (recommended)
name: str = "Alice";
age: int = 30;
pi: float = 3.14159;
active: bool = True;
}
with entry {
# Lists
numbers: list[int] = [1, 2, 3, 4, 5];
numbers.append(6);
# Dictionaries
person: dict[str, any] = {
"name": "Alice",
"age": 30
};
# Sets
unique: set[int] = {1, 2, 3};
# Tuples
point: tuple[int, int] = (10, 20);
}

def greet(name: str) -> str {
return f"Hello, {name}!";
}
def add(a: int, b: int) -> int {
return a + b;
}
with entry {
message = greet("World");
print(message); # Hello, World!
result = add(5, 3);
print(result); # 8
}
def greet(name: str, greeting: str = "Hello") -> str {
return f"{greeting}, {name}!";
}
with entry {
print(greet("Alice")); # Hello, Alice!
print(greet("Bob", "Hi")); # Hi, Bob!
}

Use def for methods inside objects:

obj Calculator {
has value: int = 0;
def add(n: int) -> int {
self.value += n;
return self.value;
}
def reset() -> None {
self.value = 0;
}
}
with entry {
calc = Calculator();
calc.add(5);
calc.add(3);
print(calc.value); # 8
}

def classify_number(n: int) -> str {
if n < 0 {
return "negative";
} elif n == 0 {
return "zero";
} else {
return "positive";
}
}
with entry {
print(classify_number(-5)); # negative
print(classify_number(0)); # zero
print(classify_number(10)); # positive
}
with entry {
# Iterate over list
for item in [1, 2, 3] {
print(item);
}
# Range-based loop
for i in range(5) {
print(i); # 0, 1, 2, 3, 4
}
# Enumerate
names = ["Alice", "Bob", "Carol"];
for (i, name) in enumerate(names) {
print(f"{i}: {name}");
}
}
with entry {
count = 0;
while count < 5 {
print(count);
count += 1;
}
}

Match case bodies use Python-style indentation, not braces:

def describe(value: int) -> str {
match value {
case 0:
return "zero";
case 1 | 2 | 3:
return "small";
case _:
return "medium";
}
}
with entry {
print(describe(0)); # zero
print(describe(2)); # small
print(describe(50)); # medium
}

obj Person {
has name: str;
has age: int;
has email: str = ""; # default value
def introduce() -> str {
return f"I'm {self.name}, {self.age} years old.";
}
def have_birthday() -> None {
self.age += 1;
}
}
with entry {
alice = Person(name="Alice", age=30);
print(alice.introduce()); # I'm Alice, 30 years old.
alice.have_birthday();
print(alice.age); # 31
}
obj Animal {
has name: str;
def speak() -> str {
return "...";
}
}
obj Dog(Animal) {
has breed: str = "Unknown";
def speak() -> str {
return "Woof!";
}
}
obj Cat(Animal) {
def speak() -> str {
return "Meow!";
}
}
with entry {
dog = Dog(name="Buddy", breed="Labrador");
cat = Cat(name="Whiskers");
print(f"{dog.name} says {dog.speak()}"); # Buddy says Woof!
print(f"{cat.name} says {cat.speak()}"); # Whiskers says Meow!
}

enum Color {
RED,
GREEN,
BLUE
}
enum Status {
PENDING = "pending",
ACTIVE = "active",
COMPLETED = "completed"
}
with entry {
color = Color.RED;
status = Status.ACTIVE;
if color == Color.RED {
print("It's red!");
}
print(status.value); # active
}

def divide(a: float, b: float) -> float {
if b == 0 {
raise ValueError("Cannot divide by zero");
}
return a / b;
}
with entry {
try {
result = divide(10, 0);
} except ValueError as e {
print(f"Error: {e}");
} finally {
print("Done");
}
}

import math;
import json;
import os;
with entry {
print(math.sqrt(16)); # 4.0
print(os.getcwd());
}
import from collections { Counter, defaultdict }
import from typing { Optional, List }
with entry {
counts = Counter(["a", "b", "a", "c", "a"]);
print(counts["a"]); # 3
}
# utils.jac
def helper() -> str {
return "I'm a helper";
}
# main.jac
import from utils { helper }
with entry {
print(helper());
}

Use glob for module-level variables:

glob config: dict = {
"debug": True,
"version": "1.0.0"
};
def get_version() -> str {
return config["version"];
}
with entry {
print(get_version()); # 1.0.0
}

ConceptPythonJac
BlocksIndentation{ } braces
StatementsNo semicolons; required
Classesclassobj
Methodsdef inside classdef inside obj
AttributesIn __init__has declarations
Entry pointif __name__ == "__main__"with entry { }
Module variablesGlobal varsglob keyword