Exr
The best programming language ever created. Designed for LLMs. Designed for stability. Always performant. Only slightly fastidious. Unapologetically extra.
Get StartedThe best programming language ever created. Designed for LLMs. Designed for stability. Always performant. Only slightly fastidious. Unapologetically extra.
Get StartedExtra is a love letter to modern programming, with features like algebraic data types, type derivations, immutable data, and reactive programming. The syntax is clean and thoroughly type-safe. No runtime errors or `try/catch`, thank you very much.
enum Result(Success, Error) {
.ok(Success)
.err(Error)
fn map<U>(
# mapper: fn(# in: Success) => U
): Result(U, Error) =>
switch (this) {
case .ok(success): .ok(mapper(success))
case .err(error): .err(error)
}
}
type User = {
name: String
phone: Array(Int)
address: Address
}
type AnonymousUser = Omit(User, 'name', 'phone')
view UserCard(user: User | AnonymousUser) =>
<Card>
{if ('name' in user) { then: <h1>{user.name}</h1> }}
<Address address=user.address />
</Card>
React has had a great run as the world's most popular foot gun. Solid and Vue and Svelte are noble attempts to bring some sanity into the world, but if you're going to concede to JavaScript/TypeScript, then you're never going to be fully declarative. The Extra runtime guarantees that state updates only render what changed *with no diffing necessary*. This is because state updates are *also declarative* (you can see the Elm inspiration here, but taken in a new direction).
import Date -- system or installed packages
import Html : {div, p, button}
import Extra : {Timer}
import /components : {Todo, CreateTodo}
import ./helper : {FormatDate}
public view Timer {
@time: Date.Instant?
@paused = false
override init() =>
tick()
fn tick() =>
Task.run(Date.now, fn(time) => @time = time)
fn pause() =>
@paused = not @paused
override render() =>
<>
<p>TODAY'S DATE</p>
{@time and <FormatDate>{$(@time)}</FormatDate>}
<Timer interval=100 onTick=tick enabled=(not @paused) />
<button onClick=pause>
{if (@paused) { then: 'Resume', else: 'Pause' }}
</button>
</>
}
default view TodoList {
@todos: Array(Todo) = []
fn add-todo(todo: Todo) =>
@todos.push(todo)
override render() =>
<div>
<p>TODOS</p>
{todo.map(todo) => <Todo todo=todo/>}
<CreateTodo onCreate=add-todo />
<Timer />
</div>
}
Extra's refinement types let you define subsets of standard types (Array, Int, String) with compile-time-enforced rules. This is a natural, if unique, extension of thinking of types in terms of set theory. These refinements propagate through conditionals, enabling branch-specific type narrowing.
-- Port cannot be less than 0, or greater than 65,536
type Port = Int(0..<65_536)
-- IPv4 segments must be in the range 0 to 255
type IP-Segment = Int(0..<256)
-- IPv4 address have exactly 4 segments
type IPv4 = Array(IP-Segment, length: =4)
type Server = { ip: IPv4, port: Port }
let server: Server = {
ip: [192, 168, 1, 256],
port: 80
}
let
userAddress: Result(Server, String) =
if (ipNumbers.length == 4) {
then:
.ok(ipNumbers)
else:
.err("""
Incorrect number of IP numbers.
Expected 4 but received ${ipNumbers.length}
""")
}
in
…
Inspired by languages like Elixir, Gleam, Elm, and of course the functional programming diva Haskell, Extra supports pattern matching with variable assignment and exhaustiveness checking.
enum PaymentStatus { .pending | .paid | .failed }
type Payment = {
amount: Float(>0),
status: PaymentStatus
}
fn process(payment: Payment): String =>
switch (payment) {
case {amount, .pending}:
"⏳ Still waiting for $amount"
case {amount, .paid}:
"✅ $amount was paid"
case {_, .failed}:
"❌"
}
Arrays, Dicts, and Sets track metadata like lengths and keys statically. Indexing becomes safe when paired with refinements, eliminating null checks.
fn zip<T, U, L is Int>(
# lhs: Array(T, length: L)
# rhs: Array(U, length: L)
): Array({T, U}, length: L) {
lhs.map(fn(lhItem, index) =>
let
rhItem = rhs[index]
in
{ lhItem, rhItem }
if (users.length == addresses.length) {
then:
-- length has been compared, and so this call is safe
zip(users, addresses)
else:
-- zip cannot be called here, because the lengths are not equal
[]
}