Values and Types

J2 is dynamically typed with a deliberately small set of value types. Optional type annotations refine them for the native compiler, but every value at runtime is one of the kinds below.

The types

TypeExampleNotes
integer42, -7, 1_000_00064-bit signed; arithmetic is overflow checked
float3.14, .5, 1.0e3064-bit IEEE 754
text"hello"immutable UTF-8 string; see Text
booltrue, false
nullnullthe absence of a value
seq[1, 2, 3]ordered, mutable, shared by reference
map{name: "Ada", age: 36}text-keyed record
pair(3, 7)an immutable two-tuple
flow1..100, 1..a lazy stream, usually a range
funcfunc(x) = x * xfunctions are ordinary values
class, instancePoint, Point(x: 3, y: 4)see Classes

The type builtin names a value's kind at runtime. Scalars (integers, floats, text, and bools) all report the umbrella kind val; the structured types report themselves:

print(type(1))          # val
print(type(1.5))        # val
print(type("s"))        # val
print(type([1]))        # seq
print(type({x: 1}))     # map
print(type((1, 2)))     # pair
print(type(1..3))       # flow
print(type(print))      # func
print(type(null))       # null

Numbers

Integers are 64-bit and signed. Underscores may separate digits in any numeric literal: 1_000_000. Floats accept fractional, leading-dot, and scientific forms: 3.14, .5, 2.5e8.

Arithmetic mixing an integer and a float produces a float. Two rules are worth committing to memory:

  • Division keeps integers only when exact. 6 / 2 is the integer 3; 7 / 2 is the float 3.5. There is no separate integer-division operator.
  • Integer overflow is an error, not a wraparound. Arithmetic that exceeds 64 bits raises OverflowError rather than silently producing a wrong number.
print(6 / 2)        # 3
print(7 / 2)        # 3.5
print(7 / 2.0)      # 3.5
print(2 ** 10)      # 1024
print(-7 % 5)       # 3

The remainder operator on integers is Euclidean: the result is never negative, so -7 % 5 is 3, which keeps index arithmetic and cyclic patterns free of negative surprises. Dividing or taking a remainder by zero raises ZeroDivisionError.

Numeric constants

Seven constants are built in. All of them are floats.

NameValue
PI3.141592653589793
E2.718281828459045
TAUtwo pi
INFpositive infinity
NANnot a number; compares unequal to everything, itself included
MAX_VALthe largest finite float
MIN_VALthe most negative finite float
print(PI)             # 3.141592653589793
print(INF > MAX_VAL)  # true
print(NAN == NAN)     # false

Truthiness

Conditions accept any value. The falsy values are null, false, zero (integer or float), NAN, empty text, an empty seq, and an empty map. Everything else is truthy.

if "" { print("never") } else { print("empty text is falsy") }
if [0] { print("a non-empty seq is truthy") }

Conversions

Conversions are explicit. num parses text into an integer when it can and a float otherwise, raising ConversionError on anything unparseable. str renders any value as text.

print(num("42") + 1)      # 43
print(num("3.5") * 2)     # 7
print(str(42) + "!")      # 42!

Type annotations

Everything above describes runtime behavior, which never requires annotations. J2 also has a written type language, used in function signatures and class fields: int, float, bool, text, nil, seq<T>, map<K, V>, pair<A, B>, and func(T) -> R. Annotated code means the same thing it meant before; the annotations exist to let the native compiler produce specialized machine code. They are covered with functions in Functions and put to work in Automatic Parallelism.