Extra

A modern, expressive programming language designed for building robust and scalable applications. Write cleaner code, faster.

Get Started

Modern cool features

Extra 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>

Refinement Types

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
  …

Expressive pattern matching

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"}  => "❌"
  }

Length-Aware Collections

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
    []
}