Build the Future with Osprey

A modern functional language with compile-time effect safety, lightweight fiber concurrency, and immutable persistent collections. Compiles to LLVM.

// Simple, clean function definitions
fn double(n: int) -> int = n * 2

// String interpolation that works
let x = 42
let name = "Alice"
print("x = ${x}")
print("name = ${name}")

// Pattern matching on values
let result = match x {
  42 => "The answer!"
  0 => "Zero"
  _ => "Something else"
}
print("Result: ${result}")

Get Osprey Now

🍎 macOS / 🐧 Linux

brew install nimblesite/tap/osprey

🪟 Windows

scoop bucket add nimblesite https://github.com/Nimblesite/scoop-bucket
scoop install osprey

🌐 Try Web Compiler

No installation needed! Compile and run Osprey code in your browser.

Open Playground

🧩 VS Code Extension

Syntax highlighting, diagnostics, and a bundled compiler.

Get the Extension

🔨 Build from Source

Want the latest features? Build the compiler locally from GitHub.

View on GitHub

The Vision

Osprey was born from the belief that elegance is the best defence against bugs. Too many hours are wasted debugging null pointer exceptions, handling unexpected panics, and tracking down race conditions. Osprey is different.

Osprey's type system aims to prevent entire classes of bugs at compile time, make concurrency safe by default, and keep your code maintainable for years to come.

// Effects in the type, handlers at the edge
effect Logger { log: fn(string) -> Unit }

fn greet(name: string) -> Unit !Logger =
  perform Logger.log("Hello, ${name}!")

handle Logger
  log msg => print(msg)
in greet("Alice")

Language Features

Type-Safe & Expressive

Strong static typing prevents runtime errors while keeping syntax clean and readable. Expression-bodied functions eliminate boilerplate.

  • Explicit type annotations
  • Compile-time error checking
  • Self-documenting code
  • Expression-bodied functions
fn analyzeNumber(n: int) -> string = match n {
  0 => "Zero"
  42 => "The answer!"
  _ => "Something else"
}

fn double(x: int) -> int = x * 2
fn square(x: int) -> int = x * x

// Test the functions
print("Testing functions:")
print(analyzeNumber(0))
print(analyzeNumber(42))
fn getGrade(score: int) -> string = match score {
  100 => "Perfect!"
  95 => "Excellent"
  85 => "Very Good"
  75 => "Good"
  _ => "Needs Improvement"
}

// Test the function
print("Grade for 100: ${getGrade(100)}")
print("Grade for 95: ${getGrade(95)}")

Pattern Matching

Elegant pattern matching with exhaustiveness checking ensures you handle all cases safely.

  • Exhaustive pattern checking
  • Safe value destructuring
  • Clear conditional logic
  • No forgotten edge cases

String Interpolation

Built-in string interpolation with full expression support makes output formatting clean and readable.

  • Expression interpolation
  • Type-safe formatting
  • Readable string templates
  • No manual concatenation
// String interpolation example
let name = "Alice"
let age = 25
let score = 95

print("Hello ${name}!")
print("Next year you'll be ${age + 1}")
print("Double score: ${score * 2}")
print("${name} (${age}) scored ${score}/100")
// Functional Programming Example
fn double(x: int) -> int = x * 2
fn square(x: int) -> int = x * x

// Clean data transformations
5 |> double |> square |> print

// Range operations with forEach
print("Range operations:")
range(1, 10) |> forEach(print)

Functional Programming

Pipe operators and functional iterators create elegant data processing pipelines.

  • Pipe operator for data flow
  • Functional iterators
  • Immutable by default
  • Clean transformation chains

Algebraic Effects

Osprey is the world's first language with 100% compile-time effect safety. Side effects live in a function's type, and an unhandled effect is a compilation error — never a runtime surprise.

Effects in the type, handlers at the edge

Declare an effect, perform its operations, then swap behaviour by changing the handle … in block — no globals, no dependency injection framework. The same logic runs against a production handler, a test double, or a silent one.

  • Unhandled effects fail at compile time
  • Handlers are first-class and composable
  • Nested handlers override outer ones
  • Pairs naturally with fibers, HTTP, and TUIs
effect Logger {
  log: fn(string) -> Unit
}

fn greet(name: string) -> Unit !Logger =
  perform Logger.log("Hello, ${name}!")

// Production: write to stdout
handle Logger
  log msg => print(msg)
in greet("Alice")

// Test: stay silent — same code, new handler
handle Logger
  log msg => 0
in greet("Bob")

Persistent Collections

// List — 32-way bitmapped vector trie
let xs = listAppend(listAppend(List(), 10), 20)
let ys = listAppend(xs, 99)

listLength(xs)              // 2  — xs untouched
listLength(ys)              // 3
xs + ys                     // O(n+m) concat

// Map — Hash Array Mapped Trie (HAMT)
let m = mapSet(Map(), "alice", 25)
let m2 = mapSet(m, "bob", 30)

mapContains(m,  "bob")      // false
mapContains(m2, "bob")      // true
m + m2                      // right-biased union

Built on FP foundations

Immutable List<T> and Map<K, V> backed by structural sharing, so "modifying" a collection never copies the whole thing — old versions stay valid in O(1) extra space relative to the change.

  • List: 32-way bitmapped vector trie (Bagwell 2000; Clojure's PersistentVector). O(log₃₂ n) point ops.
  • Map: Hash Array Mapped Trie with bitmap-packed children. O(log₃₂ n) expected for lookup, insert and remove.
  • + operator dispatches to listConcat for lists and mapMerge (right-biased) for maps.
  • Backed by 33 C-level assertions and 12 byte-exact end-to-end test programs.

Compile-Time Effect Safety

Every side effect is tracked in the type system. Unhandled effects are caught by the compiler.

Memory Safe

Strong static typing and immutable data prevent buffer overflows and data races.

Compiles to LLVM

Stream fusion and structural sharing lower high-level code to efficient native binaries.

Real-World Examples

Fiber Concurrency

fn work(n: int) -> int = n * n

// Spawn lightweight fibers, await out of order
let a = spawn work(6)
let b = spawn work(7)

let rb = await(b)
let ra = await(a)
print("a=${ra}, b=${rb}")

// Message passing over a channel
let ch = Channel(1)
send(ch, 42)
print("got ${recv(ch)}")

C Interop & SQLite

// @link: sqlite3

// Bind C functions with typed signatures
extern fn sqlite3_open(path: string, ppDb: Ptr) -> int
extern fn osprey_ffi_cell() -> Ptr
extern fn osprey_ffi_deref(cell: Ptr) -> Ptr

// Ptr carries the opaque C handle — no
// arithmetic, no dereference, handles only
let cell = osprey_ffi_cell()
let rc = sqlite3_open("app.db", cell)
let db = osprey_ffi_deref(cell)
print("sqlite open rc = ${rc}")

HTTPS Client

// TLS via OpenSSL — https just works
let client = httpCreateClient("https://httpbin.org", 5000)

let status = httpGet(client, "/get", "")
print("status: ${status}")

let closed = httpCloseClient(client)
print("client closed")

Core Philosophy

Referential Transparency

Functions return the same output for the same input. Side effects are explicit and tracked by the type system.

Immutability by Default

Data is immutable unless explicitly marked mutable, making it safe to share across fibers.

Explicit Error Handling

No hidden exceptions or panics. All failures return Result types you must handle.

Zero-Cost Abstractions

High-level features — effects, iterators, persistent collections — compile to efficient native code.

How Osprey Is Different

Feature Traditional Osprey
Side Effects Untracked, implicit In the type; unhandled effects are a compile error
Error Handling Exceptions, panics Result types
Null Safety Null pointer exceptions Option types only
Concurrency Shared mutable state Fiber-isolated, message-passing
Type Safety Runtime type errors possible Compile-time prevention via Hindley-Milner inference
Memory Management Manual memory or garbage collection Memory-safe, no GC pauses

Help Build the Future of Programming

Anyone can contribute. AI assistants like Claude + Cursor make compiler development accessible to regular developers. No CS degree required.