Types¶

Symbolic provides a numeric hierarchy built on arbitrary-precision arithmetic, a Unicode string type, ordered and unordered collection types, and an optional gradual type-annotation system.


Numeric Types¶

All numeric types derive from Number, which provides arithmetic, comparison, trigonometric, and algebraic operations uniformly across the hierarchy.

Integer¶

Arbitrary-precision integers. Division of two integers returns a Fraction, not a truncated integer:

a = 42
b = -17
c = 10 ^ 100        # arbitrarily large
d = 0xFF            # hexadecimal literal
e = 0b1010_1111     # binary literal with separator

print(a + b)        # 25
print(10 / 3)       # 10/3  (Fraction, not 3)
print(10 // 3)      # 3     (floor division returns Integer)

Float¶

Arbitrary-precision floating-point numbers backed by mpmath.mpf. The working precision defaults to 15 significant digits and adapts to the precision of the literal:

x = 3.14159265358979
y = 6.022e23
z = 1.5e-10

# Precision-controlled block
@$precision 50 {
    result = x ^ 2;
}

Fraction¶

Exact rational numbers. Created automatically by integer division and maintained in lowest terms:

r = 10 / 3       # 10/3
s = 1 / 2 + 1/3  # 5/6

print(r.numerator)    # 10
print(r.denominator)  # 3

Complex¶

Arbitrary-precision complex numbers using mpmath.mpc. The imaginary unit is i:

z1 = 3 + 4i
z2 = 2.5 - 1.7i
z3 = 1i           # pure imaginary

print(|z1|)       # 5.0  — magnitude using pipe-absolute syntax
print(z1*)        # 3-4i — complex conjugate (postfix complement)
print(z1.real)    # 3
print(z1.imag)    # 4

Numeric Operations (shared)¶

All Number subtypes support the following methods:

Method

Description

.to_int()

Convert to Integer (truncates)

.to_float()

Convert to Float

.to_complex()

Convert to Complex

.sqrt()

Square root (returns Complex when input is negative)

.cbrt()

Cube root

.sin(), .cos(), .tan()

Trigonometric functions (radians)

.asin(), .acos(), .atan()

Inverse trigonometric functions

.exp()

ex

.log(base?)

Natural log, or log to specified base

.simplify()

Return a simplified symbolic form

Uncertainty Propagation¶

Numeric values can carry an uncertainty. Arithmetic on uncertain values automatically propagates error in quadrature:

# Not yet user-facing syntax — set via .set_uncertainty()
x = Float(9.81)
x.set_uncertainty(0.02)
print(x.format())    # "9.81 ± 0.02"

Boolean¶

The Boolean type wraps true and false. Booleans behave as integers (true = 1, false = 0) in arithmetic contexts:

a = true
b = false

print(a and b)   # false
print(a or  b)   # true
print(!a)        # false
print(a + 1)     # 2

String¶

Strings are UTF-8 Unicode sequences. The String type extends Python’s str and provides functional methods:

s = "Hello, World!"

print(s.length)           # 13
print(s.upper())          # "HELLO, WORLD!"
print(s.reverse())        # "!dlroW ,olleH"
print(s.contains("World"))# true
print(s.split(", "))      # ["Hello", "World!"]
print(s[0..5])            # "Hello"

# Functional transformations
digits = "a1b2c3"
nums   = digits.filter((c) -> c >= "0" and c <= "9")
print(nums)               # "123"

# Palindrome check
print("racecar".is_palindrome())   # true

Collections¶

List ~~~

Ordered, mutable, heterogeneous sequences:

nums = [1, 2, 3, 4, 5]

nums.append(6)
nums.reverse()
print(nums)              # [6, 5, 4, 3, 2, 1]

# Functional pipeline — lazy evaluation, collect to materialize
result = nums
    .filter((x) -> x > 2)
    .map((x) -> x ^ 2)
    .to_list()

# Reduce
total = nums.reduce((a, b) -> a + b)

# Slicing
sub = nums[1..3]         # [5, 4]

# Fill constructor
zeros = [].fill(0, 10)   # [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

Tuple¶

Ordered, immutable sequences. A trailing comma distinguishes a single-element tuple from a parenthesized expression:

t = (1, "hello", 3.14)
(x, y, z) = t             # destructuring assignment

singleton = (42,)         # single-element tuple
empty     = ()

Dictionary¶

Key-value maps with functional accessors:

d = {"name": "Alice", "score": 97}

print(d["name"])               # "Alice"
print(d.get("age", 0))         # 0 (default)

d["rank"] = 1

# Functional transforms
upper_keys = d.map_keys((k) -> k.upper())
high       = d.filter_items((k, v) -> v > 50)

Set ~~

Unordered collections of unique elements:

s1 = {1, 2, 3}
s2 = {2, 3, 4}

print(s1.union(s2))           # {1, 2, 3, 4}
print(s1.intersection(s2))    # {2, 3}
print(s1.difference(s2))      # {1}

Type Annotations¶

Type annotations are optional. When present, they are written after a colon on function parameters and with an arrow for return types. Annotations are evaluated at definition time; the runtime does not enforce them by default unless contract clauses are present.

fn greet(name: String) -> String {
    return f"Hello, {name}!";
}

fn divide(a: Integer, b: Integer) -> Fraction
    requires b != 0 {
    return a / b;
}

Union types use ||:

fn process(value: Integer || Float || Complex) -> Float || Complex {
    return value.sqrt();
}

Generic parameters use angle brackets:

fn identity<T>(x: T) -> T {
    return x;
}

Constants¶

The const modifier prevents reassignment. const values are also implicitly final (cannot be shadowed in a nested scope):

const MAX_RETRIES = 5
const BASE_URL    = "https://api.example.com"

MAX_RETRIES = 6    # compile error: assignment to constant

Null and Optional Values¶

Symbolic does not have a dedicated null literal. The null-coalesce operator ?? and optional-chaining operator ?. support nullable patterns:

value = lookup("key") ?? "default"

length = user?.profile?.bio?.length ?? 0

See also

<no title> — Type annotations on parameters and return types. <no title> — Type patterns in match expressions.