Types that follow your thinking
Inferred static types, immutable data, and exhaustive pattern matching. Expected failures remain visible as Result values.
Keep the logic clear.
Choose how it meets the world.
A functional language built around algebraic effects. Your code declares what it needs. You choose the handler that makes it happen.
effect Drafts { save: fn(string, string) -> Result<int, Error> }
fn saveDraft() = match perform Drafts.save("draft.txt", "Ship it.") {
Success _ => "Saved"
Error { message } => "Retry: ${message}"
}
let disk = handler Drafts {
save path text => writeFile(path, text)
}
let offline = handler Drafts {
save _ _ => Error { message: "offline" }
}
print("${disk(saveDraft)} / ${offline(saveDraft)}")
effect Drafts
save : (string, string) => Result<int, Error>
saveDraft () = match perform Drafts.save "draft.txt" "Ship it."
Success _ => "Saved"
Error message => "Retry: ${message}"
disk = handler Drafts
save path text => writeFile path text
offline = handler Drafts
save _ _ => Error(message = "offline")
print "${disk saveDraft} / ${offline saveDraft}"
Save a draft to disk. Simulate being offline. Keep the same save logic.
01 / A different way to connect
Your editor needs to save a draft. The save function requests storage and decides what to show when it succeeds or fails.
The disk handler writes the file. The offline handler simulates failure without touching storage. Swap the handler around the work; no storage parameter needs to pass through every helper.
Understand algebraic effects ↗effect defines an operation and its types. perform asks for it.
A reusable handler supplies the implementation while your function runs.
Missing handlers and incompatible operation types are compile errors.
Osprey is alpha software. Native control effects support single-shot resumption; full scoped effect rows and reusable continuations remain in development. Current support →
02 / Less ceremony. More intent.
Inferred static types, immutable data, and exhaustive pattern matching. Expected failures remain visible as Result values.
Every flavor's concrete syntax tree lowers into one canonical AST, so types and effects are shared. Write braces in .osp, ML layout in .ospml or the Python flavor in .py, and mix them in one project.
Native fibers and channels keep concurrent work in ordinary functions. Choose the default allocator, tracing GC, or reference counting for your native build.
Follow the fibers ↗Integer overflow asks an Arith handler for the region's chosen behavior. Wrapping and saturating helpers make that choice explicit.
03 / Take it into the world
Compile through LLVM to native programs, WebAssembly, or application logic for native mobile hosts.
Direct executables. C interop. A choice of memory backends.
--target=native
02 / BROWSER & WASIShared application logic, connected to the browser through a host.
--target=wasm32
03 / IN YOUR HANDOsprey modules behind SwiftUI and native Android interfaces.
--target=ios / android-arm64
Build for the capabilities of your target. Unsupported dynamic control effects are rejected on WebAssembly and mobile.
Real measurements, open benchmarks ↗04 / A model on the surface
gpuLoadTensor reads the published weights. The model performs a static Tensor effect. One handler answers it with plain lists. The other answers every operation with a GPU-surface kernel or a gpuMatMul product, and Metal or OpenCL executes them.
On the CPU every number matches an independent pure-Python reference exactly, token ids and gradients alike. On an Apple M4 Max a training step runs about three times faster on the GPU than on the CPU. The text is the same, and the numbers agree to twelve digits.
Read how it was ported ↗fn normalised(x, means, variances, g, b, i) = {
let r = intDiv(i, width)
let c = i % width
(at(x, i) - at(means, r)) / sqrt(at(variances, r) + epsilon) * at(g, c) + at(b, c)
}
export fn layerNorm(x, t, g, b) = {
let means = rowMeans(x, t)
let variances = rowVariances(x, t, means)
perform Backends::Tensor.build(t * width, fn(i) => normalised(x, means, variances, g, b, i))
}
Device arithmetic is software binary64 and kernels are not fused yet, so generation is no faster on the GPU than on the CPU. Measurements and source →
05 / From language to application
Issue Inbox puts its model, updates, UI layout, GitHub decoding, and SQLite commands in shared Osprey modules. SwiftUI and Android widgets render the application.
Actual simulator and emulator captures. Also verified on a physical iPhone. Native hosts provide rendering, SQLite, HTTPS, and lifecycle services.
Your next idea starts here
Start with a small program. Make the behavior your own.
brew install nimblesite/tap/ospreyscoop bucket add nimblesite https://github.com/Nimblesite/scoop-bucket
scoop install ospreyNative compilation requires clang (LLVM 15+). Installation guide → · Contribute on GitHub ↗