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
| Type | Example | Notes |
|---|---|---|
| integer | 42, -7, 1_000_000 | 64-bit signed; arithmetic is overflow checked |
| float | 3.14, .5, 1.0e30 | 64-bit IEEE 754 |
| text | "hello" | immutable UTF-8 string; see Text |
| bool | true, false | |
| null | null | the 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 |
| flow | 1..100, 1.. | a lazy stream, usually a range |
| func | func(x) = x * x | functions are ordinary values |
| class, instance | Point, 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 / 2is the integer3;7 / 2is the float3.5. There is no separate integer-division operator. - Integer overflow is an error, not a wraparound. Arithmetic that
exceeds 64 bits raises
OverflowErrorrather 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.
| Name | Value |
|---|---|
PI | 3.141592653589793 |
E | 2.718281828459045 |
TAU | two pi |
INF | positive infinity |
NAN | not a number; compares unequal to everything, itself included |
MAX_VAL | the largest finite float |
MIN_VAL | the 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.