Fluxon Docs
Batteries

core — list, map, str, math, rand, time

The core methods and modules that work without `use` — list/map methods and the str/math/rand/time functions.

All of these are core — they work without use (just like log).

list — list methods

On a value, .method:

l.len                  # length
l.push x               # adds an element → a new list
l.filter \x -> x > 0   # keeps those matching the condition → a new list
l.map \x -> x * 2      # transforms each → a new list
l.has x                # is it inside → bool
l.index x              # index of the first matching element, -1 if not found
l.find \x -> x > 4     # first element matching the predicate, nil if not found
l.0  l.1               # element by index
l.slice a b            # the a..b range (b excluded) → a new list
l.join ", "            # → text: [1 2 3].join "," → "1,2,3"
l.reduce 0 \acc x -> acc + x   # accumulate: (initial value, function)
l.sort                 # natural order (numbers or strings) → a new list
l.sort \a b -> a.p - b.p   # comparator returns a number: negative → a first
l.reverse              # reversed order → a new list
l.uniq                 # removes duplicates (first occurrence kept)
l.flat                 # flattens one level: [[1 2] [3]] → [1 2 3]
l.zip other            # pairs up: [1 2].zip ["a" "b"] → [[1 "a"] [2 "b"]]
l.any \x -> x > 4      # does any match → bool (stops at first match)
l.all \x -> x > 0      # do all match → bool (stops at first mismatch)

Important

To build a list use l.push x, not l + [x]. To filter, use l.filter instead of a manual each loop; to build text, use l.join instead of a manual accumulator:

# Manual (long):              With methods (clean):
result <- []                  result = items.filter \t -> t.active
each t in items
  if t.active
    result <- result.push t

text <- ""                    text = names.join ", "
each n in names
  text <- text + n + ", "

map — key-value methods

On a value, .method:

m.set k v              # sets/updates a key → a new map
m.del k                # removes a key → a new map
m.merge other          # merges two maps (other's keys win) → a new map
m.has k                # is the key present → bool
m.keys                 # a list of keys
m.vals                 # a list of values
m.key   m[k]           # read (m[k] — dynamic, variable key)

Important

To write to a map use m.set k v. m[k] only reads (does not write). This is consistent with lists: push for a list, set for a map. Shared state (for example, who is in which room in realtime) is managed with these methods.

str — text functions

str.len s              # length (number)
str.slice s 0 3        # the 0..3 range (3 excluded): "hello" → "hel"
str.up s               # UPPERCASE
str.low s              # lowercase
str.split s ","        # split → a list: "a,b" → ["a" "b"]
str.has s "part"       # is it inside → bool
str.int "42"           # text → number
str.str 42             # number → text
str.trim "  s  "       # strips leading/trailing whitespace → "s"
str.replace s "-" "+"  # replaces every "-" with "+"
str.starts s "/api"    # starts with prefix → bool
str.ends s ".fx"       # ends with suffix → bool
str.pad "7" 3 "0"      # pads on the LEFT → "007"
str.repeat "ab" 3      # repeat → "ababab"

Why is str.len s different from list.len?

List length is a member (list.len), text length is a module function (str.len s). The reason: a list and text are separate types, and their operations should not mix. If both were the same .len it would be confusing.

math — math

math.floor 3.7         # → 3
math.ceil 3.2          # → 4
math.abs -5            # → 5
math.min 3 7           # → 3 (ints in, int out)
math.max 3 7           # → 7
math.pow 2 10          # → 1024 (int ^ non-negative int → int)
math.sqrt 9            # → 3.0 (always flt; negative input is an error)

rand — random

rand.int 1 100         # a random integer in the range 1..100
rand.str 6             # a random string of 6 characters (short codes)

rand is backed by the OS cryptographic CSPRNG, so its output is not predictable. But length matters too: rand.str 6 yields only ~36 bits of entropy (62⁶) — fine for a short code, but brute-forceable as a secret. For session IDs, tokens, and other secrets use at least rand.str 24 (~140+ bits).

time — time and date

time.now               # the current time (timestamp)
time.ago 24 :hr        # the time 24 units ago. Units: :sec :min :hr :day
time.in  60 :min       # the time 60 units later (TTL/expiry). Same units
time.fmt t "..."       # format a timestamp into text
time.sleep 1           # waits 1 second (flt too — 0.5). Polling/retry backoff
time.parse "2026-06-10T10:00:00Z"   # arbitrary ISO text -> canonical UTC timestamp ("Z"/"±HH:MM")
time.add t 30 :min     # offset from ANY timestamp (not now): end_at = start_at + duration
time.sub t 5 :min      # mirror of time.add — shift backward (e.g. buffer before)
time.diff a b          # (a - b) difference in seconds (int); / 60 -> minutes

Difference between time.in/time.ago (offset from now) and time.add/time.sub (offset from any given timestamp): a booking server computes end_at = time.add start_at 30 :min from a client-supplied start_at. Instead of writing raw now() - interval '24 hours' in a DB query, use time.ago — it is clean and safe:

r = db.one "select count(*) c from tickets where created > $1" [time.ago 24 :hr]

Duration & interval recipes (interval arithmetic IS available — time.add/diff exist):

end_at = time.add start_at dur :min            # duration: start + dur minutes
mins   = (time.diff end_at start_at) / 60       # gap between two times -> minutes
overlap = a.start < b.end & a.end > b.start     # do two intervals overlap? (bool)
buf_start = time.sub start_at 15 :min           # buffer: 15 min before start

IANA timezone / DSTtime.parse takes an optional zone name; time.fmt takes an optional zone as a third argument. Wall-clock ↔ UTC conversion is DST-aware (NOT a fixed offset), so "09:00 local every day" lands on the correct UTC instant across summer/winter transitions:

utc = time.parse "2026-07-15 09:00:00" "Asia/Tashkent"   # local wall-clock -> UTC
loc = time.fmt utc "HH:mm" "America/New_York"             # UTC instant -> zone wall-clock

A wall-clock time in a spring-forward gap (e.g. 02:30 on the night clocks jump) does not exist and raises an error; an unknown zone name raises too.

On this page