Standard Library
The standard library is written in Polar and lives in the
repository's std/ folder. Import a module with
uses Std.<Name>, then call its functions through
the module name.
Signatures use the notation from the main concepts: lowercase names
are type variables,
/ {…} lists effects,
{| e} passes effects through, and
where asks for a trait.
List
List<a> = Nil | Cons(a, List<a>) derive(Eq, Show)
Write lists with [1, 2, 3] rather than
Cons. List syntax needs uses Std.List.
| Function | Description |
|---|---|
range(from: Int, to: Int) -> List<Int>
|
Integers from from, up to but not including
to
|
is_empty(xs: List<a>) -> Bool
|
Whether the list has no items |
length(xs: List<a>) -> Int
|
The number of items |
map(xs: List<a>, f: function(a) -> b / {| e}) ->
List<b> / {| e}
|
Applies f to every item |
filter(xs: List<a>, keep: function(a) -> Bool / {|
e}) -> List<a> / {| e}
|
The items where keep is true |
fold(xs: List<a>, initial: b, f: function(b, a) ->
b / {| e}) -> b / {| e}
|
Combines the items from left to right, starting with
initial
|
head(xs: List<a>) -> Option<a>
|
The first item |
nth(xs: List<a>, index: Int) ->
Option<a>
|
The item at index, counting from 0 |
find(xs: List<a>, test: function(a) -> Bool / {|
e}) -> Option<a> / {| e}
|
The first item that passes test |
any(xs: List<a>, test: function(a) -> Bool / {| e})
-> Bool / {| e}
|
Whether at least one item passes |
all(xs: List<a>, test: function(a) -> Bool / {| e})
-> Bool / {| e}
|
Whether every item passes |
append(xs: List<a>, ys: List<a>) ->
List<a>
|
xs followed by ys |
concat_map(xs: List<a>, f: function(a) ->
List<b> / {| e}) -> List<b> / {| e}
|
Maps each item to a list, and joins the lists |
reverse(xs: List<a>) -> List<a>
|
The items in reverse order |
join(xs: List<String>, separator: String) ->
String
|
Joins strings with separator between them
|
Option
Option<a> = None | Some(a) derive(Eq, Show)
| Function | Description |
|---|---|
map(option: Option<a>, f: function(a) -> b / {| e})
-> Option<b> / {| e}
|
Applies f to the value, if there is one |
with_default(option: Option<a>, default: a) ->
a
|
The value, or default for None
|
and_then(option: Option<a>, f: function(a) ->
Option<b> / {| e}) -> Option<b> / {| e}
|
Chains a step that might also return None
|
Result
Result<e, a> = Err(e) | Ok(a) derive(Eq, Show)
| Function | Description |
|---|---|
map(result: Result<e, a>, f: function(a) -> b / {|
fx}) -> Result<e, b> / {| fx}
|
Applies f to an Ok value |
map_err(result: Result<e, a>, f: function(e) -> g /
{| fx}) -> Result<g, a> / {| fx}
|
Applies f to an Err value |
and_then(result: Result<e, a>, f: function(a) ->
Result<e, b> / {| fx}) -> Result<e, b> / {|
fx}
|
Chains a step that might also fail |
Map
Map<k, v> = Empty | Entry(k, v, Map<k, v>)
Keys are compared with Eq. Every change returns a new
map.
| Function | Description |
|---|---|
empty() -> Map<k, v> |
A map with no entries |
is_empty(map: Map<k, v>) -> Bool
|
Whether there are no entries |
size(map: Map<k, v>) -> Int
|
The number of entries |
get(map: Map<k, v>, key: k) -> Option<v>
where Eq<k>
|
The value for key |
has(map: Map<k, v>, key: k) -> Bool where
Eq<k>
|
Whether key is present |
insert(map: Map<k, v>, key: k, value: v) ->
Map<k, v> where Eq<k>
|
Adds or replaces an entry |
remove(map: Map<k, v>, key: k) -> Map<k, v>
where Eq<k>
|
Removes an entry |
keys(map: Map<k, v>) -> List<k>
|
All keys |
values(map: Map<k, v>) -> List<v>
|
All values |
to_list(map: Map<k, v>) -> List<{ key: k, value:
v }>
|
All entries as records |
Json
traits
Json<a> {
to_json(value: a) -> JsonValue
from_json(value: JsonValue, path: String) -> Result<JsonError, a>
}
types
JsonValue =
| JNull
| JBool(Bool)
| JNumber(Float)
| JString(String)
| JArray(List<JsonValue>)
| JObject(List<{ key: String, value: JsonValue }>)
JsonError = { path: String, expected: String, found: String }
Derive Json for your own types. Int,
Float, String, Bool,
List, Option and
Result implement it already. See
Working with JSON.
| Function | Description |
|---|---|
encode(value: a) -> String where Json<a>
|
JSON text for a value |
decode(text: String) -> Result<JsonError, a> where
Json<a>
|
Parses and checks JSON text |
message(error: JsonError) -> String
|
A readable description of an error |
For writing a Json implementation by hand, the module
also exports object, tagged,
object_fields, field,
variant and unknown_variant. They're the
building blocks derive(Json) uses.
Ref
Ref<a>
A mutable cell. See Mutable State.
| Function | Description |
|---|---|
new(value: a) -> Ref<a>
|
A new cell holding value |
get(ref: Ref<a>) -> a / {Mut}
|
The current value |
set(ref: Ref<a>, value: a) -> {} / {Mut}
|
Replaces the value |
update(ref: Ref<a>, f: function(a) -> a) -> {} /
{Mut}
|
Replaces the value with f(current) |
modify(ref: Ref<a>, f: function(a) -> { value: a,
result: b }) -> b / {Mut}
|
Replaces the value and returns result |
Table
Table<r> = Ref<{ next: Int, rows: List<{ id: Int, row: r }> }>
An in-memory table with automatic numeric ids.
| Function | Description |
|---|---|
new() -> Table<r> |
An empty table |
find(table: Table<r>, id: Int) -> Option<r> /
{Mut}
|
The row with that id |
all(table: Table<r>) -> List<r> / {Mut}
|
Every row, in insertion order |
size(table: Table<r>) -> Int / {Mut}
|
The number of rows |
insert(table: Table<r>, make: function(Int) -> r)
-> r / {Mut}
|
Adds the row make(id) and returns it |
update(table: Table<r>, id: Int, row: r) -> Bool /
{Mut}
|
Replaces a row. Returns whether it existed |
delete(table: Table<r>, id: Int) -> Bool /
{Mut}
|
Removes a row. Returns whether it existed |
Id
Id<a> = Id(Int)
A typed id. An Id<Post> and an
Id<User> are different types, so you can't pass
one where the other is expected. Id implements
Eq, Show and Json.
| Function | Description |
|---|---|
value(id: Id<a>) -> Int
|
The number inside |
Http
Header = { name: String, value: String }
Request = {
method: String,
path: String,
query: String,
headers: List<Header>,
body: String,
}
Response = { status: Int, headers: List<Header>, body: String }
| Function | Description |
|---|---|
header(headers: List<Header>, name: String) ->
String
|
The value of a header, matched without regard to case, or
"" if it's missing
|
Url
Pair = { name: String, value: String } derive(Eq, Show)
| Function | Description |
|---|---|
encode(text: String) -> String
|
Percent-encodes text for a URL component |
decode(text: String) -> String
|
Percent-decodes text. Never throws: invalid input comes back unchanged |
parse_query(text: String) -> List<Pair>
|
Parses a=1&b=2, with or without a leading
?. + becomes a space
|
build_query(pairs: List<Pair>) -> String
|
The reverse of parse_query |
Dom
Provides the Browser host and the
Dom effect. See
Running in the Browser.
effects
Dom in Browser {
append_text(tag: String, text: String) -> {}
append_html(html: String) -> {}
has(id: String) -> Bool
set_html(id: String, html: String) -> {}
}
Process
Provides the Node host and the
Process effect. See
Command-Line Programs.
types
EnvVar = { name: String, value: String }
RunOptions = { cwd: Option<String>, env: List<EnvVar> }
Output = { code: Int, stdout: String, stderr: String }
effects
Process in Node {
args() -> List<String>
env(name: String) -> Option<String>
cwd() -> String
set_exit_code(code: Int) -> {}
run(command: String, args: List<String>, options: RunOptions) -> Int
output(command: String, args: List<String>, options: RunOptions) -> Output
}
| Function | Description |
|---|---|
inherit() -> RunOptions |
Run in the current directory and environment |
in_dir(options: RunOptions, dir: String) ->
RunOptions
|
Run in dir instead |
with_env(options: RunOptions, name: String, value:
String) -> RunOptions
|
Add an environment variable |
Fs
Reads and writes text files on the Node host. See
Command-Line Programs.
types
FsError = { code: String, path: String, message: String } derive(Show)
effects
Fs in Node {
read(path: String) -> Result<FsError, String>
write(path: String, text: String) -> Result<FsError, {}>
append(path: String, text: String) -> Result<FsError, {}>
exists(path: String) -> Bool
is_dir(path: String) -> Bool
mkdir_all(path: String) -> Result<FsError, {}>
list(path: String) -> Result<FsError, List<String>>
walk(path: String) -> Result<FsError, List<String>>
remove(path: String) -> Result<FsError, {}>
remove_all(path: String) -> Result<FsError, {}>
}
| Operation | Description |
|---|---|
read |
The file's text, as UTF-8 |
write |
Creates or truncates the file. A missing parent directory is
ENOENT
|
append |
Creates the file or adds to its end |
exists, is_dir |
Whether the path exists, or is a directory. Never fail |
mkdir_all |
Creates a directory and its parents, like
mkdir -p. Fine if it exists
|
list |
The names in a directory, sorted |
walk |
Every file below a directory, as sorted paths relative to it
with / separators. Doesn't follow symlinks
|
remove |
Removes a file or an empty directory |
remove_all |
Removes a path and everything below it. Fine if it's missing |
An FsError's code is Node's:
ENOENT, EEXIST, ENOTDIR,
EACCES and so on, so you can match on it.
Path
Pure functions on POSIX paths, written in Polar. They don't touch the file system and work on every host.
| Function | Description |
|---|---|
join(base: String, part: String) -> String
|
Joins and normalizes. An absolute part wins:
join("a", "/b") is /b
|
dirname(path: String) -> String
|
dirname("/a/b.px") is /a,
dirname("b.px") is .
|
basename(path: String) -> String
|
basename("/a/b.px") is b.px |
extension(path: String) -> String
|
.px, or "" when there is none.
extension(".gitignore") is ""
|
normalize(path: String) -> String
|
Collapses ., .. and
//, keeping a leading /.
normalize("") is .
|
is_absolute(path: String) -> Bool
|
Whether the path starts with / |
relative(from: String, to: String) -> String
|
The path from from to to:
relative("/w/ticket", "/w/apps/demo") is
../apps/demo
|
Regex
Regular expressions, using the host's JavaScript syntax with the
u flag and no other flags. Matching is unanchored, so
write ^…$ to match the whole text. It works on both
Node and Browser. A pattern with
catastrophic backtracking, like (a+)+$, hangs as it
does in JavaScript. Regex has Eq and
Show, shown as /pattern/.
types
Regex = Regex(String)
| Function | Description |
|---|---|
compile(pattern: String) -> Result<String, Regex>
|
Ok for a valid pattern, or Err with the engine's message |
pattern(regex: Regex) -> String
|
The string given to compile |
is_match(regex: Regex, text: String) -> Bool
|
Whether the pattern matches anywhere in text |
find(regex: Regex, text: String) -> Option<String>
|
The first match's text, or None. An empty match is Some("") |
find_all(regex: Regex, text: String) -> List<String>
|
Every non-overlapping match, in order |
replace(regex: Regex, text: String, with: String) -> String
|
Replaces every match with with verbatim; $1 is not expanded |
matches(pattern: String, text: String) -> Bool
|
is_match of compile(pattern), and false for a bad pattern |
Time
An instant in UTC, held as milliseconds since the Unix epoch, and
the Clock effect that reads it.
Clock works on both Node and
Browser. A program can bind it itself, to pin the time
in a test. Time has Eq,
Show and Json: its JSON form is an
ISO-8601 string like "2026-10-02T09:30:00.123Z".
types
Time = Time(Int)
effects
Clock {
now() -> Time
}
| Function | Description |
|---|---|
from_millis(ms: Int) -> Time |
Builds a Time from milliseconds since the epoch
|
millis(time: Time) -> Int |
The milliseconds since the epoch |
to_iso(time: Time) -> String |
UTC with milliseconds: 2026-10-02T09:30:00.123Z
|
from_iso(text: String) -> Option<Time>
|
Accepts YYYY-MM-DDTHH:MM:SS[.fff](Z|±HH:MM) and
SQLite's YYYY-MM-DD HH:MM:SS[.fff] (read as
UTC). Anything else, like 2026-02-30, is
None
|
compact(time: Time) -> String
|
YYYYMMDD_HHMMSS in UTC:
20261002_093000
|
compare(a: Time, b: Time) -> Int
|
-1, 0 or 1 |
before(a: Time, b: Time) -> Bool
|
Whether a is earlier than b |
Crypto
Hashing, signing and random tokens over node:crypto.
Crypto works on Node only, so a
Browser build can't use Random.
Randomness is the Random effect, so a function that
makes a token says so in its type, and a program can bind
Random to a fixed value in a test. There is no
Bytes type: random bytes are a hex string.
effects
Random {
bytes(count: Int) -> String
}
| Function | Description |
|---|---|
sha256(text: String) -> String |
Lowercase hex SHA-256 of the string's UTF-8 bytes |
hmac_sha256(key: String, text: String) -> String |
Lowercase hex HMAC-SHA-256 of text under key |
equal(a: String, b: String) -> Bool |
Constant-time comparison; false for different lengths. Use it to check a signature |
base64url(text: String) -> String |
URL-safe base64 of the UTF-8 bytes, no padding |
from_base64url(text: String) -> Option<String> |
Decodes base64url output; None for invalid text |
Random.bytes(count: Int) -> String |
count random bytes as 2 × count lowercase hex characters |
Assert
Failed = Failed(String)
| Function | Description |
|---|---|
assert(check: Bool) -> {} / {Throws<Failed>}
|
Throws Failed when check is false
|
equal(actual: a, expected: a) -> {} /
{Throws<Failed>} where Eq<a>,
Show<a>
|
Throws
Failed("expected <expected>, got
<actual>")
when the values differ
|
not_equal(actual: a, other: a) -> {} /
{Throws<Failed>} where Eq<a>,
Show<a>
|
Throws
Failed("expected a value other than <other>")
when the values are equal
|
fail(message: String) -> a / {Throws<Failed>}
|
Always throws Failed(message). It has any type,
so it fits any match arm
|
Math
| Constant | Description |
|---|---|
pi: Float |
3.141592653589793 |
Prelude
Always in scope, without a uses.
traits
Eq<a> {
eq(x: a, y: a) -> Bool
}
Show<a> {
show(value: a) -> String
}
Int, Float, String and
Bool implement both. == uses
Eq, and #{…} uses Show.