
A modern, expressive programming language designed for building robust and scalable applications. Write cleaner code, faster.
Get StartedA modern, expressive programming language designed for building robust and scalable applications. Write cleaner code, faster.
Get StartedExtra is a love letter to modern programming, with features like algebraic data types, type derivations, immutable data, and reactive programming.
type Result(Success, Error) = enum {
case ok(Success)
case 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>
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.
type Port = Int(0..<65_536) -- Port cannot be less than 0, or greater than 65,536
type IP-Segment = Int(0..<256) -- IPv4 segments must be in the range 0 to 255
type IPv4 = Array(IP-Segment, length: =4) -- IPv4 address have exactly 4 segments
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.
type 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
[]
}