x-lang

x-lang Specification

Version: 0.5.2

This document is the normative specification for x-lang. Each section maps 1:1 to a test file in tests/x/specs/. Behavior described here MUST be implemented and tested. Items marked TBD have uncertain semantics and need investigation.

All primitives receive unevaluated arguments (fexpr-style) and evaluate what they need internally. Boolean true is #t; boolean false is #f. Nil is () (the empty list).

x-lang uses the fexpr evaluation model: every combiner at the C level receives its arguments unevaluated. Applicative semantics (automatic argument evaluation) are provided by fn, which wraps a closure. This is the inverse of most Lisps where functions evaluate arguments by default and macros are the special case. In x-lang, operatives (op) are the default and applicatives (fn) are the special case. This follows the Kernel language design.


1. Evaluation Model

Self-evaluation

Integers, strings, and characters evaluate to themselves.

42 -> 42
"hello" -> "hello"
#\a -> #\a

Symbol lookup

A symbol evaluates to the value bound to it in the current environment. An unbound symbol signals an error.

(def x 10)
x -> 10

List evaluation

A list (f args ...) evaluates f to obtain a combiner, then applies it. For applicatives (created by fn or wrap), each argument is evaluated before the call. For operatives (created by op or C primitives), arguments are passed unevaluated.

Calling an applicative with an IMPROPER argument list – (list 1 . 5) – is an error: the argument walk raises call: improper argument list (dotted tail) rather than reading past the spine (#69 ruled). Operatives are untouched: they receive their spines raw, and a dotted parameter spec binds a dotted tail legitimately. When f evaluates to a non-callable value the form is DATA, not a call, and echoes back unchanged – proper or dotted (see Lists, section 10).

(guard (e "caught") (list 1 . 5)) -> "caught"
(1 . 2) -> (1 . 2)

Nil, false, and truthiness

The empty list () is nil. Nil, the empty list, and absence are ONE value — the pun is deliberate: a list is its first pair or the absence of one, and nil’s representation (the null object) is what makes every list-termination test free. The boolean false value is #f, a distinct canonical atom carried on the base. () self-evaluates.

() -> ()

Truthiness: exactly two values are falsy — nil and #f. Everything else is truthy, including 0, "", and empty vectors/dicts (which are real objects, distinct from nil; only lists pun empty with absence).

This model (Model A) is a settled, adjudicated decision. The design record — including the priced migration analysis for the nil ≠ () alternative (Model C), kept for any future major version — is issue #41; do not relitigate the model without reading it.

The absence discipline (normative for the standard library):

  1. Predicates answer #t/#f, never a useful value.
  2. Absence is nil: lookup and find misses return () — never #f.
  3. An API whose slots can legally store nil or #f must offer a presence door — has?, or a presence-based -or/-or-else default — never a value sentinel. (Internal walkers use one-element boxes.)
  4. Index-search misses return () like every other miss. Negative indexes are valid positions — they count from the end on strict indexed collections (Gen excepted: a lazy stream has no end) — so no number is ever a miss, and checked refs reject a nil index loudly. (OS-boundary tables keep the OS’s own -1 invalid marker, per rule 5.)
  5. Boundaries (e.g. JSON) carry a foreign null as the symbol null; () crossing a boundary always means the empty sequence.
  6. and/or are value operators: and normalizes failure to #f (it answers “did all pass”); or returns its first truthy value, and when nothing is truthy it passes its LAST operand through unchanged – so (or () #f) is #f and (or #f ()) is (). (or) is (). Use not if you need a normalized answer rather than the operand.

Tail-call optimization

The following forms evaluate their final expression in tail position:

Proper tail calls MUST NOT grow the stack. A tail-recursive loop MUST be able to iterate without limit.

(def loop (fn (self n) (if (= n 0) #t (self (- n 1)))))
(loop 1000000) -> #t

Mutual recursion

Mutually tail-recursive functions MUST also run in constant stack space.

(def even-tc (fn (_ n) (if (= n 0) #t (odd-tc (- n 1)))))
(def odd-tc (fn (_ n) (if (= n 0) #f (even-tc (- n 1)))))
(even-tc 100000) -> #t

2. Core Forms

lit

(lit expr) -> expr

Returns expr unevaluated. This is the quoting primitive. The reader provides 'expr as shorthand for (lit expr) (see core/quote-reader.spec.md).

(lit (+ 1 2)) -> ('+ 1 2)
(lit abc) -> 'abc
'abc -> 'abc
'(1 2 3) -> (1 2 3)

pair

(pair a b) -> (a . b)

Constructs a pair from evaluated a and b.

(pair 1 2) -> (1 . 2)
(pair 1 (pair 2 ())) -> (1 2)

first

(first p) -> obj

Returns the first element of pair p. Calling (first ()) is undefined — in practice it dereferences nil and takes the process down. Importing x/tool/safe-access shadows first and rest with guarded closures so both raise instead; it is opt-in because the library walks its own lists through the same globals and the guard costs 1.4x–1.7x.

(first (pair 1 2)) -> 1
(first (list 10 20 30)) -> 10

rest

(rest p) -> obj

Returns the rest element of pair p. Calling (rest ()) is undefined; see first for the opt-in guard.

(rest (pair 1 2)) -> 2
(rest (list 10 20 30)) -> (20 30)

list

(list a ...) -> (a ...)

Constructs a proper list from zero or more evaluated arguments.

(list 1 2 3) -> (1 2 3)
(list) -> ()

def

(def name expr) -> value

Binds name (unevaluated symbol) to the result of evaluating expr in the current environment. expr is evaluated before the binding is created, so it cannot reference the binding being defined. Recursive functions still work: a closure body resolves names when the closure is called, by which time the def has completed. For a definition that needs the name while expr itself evaluates, forward-declare it: (def name ()) then (set! name expr). def always creates a new binding; it shadows any existing binding with the same name rather than replacing it.

(def x 42) -> 42
(def fact (fn (_ n) (if (= n 0) 1 (* n (fact (- n 1))))))
(fact 5) -> 120

set!

(set! name expr) -> value

Mutates an existing binding of name to the result of evaluating expr. Walks the scope chain to find the nearest enclosing binding of name and modifies it in place. Signals an error if name is not bound in any scope.

(def x 1)
(set! x 2)
x -> 2

if

(if cond then [else]) -> value

Evaluates cond. If truthy, tail-evaluates then. If falsy, tail-evaluates else (or returns () if omitted).

(if #t 1 2) -> 1
(if () 1 2) -> 2
(if () 1) -> ()

do

(do form ...) -> value

Evaluates each form in sequence and returns the last value. The final form is tail-evaluated. With no arguments, returns ().

(do 1 2 3) -> 3
(do (def x 1) (+ x 1)) -> 2

match

(match (test expr) ...) -> value

Multi-branch conditional. Evaluates each test in order; for the first truthy test, tail-evaluates the corresponding expr and returns it. Returns () if no test succeeds. Each clause has exactly ONE body form; for multiple expressions, wrap in do.

(match
  ((= 1 2) 10)
  ((= 1 1) 20)
  (#t 30)) -> 20

let

(let ((name val) ...) body ...) -> value

Creates local bindings, evaluates body forms in the extended environment, and returns the last value. The final body form is tail-evaluated. Environment is restored after let completes.

(let ((x 1) (y 2)) (+ x y)) -> 3
(let ((x 10)) x) -> 10

List indexing

Lists support direct indexing when called as functions. A single integer argument returns the element at that zero-based index. Negative indices count from the end. Two integer arguments (lst start len) return a sublist.

(def xs (list 10 20 30 40))
(xs 0) -> 10
(xs 2) -> 30
(xs -1) -> 40
(xs 1 2) -> (20 30)

3. Closures & Operatives

fn

(fn (params ...) body ...) -> procedure

Creates a closure (applicative). Arguments are evaluated before binding. Every closure receives itself as an implicit first argument: a call (add 1 2) binds the first formal to the add closure itself, the second to 1, and the third to 2. By convention the first formal is named _ when unused, or self when the body recurses through it — self-recursion needs no global name and survives rebinding. Supports variadic: if params is a single symbol, it captures the entire argument list, whose head is the closure itself. A dotted-pair parameter list (_ a . rest) binds named parameters and collects remaining arguments into rest.

(def add (fn (_ a b) (+ a b)))
(add 1 2) -> 3
(def fact (fn (self n) (if (= n 0) 1 (* n (self (- n 1))))))
(fact 5) -> 120
(def id (fn args (rest args)))
(id 1 2 3) -> (1 2 3)
(def f (fn (_ a b . rest) rest))
(f 1 2 3 4 5) -> (3 4 5)

Closures capture their lexical environment:

(def make-adder (fn (_ n) (fn (_ x) (+ n x))))
((make-adder 10) 5) -> 15

op

(op formals env-param body ...) -> operative

Creates an operative (fexpr). Like fn, but arguments are NOT evaluated. formals binds the raw argument tree, env-param binds the dynamic environment.

(def my-quote (op (x) e x))
(my-quote (+ 1 2)) -> ('+ 1 2)

The environment parameter can be used for selective evaluation:

(def my-if (op (c t f) e (if (eval c e) (eval t e) (eval f e))))
(my-if (= 1 1) "yes" "no") -> "yes"

wrap

(wrap combiner) -> applicative

Wraps a combiner to create an applicative that evaluates arguments before passing them to the underlying combiner.

(def my-op (op (x) e x))
(def my-fn (wrap my-op))
(my-fn (+ 1 2)) -> 3

unwrap

(unwrap applicative) -> combiner

Extracts the underlying combiner from an applicative. Calling unwrap on a value that was not created by wrap is undefined behaviour.

(def my-op (op (x) e x))
(def my-fn (wrap my-op))
((unwrap my-fn) (+ 1 2)) -> ('+ 1 2)

apply

(apply f args) -> value

Calls callable f with a pre-evaluated list of arguments. Arguments are not re-evaluated. When applying a C primitive, the arguments must be self-evaluating values (integers, strings, etc.) since primitives may internally evaluate their arguments.

(apply + (list 1 2 3)) -> 6
(apply list (list 1 2 3)) -> (1 2 3)

eval

(eval expr [env]) -> value

Evaluates expression expr. With optional env, evaluates in that environment.

(eval '(+ 1 2)) -> 3

4. Logic & Control

and

(and expr ...) -> value

Short-circuit logical AND. Evaluates each expr left to right. Normalizes failure to #f at the first falsy value – see the absence discipline in section 1, which this section previously contradicted. If all truthy, returns the last value. (and) returns #t.

(and 1 2 3) -> 3
(and 1 () 3) -> #f
(and) -> #t

or

(or expr ...) -> value

Short-circuit logical OR. Returns the first truthy value. If every operand is falsy, the LAST operand passes through unchanged – or does not normalize its failure the way and does, so the result is whichever of () or #f you supplied last. (or) returns ().

(or () () 3) -> 3
(or 1 2) -> 1
(or () #f) -> #f
(or #f ()) -> ()
(or) -> ()

not

(not x) -> #t | #f

Logical negation. Returns #t if x is falsy; #f otherwise.

(not ()) -> #t
(not 1) -> #f
(not #t) -> #f

guard

(guard (var handler-body ...) body ...) -> value

Error recovery. Evaluates body forms. If an error is signalled, binds the error value to var and evaluates handler-body instead. Handlers nest.

The error value depends on the raiser: (error x) delivers x itself, (Err raise ...) delivers an Err instance, and C-raised errors (an unbound symbol, the built-in guards) deliver a message-carrying atom that prints as its message – (display (guard (e e) nosuchsym)) shows Unbound SYMBOL 'nosuchsym', and an uncaught one prints the same text after Error: (#54).

(guard (e e) (error "oops")) -> "oops"
(guard (e "caught") (+ 1 2)) -> 3

Nested guards:

(guard (e (Str8 append "outer: " e))
  (guard (e (error (Str8 append "re: " e)))
    (error "inner"))) -> "outer: re: inner"

error

(error message) -> <does not return>

Signals an error. If a guard handler is installed, the error is caught. Without a handler, error terminates the process.

(guard (e e) (error "fail")) -> "fail"

5. Arithmetic

All arithmetic operators are variadic and evaluate their arguments.

+

Arithmetic operands must be numbers. A NIL operand raises (the prims share eq?’s nil-safety convention), and a non-numeric operand – string, list, pair, vector – raises err:type through the same registry that dispatches the numeric tower: each of those types registers refusal handlers for + - * / % <, so (+ 1 "abc") errors instead of reading the string’s pointer as an integer (#52). CHARACTERS are exempt by contract: a char IS its code point arithmetically – (- #\3 #\0) is 3 – and the regex engine, utf8 decode, and the printer all depend on it. One documented residual: symbols (their type slot is the interning tree; the registry cannot carry ops for them) still fall through to machine arithmetic. The booleans are a real BOOL type (#101) – (Type of #t) answers, and (+ #t 1) refuses through the same registry.

(Type name (Type of #t)) -> "BOOL"
(guard (e "caught") (+ #t 1)) -> "caught"

(~ & | ^ << >>) is stricter: integer or char operands only, enforced in its wrappers.

(guard (e "caught") (+ 1 ())) -> "caught"
(guard (e "caught") (+ 1 "abc")) -> "caught"
(guard (e "caught") (& "a" 1)) -> "caught"

(+ a ...) -> integer

Addition. Identity: 0.

(+ 1 2 3) -> 6
(+) -> 0
(+ 5) -> 5

-

(- a ...) -> integer

Subtraction. One argument: negation. Zero arguments: 0.

(- 5 3) -> 2
(- 5) -> -5
(- 10 3 2) -> 5
(-) -> 0

*

(* a ...) -> integer

Multiplication. Identity: 1.

(* 2 3 4) -> 24
(*) -> 1

/

(/ a ...) -> integer

Integer division. Identity: 1. Division by an integer zero raises an error (#80); it was previously undefined, and in practice took an uncatchable hardware trap. Boxed tower zeros are not affected — a float divisor keeps IEEE semantics, a rational zero keeps the rational constructor’s own error.

(/ 10 2) -> 5
(/ 100 2 5) -> 10
(/) -> 1

%

(% a ...) -> integer

Integer modulo. Unlike + - * /, % has no identity element: calling it with no arguments is an error, not 0 (#72). One argument passes through. Modulo by an integer zero raises an error (#80), as with /.

(% 10 3) -> 1
(% 17 10 3) -> 1
(% 7) -> 7

~

(~ n) -> integer

Bitwise NOT (one’s complement).

(~ 0) -> -1
(~ -1) -> 0

&

(& a b) -> integer

Bitwise AND.

(& 6 3) -> 2
(& 255 15) -> 15

|

(| a b) -> integer

Bitwise OR.

(| 6 3) -> 7
(| 0 5) -> 5

^

(^ a b) -> integer

Bitwise XOR.

(^ 6 3) -> 5
(^ 5 5) -> 0

<<

(<< a b) -> integer

Left shift.

(<< 1 4) -> 16
(<< 3 2) -> 12

>>

(>> a b) -> integer

Right shift (arithmetic).

(>> 16 4) -> 1
(>> 12 2) -> 3

6. Predicates

eq?

(eq? a b) -> #t | #f

Scalar-value identity: the same object, or two scalars (integers, characters) carrying the same value. Symbols with the same name are interned and thus eq?. Use same? for strict object identity.

(eq? 'x 'x) -> #t
(eq? 1 1) -> #t
(eq? "a" "a") -> #f

=

(= a b) -> #t | #f

Numeric/value equality. Compares integer values (and characters by code point). Comparing values of different types is undefined.

(= 1 1) -> #t
(= 1 2) -> #f

<

(< a b) -> #t | #f

(< 1 2) -> #t
(< 2 1) -> #f

>

(> a b) -> #t | #f

(> 2 1) -> #t
(> 1 2) -> #f

<=

(<= a b) -> #t | #f

(<= 1 1) -> #t
(<= 2 1) -> #f

>=

(>= a b) -> #t | #f

(>= 1 1) -> #t
(>= 0 1) -> #f

null?

(null? x) -> #t | #f

Returns #t if x is nil.

(null? ()) -> #t
(null? 1) -> #f

pair?

(pair? x) -> #t | #f

Returns #t if x is a pair.

(pair? (list 1 2)) -> #t
(pair? 1) -> #f

atom?

(atom? x) -> #t | #f

Returns #t if x is not a pair. Inverse of pair?.

(atom? 1) -> #t
(atom? (list 1 2)) -> #f

number?

(number? x) -> #t | #f

(number? 42) -> #t
(number? "hello") -> #f

str?

(str? x) -> #t | #f

(str? "hello") -> #t
(str? 42) -> #f

symbol?

(symbol? x) -> #t | #f

(symbol? 'x) -> #t
(symbol? 42) -> #f

procedure?

(procedure? x) -> #t | #f

Returns #t if x is a fn closure, a wrap applicative, or a C primitive. Returns #f for op operatives and all other values.

(procedure? +) -> #t
(procedure? (fn (_ x) x)) -> #t
(procedure? 42) -> #f

char?

(char? x) -> #t | #f

Returns #t if x is a character object.

(char? #\a) -> #t
(char? 42) -> #f

Char ->int

(Char ->int c) -> integer (the bare char->integer global was de-registered in R5; the class — or (prim-ref 'char '->int) for load-time/hot fetches — is the surface)

Returns the integer code point of character c. Passing a non-character value is undefined.

(Char ->int #\a) -> 97
(Char ->int #\A) -> 65

Char from-int

(Char from-int n) -> char (the bare integer->char global was de-registered in R5; the class — or (prim-ref 'int '->char) — is the surface)

Returns the character with code point n.

(Char from-int 97) -> #\a
(Char from-int 65) -> #\A
(= (Char from-int 97) #\a) -> #t

7. Strings

Str8 length

(Str8 length str) -> integer

Returns the byte length of string str (not character count; x-lang strings are byte arrays with no encoding awareness). The bare str-length spelling is retired (#108): the boot layer keeps it %-private; the class is the surface.

(Str8 length "hello") -> 5
(Str8 length "") -> 0

Str8 ref

(Str8 ref index str) -> char

Returns the character at zero-based index in str (index first, the adjudicated seat order; negative counts from the end). The bare str-ref spelling is retired (#108).

(Str8 ref 0 "hello") -> #\h
(Str8 ref 4 "hello") -> #\o

Str8 append

(Str8 append str1 str2 ...) -> string (ns str is de-registered: the class – or (prim-ref 'str 'append) for load-time/hot fetches – is the surface)

Concatenates exactly two strings. For multiple strings, use Str append (variadic).

(Str8 append "hello" " world") -> "hello world"
(Str8 append "" "x") -> "x"

Str8 sub

(Str8 sub start len str) -> string

Extracts len bytes starting at byte offset start (count-first seats, the adjudicated order; start+length, not start+end). The bare substring spelling is retired (#108).

(Str8 sub 1 2 "hello") -> "el"
(Str8 sub 0 5 "hello") -> "hello"

str=?

(str=? str1 str2) -> #t | #f

String content equality.

(str=? "abc" "abc") -> #t
(str=? "abc" "xyz") -> #f

Str8 ->sym

(Str8 ->sym str) -> symbol

Converts a string to an interned symbol.

(Str8 ->sym "hello") -> 'hello

symbol->str

(symbol->str sym) -> string

Converts a symbol to a string.

(symbol->str 'hello) -> "hello"

number to string

(Convert to n (Type of "")) -> string

Converts an integer to its decimal string representation through the conversion catalog. The bare number->str spelling is retired (#108); the boot layer keeps it %-private for the printer’s hot path.

(Convert to 42 (Type of "")) -> "42"
(Convert to -1 (Type of "")) -> "-1"

string to number

(Convert to str (Type of 0) [radix]) -> integer | ()

Parses string as an integer. A 0x/0X prefix selects hex, matching the reader’s literal; the sign parses first, so "-0xff" is -255. An explicit radix argument disables prefix detection – the caller controls interpretation, and digits run to radix 36. Non-numeric strings miss with (), like every other miss; 0 would be indistinguishable from parsing "0". (This section previously claimed a 0 fallback – stale text predating the nil model – and documented the hex prefix before it was implemented; #76 ruled it in.)

(Convert to "42" (Type of 0)) -> 42
(Convert to "0xff" (Type of 0)) -> 255
(Convert to "-0xff" (Type of 0)) -> -255
(Convert to "ff" (Type of 0) 16) -> 255
(Convert to "abc" (Type of 0)) -> ()
(guard (e "caught") (Convert to "12345678901234567890" (Type of 0))) -> "caught"

Digits that overflow the machine integer RAISE rather than wrapping – a literal parse silently becoming a different number corrupted 64-bit IDs in JSON (#52). Accumulation is negative-domain, so INT_MIN parses exactly.


8. I/O

write

(write obj) -> ()

Outputs the s-expression representation of obj to stdout. Strings are quoted, special characters escaped. Returns ().

(write "hello")   ; outputs: "hello"
(write 42)        ; outputs: 42
(write (list 1 2)) ; outputs: (1 2)

display

(display obj) -> ()

Outputs human-readable representation. Strings are printed without quotes. Returns ().

(display "hello") ; outputs: hello
(display 42)      ; outputs: 42

newline

(newline) -> ()

Outputs a newline character.

Io read

(Io read) -> obj

Reads and parses one s-expression from stdin. Behavior at EOF is implementation-dependent.

Io read-char

(Io read-char) -> char | ()

Reads a single character from stdin. Returns () on end-of-input.

Heap collect

(Heap collect) -> integer

Triggers garbage collection.


9. Quasiquote

quasi

(quasi template) -> obj

Quasiquote. Returns template with unquote and unquote-splicing forms evaluated.

(def x 1)
(quasi (a (unquote x) b)) -> ('a 1 'b)

unquote

(unquote expr) – only valid inside quasi.

Evaluates expr and substitutes the result.

(def x 42)
(quasi (unquote x)) -> 42

unquote-splicing

(unquote-splicing expr) – only valid inside quasi.

Evaluates expr (must produce a list) and splices it into the surrounding list.

(def xs (list 2 3))
(quasi (1 (unquote-splicing xs) 4)) -> (1 2 3 4)

Nested quasiquote is depth-tracked: each quasi deepens by one, each unquote returns one level, and only a depth-1 payload evaluates – so the inner form survives one wrapping as syntax while the innermost value is substituted. The printer renders the surviving quasi form with the reader’s shorthand:

(quasi (quasi (unquote (unquote 'x)))) -> `,'x

10. Reader Syntax

The reader converts text into s-expressions. The following syntactic forms are supported:

Integers

Sequences of digits, optionally preceded by - for negative numbers.

42 -> 42
-7 -> -7
0 -> 0

Strings

Delimited by ". Strings support C-style backslash escape sequences:

Escape Byte Name
\" 0x22 double quote
\\ 0x5C backslash
\n 0x0A newline
\t 0x09 tab
\r 0x0D carriage return
\0 0x00 null
\xHH HH hex byte

Escape sequences are processed at read time: "\n" is a one-character string containing a newline byte. Unknown escape sequences (e.g., \q) preserve the literal backslash and following character. Invalid \x sequences (not followed by two hex digits) also preserve the literal characters.

The write function re-escapes special characters so that the output is a valid string literal: (write "\n") prints "\n", not a raw newline.

Note: \0 produces a null byte, which terminates the string for all operations that use byte-length (e.g., str-length, (Str8 append)).

"hello" -> "hello"
"" -> ""
"a\"b" -> "a\"b"
"a\\b" -> "a\\b"

Symbols

Sequences of non-whitespace, non-parenthesis, non-quote characters that don’t parse as integers.

abc -> <symbol>
+ -> <symbol>
my-var? -> <symbol>

Characters

#\c where c is a single character. Named characters are also supported:

Syntax Character Code
#\space space 32
#\newline newline (LF) 10
#\tab horizontal tab 9
#\a -> #\a
(Char ->int #\space) -> 32
(Char ->int #\newline) -> 10
(Char ->int #\tab) -> 9

Lists

(a b c) creates a proper list. (a b . c) creates a dotted pair where c is the tail. Bare data forms – proper or dotted – evaluate to themselves through the non-callable pass-through (#69 ruled: a non-callable head was never a call, so data echoes back; see List evaluation in section 1 for the callable half of that ruling).

(1 2 3) -> (1 2 3)
(1 . 2) -> (1 . 2)
(1 2 . 3) -> (1 2 . 3)

Quote shorthand

'expr is sugar for (lit expr).

'abc -> 'abc
'(1 2 3) -> (1 2 3)

Quasiquote shorthand

`expr is sugar for (quasi expr). ,expr is sugar for (unquote expr). ,@expr is sugar for (unquote-splicing expr).

Comments

; begins a line comment; everything until end-of-line is ignored.

; this is a comment
42 ; this is also a comment -> 42

Vector literals

#(a b c) creates a vector.

#(1 2 3) -> #(1 2 3)

Regex literals

#/pattern/ creates a regex.

#/abc/ -> #/abc/
#/a.*b/ -> #/a.*b/

11. Type Extension

make-type

(Type make name handlers) -> type-handle

Creates a new runtime type with string name and an alist of handlers. Supported handler keys: call, write, length, analyse, delimit. Returns a type handle used with make-instance and type?.

(def my-t (Type make "MY-T" (list)))

make-instance

(Type make-instance type-handle data) -> instance

Creates a new instance of the type. Data is stored and accessible via (first instance).

(def my-t (Type make "MY-T" (list)))
(def obj (Type make-instance my-t 42))
(first obj) -> 42

Custom type instances self-evaluate:

(def obj (Type make-instance my-t 42))
obj -> <instance>

type?

(Type ? obj type-handle) -> #t | #f

Returns #t if obj’s runtime type matches type-handle.

(Type ? obj my-t) -> #t
(Type ? 42 my-t) -> #f

type-name

(Type name obj) -> string | ()

Returns the name string of obj’s type, or () if no type.

(Type name obj) -> "MY-T"
(Type name 42) -> "INTEGER"
(Type name "hi") -> "STRING"

score-match

(score-match score length reader) -> score

Sets the score fields for the tokenizer protocol. length is the match length, reader is the read function to call. Used internally by custom type readers.

(score-match score 5 my-reader) -> ...

(Illustrative: score and my-reader come from the worked reader above, so the result is the mutated score object, not a printable literal.)

Call handler

When a typed instance is called as a function, the call handler is invoked with the instance followed by the arguments. Like every closure, the handler also receives itself as implicit argument 0, so the instance binds as the second formal.

(def counter-t (Type make "COUNTER"
  (list (pair 'call (fn (_ self . args) (first self))))))
(def c (Type make-instance counter-t 42))
(c) -> 42

Write handler

When write or display outputs a typed instance, the write handler is called with the instance (after the closure’s implicit self slot).

(def my-t (Type make "SHOW"
  (list (pair 'write (fn (_ self) (display "[") (display (first self)) (display "]"))))))

Type reflection

(Type wrap t) -> instance

Clothes a type handle (from Type of) or the type itself (from Type by-atom) as an interactive Type instance carrying both forms: the handle member is the name atom, raw the struct the wiring statics consume.

((Type wrap (Type of 0)) name) -> "INTEGER"
((Type wrap (Type by-atom (Type of 0))) name) -> "INTEGER"

(t cell 'field-name) walks the layout contract (engine/tools/contract/base-paths.x) to the object the type-rooted row for field-name addresses — handler stacks, the conversion catalog cells, the generic-operator alist. (t fields) lists the row names. A name whose row is not type-rooted is refused: a base-rooted path stepped from a type would address arbitrary spine words.

(null? ((Type wrap (Type of 0)) cell 'type-ops-stack)) -> #f
(null? (List filter (fn (_ n) (eq? n 'type-iter)) ((Type wrap (Type of 0)) fields))) -> #f
(guard (e 'refused) ((Type wrap (Type of 0)) cell 'line)) -> 'refused

The push verbs wire handlers through the instance — push-write, push-display, push-call, push-op — shadowing the current handler, and (Type pop-write (t raw)) restores the write stack. Restyling a shared built-in is a shadow-then-pop round trip (illustrative; the round trip is pinned by tests/x/specs/lib/type.spec.md):

(def t-int (Type wrap (Type of 0)))
(t-int push-write (fn (_ n) (display (Str8 append "0x" (%number->str n 16)))))
; the write/echo mode now renders integers as 0x2a ...
(Type pop-write (t-int raw))
; ... and is restored byte-for-byte

12. Sandboxing

Base make

(Base make) -> instance

Creates a fresh, sandboxed interpreter — all built-in types and C primitives, no library — wrapped as a Base instance. The raw C base object rides the instance’s raw member; every Base static accepts either form, and (Base raw-of v) unwraps. A fresh child is the bare C ISA: no output verbs, no catalog protocol, no reader macros — reach in with parent closures or bind.

(def b (Base make))
(Base base? b) -> #t
(Base base? (b raw)) -> #f
(Base base? 5) -> #f

Base eval

(Base eval base expr) -> value

Evaluates expr in the target base environment.

(def b (Base make))
(Base eval b '(+ 1 2)) -> 3

Bases are isolated:

(def b (Base make))
(Base eval b '(def x 42))
(Base eval b 'x) -> 42

Base bind

(Base bind base name value) -> value

Binds name to value in the target base.

(def b (Base make))
(Base bind b 'x 42)
(Base eval b 'x) -> 42

Base instances

The instance answers eval, bind, and make-type directly — the receiver is the base:

(def b (Base make))
(b eval '(* 6 7)) -> 42
(def b (Base make))
(b bind 'x 5)
(b eval 'x) -> 5

The statics keep working on raw bases from the catalog prims — plumbing that holds a raw base passes it straight through:

(def rb ((prim-ref 'base 'make)))
(Base eval rb '(+ 40 2)) -> 42

Base field reflection

(b cell 'field-name) walks the layout contract (engine/tools/contract/base-paths.x) to the object the base-rooted row for field-name addresses; (Base fields) lists the row names. A cell-kind field’s value sits in the cell’s first slot. A name whose row is not base-rooted is refused — a type-rooted path stepped from a base spine would address arbitrary interpreter state.

(def b (Base make))
(null? (List filter (fn (_ n) (eq? n 'type-alist)) (b fields))) -> #f
(def b (Base make))
(b bind 'marker 77)
(list (b eval 'marker) (null? (rest (b cell 'env-root)))) -> (77 #t)
(def b (Base make))
(guard (e 'refused) (b cell 'type-iter)) -> 'refused

13. Lib: Combinators

Standard library functions for function composition and transformation.

Fn identity

(Fn identity x) -> x

Returns its argument unchanged.

(Fn identity 42) -> 42

Fn const

(Fn const x) -> (fn (_ y) x)

Returns a function that always returns x.

((Fn const 5) 99) -> 5

Fn compose

(Fn compose f g) -> (fn (_ x) (f (g x)))

Right-to-left function composition.

((Fn compose (method-ref Num inc) (method-ref Num inc)) 3) -> 5

Fn pipe

(Fn pipe f g) -> (fn (_ x) (g (f x)))

Left-to-right function composition.

((Fn pipe (method-ref Num inc) (method-ref Num inc)) 3) -> 5

Fn curry

(Fn curry f x) -> (fn (_ y) (f x y))

Partially applies a two-argument function by fixing its first argument.

((Fn curry + 10) 5) -> 15

Fn flip

(Fn flip f) -> (fn (_ a b) (f b a))

Reverses the arguments of a binary function.

((Fn flip -) 1 10) -> 9

Fn tap

(Fn tap f) -> (fn (_ x) ...x)

Returns a function that applies f for side effects, then returns the argument.

((Fn tap write) 42) -> 42

Fn complement

(Fn complement pred) -> function

((Fn complement (method-ref Num even?)) 3) -> #t

Fn partial

(Fn partial f . bound) -> function

((Fn partial + 10) 5) -> 15

Fn juxt

(Fn juxt . fns) -> function

((Fn juxt (method-ref Num inc) (method-ref Num dec)) 5) -> (6 4)

Fn both

(Fn both f g) -> function

((Fn both (method-ref Num positive?) (method-ref Num even?)) 4) -> #t
((Fn both (method-ref Num positive?) (method-ref Num even?)) 3) -> #f

Fn either

(Fn either f g) -> function

((Fn either (method-ref Num positive?) (method-ref Num even?)) -2) -> #t

Fn all-pass

(Fn all-pass preds) -> function

((Fn all-pass (list (method-ref Num positive?) (method-ref Num even?))) 4) -> #t

Fn any-pass

(Fn any-pass preds) -> function

((Fn any-pass (list (method-ref Num positive?) (method-ref Num even?))) -2) -> #t

14. Lib: Math

Num inc

(Num inc n) -> integer

(Num inc 5) -> 6
(Num inc -1) -> 0

Num dec

(Num dec n) -> integer

(Num dec 5) -> 4
(Num dec 0) -> -1

Num negate

(Num negate n) -> integer

(Num negate 7) -> -7
(Num negate -3) -> 3

Num abs

(Num abs n) -> integer

(Num abs -3) -> 3
(Num abs 3) -> 3

Num min

(Num min a b) -> integer

(Num min 3 7) -> 3

Num max

(Num max a b) -> integer

(Num max 3 7) -> 7

Num clamp

(Num clamp lo hi n) -> integer

Clamps n to the range [lo, hi].

(Num clamp 0 10 15) -> 10
(Num clamp 0 10 -5) -> 0
(Num clamp 0 10 5) -> 5

Num min-by

(Num min-by f a b) -> a | b

Returns whichever of a, b has the smaller (f x).

(Num min-by (method-ref Num abs) -5 3) -> 3

Num max-by

(Num max-by f a b) -> a | b

Returns whichever of a, b has the larger (f x).

(Num max-by (method-ref Num abs) -5 3) -> -5

Num zero?

(Num zero? n) -> #t | #f

(Num zero? 0) -> #t
(Num zero? 1) -> #f

Num positive?

(Num positive? n) -> #t | #f

(Num positive? 5) -> #t
(Num positive? -1) -> #f
(Num positive? 0) -> #f

Num negative?

(Num negative? n) -> #t | #f

(Num negative? -3) -> #t
(Num negative? 0) -> #f

Num even?

(Num even? n) -> #t | #f

(Num even? 4) -> #t
(Num even? 3) -> #f

Num odd?

(Num odd? n) -> #t | #f

(Num odd? 3) -> #t
(Num odd? 4) -> #f

List sum

(List sum lst) -> integer

(List sum (list 1 2 3)) -> 6

List product

(List product lst) -> integer

(List product (list 2 3 4)) -> 24

15. Lib: Logic

boolean?

(boolean? x) -> #t | #f

Returns #t if x is #t or #f.

(boolean? #t) -> #t
(boolean? #f) -> #t
(boolean? 1) -> #f

Fn default-to

(Fn default-to d x) -> x | d

Returns x if non-nil, otherwise d.

(Fn default-to 0 ()) -> 0
(Fn default-to 0 42) -> 42

Fn until

(Fn until pred f x) -> value

Repeatedly applies f to x until pred is true.

(Fn until (fn (_ n) (> n 10)) (method-ref Num inc) 1) -> 11

equal?

(equal? a b) -> #t | #f

Value equality: numbers by value, strings by content, and structural for pairs, lists and vectors (vectors via the %equal-others handler cell). Falls back to identity (eq?) for everything else. Instances stay identity-compared.

(equal? 3 3) -> #t
(equal? "abc" "abc") -> #t
(equal? (list 1) (list 1)) -> #t

16. Lib: Lists

Folds

fold

(List fold f init lst) -> value

Left fold.

(List fold + 0 (list 1 2 3)) -> 6
(List fold (fn (_ acc x) (pair x acc)) () (list 1 2 3)) -> (3 2 1)

List reduce

(List reduce f lst) -> value

Left fold using first element as initial value.

(List reduce + (list 1 2 3)) -> 6

List scan

(List scan f init lst) -> list

Like fold but collects intermediate values.

(List scan + 0 (list 1 2 3)) -> (0 1 3 6)

Basics

length

(List length lst) -> integer

(List length (list 1 2 3)) -> 3
(List length ()) -> 0

List ref

(List ref n lst) -> value

Zero-based index.

(List ref 0 (list 10 20 30)) -> 10
(List ref 2 (list 10 20 30)) -> 30

List last

(List last lst) -> value

(List last (list 1 2 3)) -> 3

List init

(List init lst) -> list

All elements except the last.

(List init (list 1 2 3)) -> (1 2)

append

(List append a b) -> list

(List append (list 1 2) (list 3 4)) -> (1 2 3 4)

List prepend

(List prepend x lst) -> list

(List prepend 0 (list 1 2)) -> (0 1 2)

reverse

(List reverse lst) -> list

(List reverse (list 1 2 3)) -> (3 2 1)

List flatten

(List flatten lst) -> list

(List flatten (list 1 (list 2 (list 3)))) -> (1 2 3)

Iteration

map

(List map f lst) -> list

(List map (method-ref Num inc) (list 1 2 3)) -> (2 3 4)

filter

(List filter pred lst) -> list

(List filter (method-ref Num even?) (list 1 2 3 4)) -> (2 4)

for-each

(List for-each f lst) -> ()

Applies f to each element for side effects only.

(List for-each display (list 1 2 3)) -> ()

List flat-map

(List flat-map f lst) -> list

Maps then flattens one level.

(List flat-map (fn (_ x) (list x x)) (list 1 2)) -> (1 1 2 2)

Predicates

List any?

(List any? pred lst) -> #t | #f

(List any? (method-ref Num even?) (list 1 3 4)) -> #t
(List any? (method-ref Num even?) (list 1 3 5)) -> #f

List all?

(List all? pred lst) -> #t | #f

(List all? (method-ref Num even?) (list 2 4 6)) -> #t
(List all? (method-ref Num even?) (list 2 3 6)) -> #f

List none?

(List none? pred lst) -> #t | #f

(List none? (method-ref Num even?) (list 1 3 5)) -> #t

List empty?

(List empty? lst) -> #t | #f

(List empty? ()) -> #t
(List empty? (list 1)) -> #f

Filtering

List reject

(List reject pred lst) -> list

Complement of filter.

(List reject (method-ref Num even?) (list 1 2 3 4)) -> (1 3)

List find

(List find pred lst) -> value | ()

(List find (method-ref Num even?) (list 1 3 4 6)) -> 4
(List find (method-ref Num even?) (list 1 3 5)) -> ()

List find-index

(List find-index pred lst) -> integer | ()

Misses return () like every other miss (negative indexes are valid from-the-end positions, so no number can mark absence).

(List find-index (method-ref Num even?) (list 1 3 4)) -> 2
(List find-index (method-ref Num even?) (list 1 3 5)) -> ()

List index-of

(List index-of x lst) -> integer | ()

Misses return ().

(List index-of 3 (list 1 2 3 4)) -> 2

List includes?

(List includes? x lst) -> #t | #f

(List includes? 3 (list 1 2 3)) -> #t
(List includes? 9 (list 1 2 3)) -> #f

List count-if

(List count-if pred lst) -> integer

(List count-if (method-ref Num even?) (list 1 2 3 4)) -> 2

Slicing

List take

(List take n lst) -> list

(List take 2 (list 1 2 3 4)) -> (1 2)

List drop

(List drop n lst) -> list

(List drop 2 (list 1 2 3 4)) -> (3 4)

List take-while

(List take-while pred lst) -> list

(List take-while (method-ref Num odd?) (list 1 3 4 5)) -> (1 3)

List drop-while

(List drop-while pred lst) -> list

(List drop-while (method-ref Num odd?) (list 1 3 4 5)) -> (4 5)

List split-at

(List split-at n lst) -> (list list)

(List split-at 2 (list 1 2 3 4)) -> ((1 2) (3 4))

List slice

(List slice start end lst) -> list

(List slice 1 3 (list 10 20 30 40)) -> (20 30)

Generators

List range

(List range start end) -> list

(List range 0 5) -> (0 1 2 3 4)

List repeat

(List repeat n x) -> list

(List repeat 3 0) -> (0 0 0)

List times

(List times n f) -> list

(List times 4 (method-ref Fn identity)) -> (0 1 2 3)

List unfold

(List unfold pred f g seed) -> list

(List unfold (fn (_ x) (> x 3)) (method-ref Fn identity) (method-ref Num inc) 1) -> (1 2 3)

List iterate

(List iterate f n x) -> list

(List iterate (method-ref Num inc) 4 0) -> (0 1 2 3)

List zip

(List zip a b) -> alist

Pairs corresponding elements as assocs; the result is an alist.

(List zip (list 1 2 3) (list 4 5 6)) -> ((1 . 4) (2 . 5) (3 . 6))

List zip-with

(List zip-with f a b) -> list

(List zip-with + (list 1 2 3) (list 10 20 30)) -> (11 22 33)

Transformation

List partition

(List partition pred lst) -> (list list)

(List partition (method-ref Num even?) (list 1 2 3 4)) -> ((2 4) (1 3))

List group-by

(List group-by f lst) -> alist

(List group-by (method-ref Num even?) (list 1 2 3 4)) -> ((#f 1 3) (#t 2 4))

List sort

(List sort cmp lst) -> list

Merge sort.

(List sort < (list 3 1 2)) -> (1 2 3)

List sort-by

(List sort-by f lst) -> list

(List sort-by (method-ref Num abs) (list -3 1 -2)) -> (1 -2 -3)

List uniq

(List uniq lst) -> list

Removes consecutive duplicates.

(List uniq (list 1 1 2 2 3)) -> (1 2 3)

List uniq-by

(List uniq-by f lst) -> list

(List uniq-by (method-ref Num abs) (list 1 -1 2 -2 3)) -> (1 2 3)

List intersperse

(List intersperse sep lst) -> list

(List intersperse 0 (list 1 2 3)) -> (1 0 2 0 3)

List transpose

(List transpose lsts) -> list

(List transpose (list (list 1 2) (list 3 4))) -> ((1 3) (2 4))

List update

(List update n val lst) -> list

(List update 1 99 (list 1 2 3)) -> (1 99 3)

List insert

(List insert n val lst) -> list

(List insert 1 99 (list 1 2 3)) -> (1 99 2 3)

List remove

(List remove start n lst) -> list

(List remove 1 2 (list 1 2 3 4)) -> (1 4)

List adjust

(List adjust n f lst) -> list

(List adjust 1 (method-ref Num inc) (list 10 20 30)) -> (10 21 30)

17. Lib: Alists

Association lists are lists of pairs ((key . val) ...). Keys are compared with eq?.

assoc-get

(Assoc get key alist) -> value | ()

(Assoc get 'b (list (pair 'a 1) (pair 'b 2))) -> 2
(Assoc get 'z (list (pair 'a 1))) -> ()

Assoc get-or

(Assoc get-or d key alist) -> value

(Assoc get-or 0 'z (list (pair 'a 1))) -> 0

assoc-has?

(Assoc has? key alist) -> #t | #f

(Assoc has? 'a (list (pair 'a 1))) -> #t
(Assoc has? 'z (list (pair 'a 1))) -> #f

assoc-del

(Assoc del key alist) -> alist

(Assoc del 'a (list (pair 'a 1) (pair 'b 2))) -> (('b . 2))

assoc-put

(Assoc put key val alist) -> alist

(Assoc put 'a 99 (list (pair 'a 1) (pair 'b 2))) -> (('a . 99) ('b . 2))

assoc-keys

(Assoc keys alist) -> list

(Assoc keys (list (pair 'a 1) (pair 'b 2))) -> ('a 'b)

Assoc vals

(Assoc vals alist) -> list

(Assoc vals (list (pair 'a 1) (pair 'b 2))) -> (1 2)

Assoc map

(Assoc map f alist) -> alist

Applies f to each value.

(Assoc map (method-ref Num inc) (list (pair 'a 1) (pair 'b 2))) -> (('a . 2) ('b . 3))

Assoc filter

(Assoc filter pred alist) -> alist

Filters entries by predicate applied to each (key . val) pair.

(Assoc filter (fn (_ e) (> (rest e) 1)) (list (pair 'a 1) (pair 'b 2))) -> (('b . 2))

Assoc merge

(Assoc merge a b) -> alist

Merges b into a, keeping a’s entries on collision.

(Assoc merge (list (pair 'a 1)) (list (pair 'a 9) (pair 'b 2))) -> (('a . 1) ('b . 2))

Assoc pick

(Assoc pick keys alist) -> alist

Returns entries whose keys appear in keys.

(Assoc pick (list 'a) (list (pair 'a 1) (pair 'b 2))) -> (('a . 1))

Assoc omit

(Assoc omit keys alist) -> alist

Returns entries whose keys are NOT in keys.

(Assoc omit (list 'a) (list (pair 'a 1) (pair 'b 2))) -> (('b . 2))

Assoc from-bindings

(Assoc from-bindings bindings) -> alist

Converts a bindings list – ((key value) ...) two-element lists, the let shape – to an alist of assocs.

(Assoc from-bindings (list (list 'a 1) (list 'b 2))) -> (('a . 1) ('b . 2))

Assoc ->bindings

(Assoc ->bindings alist) -> list

Converts an alist of assocs to a bindings list of two-element lists.

(Assoc ->bindings (list (pair 'a 1) (pair 'b 2))) -> (('a 1) ('b 2))

Assoc evolve

(Assoc evolve fns alist) -> alist

Applies transformation functions to matching keys.

(Assoc evolve (list (pair 'a (method-ref Num inc))) (list (pair 'a 1) (pair 'b 2))) -> (('a . 2) ('b . 2))

18. Lib: Strings

Str empty?

(Str empty? s) -> #t | #f

(Str empty? "") -> #t
(Str empty? "a") -> #f

Str join

(Str join sep lst) -> string

(Str join ", " (list "a" "b" "c")) -> "a, b, c"
(Str join "" (list "a" "b")) -> "ab"

Str repeat

(Str repeat n s) -> string

(Str repeat 3 "ab") -> "ababab"
(Str repeat 0 "x") -> ""

Str includes?

(Str includes? sub s) -> #t | #f

(Str includes? "ell" "hello") -> #t
(Str includes? "xyz" "hello") -> #f

Str starts?

(Str starts? pfx s) -> #t | #f

(Str starts? "he" "hello") -> #t
(Str starts? "lo" "hello") -> #f

Str ends?

(Str ends? sfx s) -> #t | #f

(Str ends? "lo" "hello") -> #t
(Str ends? "he" "hello") -> #f

Str reverse

(Str reverse s) -> string

(Str reverse "hello") -> "olleh"
(Str reverse "") -> ""

19. Lib: Vectors

Vectors are fixed-size indexed collections backed by lists. They display as #(...).

Vector of

(Vector of . args) -> vector

(Vector of 1 2 3) -> #(1 2 3)
(Vector of) -> #()

Vector vector?

(Vector vector? x) -> #t | #f

(Vector vector? (Vector of 1 2)) -> #t
(Vector vector? (list 1 2)) -> #f

Vector ref

(Vector ref i v) -> value

(Vector ref 1 (Vector of 10 20 30)) -> 20

Vector length

(Vector length v) -> integer

(Vector length (Vector of 1 2 3)) -> 3

Vector ->list

(Vector ->list v) -> list

(Vector ->list (Vector of 1 2 3)) -> (1 2 3)

Vector from-list

(Vector from-list lst) -> vector

(Vector from-list (list 1 2 3)) -> #(1 2 3)

Vector make

(Vector make n fill) -> vector

(Vector make 3 0) -> #(0 0 0)

20. Lib: Regex

Regex values are created with the #/pattern/ literal syntax. They compile the pattern into an AST at read time and match against strings at runtime.

Regex regex?

(Regex regex? x) -> #t | #f

(Regex regex? #/abc/) -> #t
(Regex regex? "abc") -> #f

Regex literals

(write #/abc/) -> #/abc/
(write #//) -> #//
(write #/ab*c/) -> #/ab*c/
(write #/a\.b/) -> #/a\.b/

Matching

A regex called as a function performs a full match against a string. Returns #t on match, #f on no match.

(#/abc/ "abc") -> #t
(#/abc/ "abd") -> #f
(#/abc/ "ab") -> #f
(#/abc/ "abcd") -> #f

Dot wildcard

. matches any single character.

(#/./ "x") -> #t
(#/a.c/ "abc") -> #t
(#/a.c/ "axc") -> #t
(#/./ "") -> #f

Star quantifier

* matches zero or more of the preceding element.

(#/ab*c/ "ac") -> #t
(#/ab*c/ "abc") -> #t
(#/ab*c/ "abbbc") -> #t
(#/a*/ "") -> #t

Plus quantifier

+ matches one or more of the preceding element.

(#/ab+c/ "abc") -> #t
(#/ab+c/ "abbbc") -> #t
(#/ab+c/ "ac") -> #f

Optional quantifier

? matches zero or one of the preceding element.

(#/ab?c/ "abc") -> #t
(#/ab?c/ "ac") -> #t
(#/ab?c/ "abbc") -> #f

Escape sequences

\ escapes the following character, treating it as a literal. Note the asymmetry with strings: a regex literal is read directly, so one backslash escapes – #/a\.b/ is the three-element pattern a . b – while the STRING being matched uses source-level escaping, so the one-backslash string is written "a\\b". (This section previously doubled the regex escapes as if they were strings; those patterns matched a literal backslash and did not demonstrate escaping at all.)

(#/a\.b/ "a.b") -> #t
(#/a\.b/ "axb") -> #f
(#/a\\b/ "a\\b") -> #t
(#/a\*b/ "a*b") -> #t

Backtracking

The * quantifier is greedy but backtracks to find a match.

(#/a.*b/ "axxb") -> #t
(#/.*b/ "aab") -> #t
(#/a.*b/ "axx") -> #f

Combined patterns

(#/a.*/ "abcdef") -> #t
(#/a.b*c/ "axbbc") -> #t
(#/.+/ "abc") -> #t
(#/.+/ "") -> #f

type-name

(Type name #/abc/) -> "REGEX"