Koka: Functional Language with Algebraic Effect Types
PROGRAMMING LANGUAGES March 18, 2026, 5:30 a.m.

Koka: Functional Language with Algebraic Effect Types

Koka is a modern functional language that brings algebraic effect types to the forefront of everyday programming. It blends the elegance of pure functional code with a disciplined way to handle side‑effects, making programs both expressive and safe. In this article we’ll explore Koka’s core concepts, walk through practical examples, and see why its effect system matters for real‑world projects.

What Is Koka?

Koka was created by Daan Leijen as a research language to demonstrate how algebraic effects can replace traditional monads. The syntax feels familiar to anyone who has used ML or Haskell, yet the type system tracks effects directly in function signatures. This means the compiler can warn you when an unexpected side‑effect slips into a supposedly pure function.

Because effects are part of the type, Koka encourages a clear separation between pure computation and effectful actions. The result is code that reads like a mathematical description while still being able to perform I/O, mutable state, exceptions, and more.

Core Language Features

  • Statically typed with Hindley‑Milner inference.
  • First‑class functions and pattern matching.
  • Algebraic data types and extensible records.
  • Effect polymorphism that lets you write generic effectful code.

Algebraic Effect Types

In Koka, an effect is a set of operations that a function may invoke, such as io, exn (exceptions), or state. The type of a function looks like (Int) -> Int ! {io, exn}, where the ! {…} part lists the effects. Handlers are defined separately and can be swapped, giving you a powerful way to modularize concerns like logging or transaction management.

Effect rows can be open, allowing a function to be polymorphic over additional effects. This flexibility is what makes Koka’s effect system feel like a lightweight alternative to monad transformers.

Getting Started with Koka

Installing Koka is straightforward. The official compiler is distributed as a single binary for Windows, macOS, and Linux. After downloading, add the binary to your PATH and verify the installation with koka --version.

Let’s write the classic “Hello, World!” program to see the syntax in action.

module main

fun main() : () ! {io} {
  io.println("Hello, World!")
}

Notice the ! {io} annotation indicating that main performs I/O. Compile with koka main.koka -o hello and run ./hello to see the output.

Working with Effects: Example 1 – Asynchronous I/O

Koka’s effect system shines when dealing with asynchronous operations. The built‑in async effect lets you spawn lightweight fibers without leaking implementation details into your code.

module async_demo

fun fetch(url: string) : string ! {io, async} {
  // Simulated async HTTP request
  async {
    io.println("Fetching " ++ url)
    // Pretend we wait for network
    async.sleep(1000)
    "Response from " ++ url
  }
}

fun main() : () ! {io, async} {
  val r1 = fetch("https://example.com")
  val r2 = fetch("https://koka-lang.github.io")
  // Wait for both fibers and combine results
  val result = async.waitAll([r1, r2])
  io.println("Combined: " ++ result[0] ++ " | " ++ result[1])
}

The async block returns a fiber that can be awaited later. Because fetch declares both io and async effects, the compiler enforces that callers handle these effects explicitly.

Working with Effects: Example 2 – State Management

Mutable state is often a source of bugs in imperative languages. Koka provides a scoped state effect that can be used safely without leaking mutable references.

module state_demo

fun counter() : (int -> int) ! {state} {
  // Create a local mutable cell
  val cell = state.new(0)
  fun inc(_: int) : int {
    val cur = state.get(cell)
    state.set(cell, cur + 1)
    cur + 1
  }
  inc
}

fun main() : () ! {io, state} {
  val inc = counter()
  io.println("First: " ++ inc(0))
  io.println("Second: " ++ inc(0))
  io.println("Third: " ++ inc(0))
}

Here counter returns a closure that captures a hidden state cell. The effect annotation ! {state} guarantees that the mutable cell cannot escape its intended scope, preserving referential transparency for the rest of the program.

Pro tip: When you need a quick mutable cache, prefer the state effect over global variables. It keeps the mutation local, makes reasoning easier, and the compiler will catch accidental leaks.

Real‑World Use Cases

Koka’s effect system is not just an academic curiosity; it solves concrete problems in production code.

  • Web servers: Use the io and async effects to handle thousands of concurrent connections without callback hell.
  • Domain‑specific languages (DSLs): Model language constructs as effects, allowing you to plug in custom interpreters or optimizers.
  • Testing: Replace the real io effect with a mock handler that records interactions, enabling pure unit tests for effectful code.
  • Financial calculations: Keep the core algorithm pure while isolating logging, error handling, and external data fetches as separate effects.

Because handlers are first‑class, you can swap a logging effect with a no‑op version in production, or inject a profiling handler during benchmarking—all without touching the business logic.

Advanced Patterns: Effect Handlers as Dependency Injection

Effect handlers can serve as a lightweight dependency injection (DI) mechanism. Define an abstract effect like log and provide multiple concrete handlers: a console logger for development, a file logger for production, or a silent logger for tests.

module di_demo

// Abstract logging effect
effect log(message: string)

// Console implementation
handler consoleLog : log ~> io {
  fun handle(message) {
    io.println("[Console] " ++ message)
  }
}

// Silent implementation
handler silentLog : log ~> {} {
  fun handle(_: string) { /* do nothing */ }
}

// Business logic that uses the abstract effect
fun process(x: int) : int ! {log} {
  log("Processing " ++ x)
  x * 2
}

// Main selects a handler at runtime
fun main() : () ! {io} {
  // Choose console logger
  with consoleLog {
    val r = process(10)
    io.println("Result: " ++ r)
  }

  // Switch to silent logger
  with silentLog {
    val r = process(20)
    io.println("Result (silent): " ++ r)
  }
}

The with construct installs a handler for the duration of the block. This pattern cleanly separates concerns and makes the codebase easier to extend.

Composing Multiple Effects

Often a function needs more than one effect, such as I/O, state, and exceptions. Koka lets you compose them by listing them in the effect row. Handlers can be layered, each taking care of a specific effect.

module compose_demo

effect exn(message: string)

fun safeDiv(a: int, b: int) : int ! {exn} {
  if b == 0 then exn.raise("Division by zero")
  else a / b
}

fun compute() : int ! {io, exn, state} {
  val counter = state.new(0)
  val result = safeDiv(10, 2)
  state.set(counter, state.get(counter) + 1)
  io.println("Division succeeded")
  result
}

// Top‑level handler that catches exceptions and prints them
handler exnHandler : exn ~> io {
  fun handle(msg) {
    io.println("Error: " ++ msg)
    0 // fallback value
  }
}

// Run with all handlers in place
fun main() : () ! {io, state} {
  with exnHandler {
    val r = compute()
    io.println("Result: " ++ r)
  }
}

Notice how compute declares three effects, yet the main function only needs to handle io and state. The exn effect is resolved by the surrounding exnHandler, demonstrating clean separation of error handling from core logic.

Pro tip: Keep effect rows as small as possible. If a function only needs io, don’t sprinkle state or exn into its signature. Smaller rows lead to better composability and clearer error messages.

Tooling and Ecosystem

Koka ships with a REPL, a standard library, and integration for VS Code that provides syntax highlighting and type‑on‑hover. The compiler also offers detailed effect inference errors, guiding you to add or remove effects where needed.

Community packages are published on the official GitHub and include utilities for JSON handling, HTTP clients, and database connectors—all built using Koka’s effect abstractions.

Performance Considerations

Effect handling introduces a small runtime overhead, but Koka’s implementation uses lightweight continuations and tail‑call optimization to keep the cost minimal. Benchmarks show that pure Koka code can be competitive with OCaml, and effectful code often outperforms monad‑heavy Haskell equivalents because there’s no need for monad transformer stacks.

When performance is critical, you can specialize handlers to inline effect operations, or use the inline pragma to guide the compiler.

Best Practices Checklist

  1. Declare only the effects you truly need.
  2. Prefer local state over global mutable variables.
  3. Write pure core algorithms and isolate side‑effects in thin wrappers.
  4. Use handlers to swap implementations for testing and production.
  5. Leverage the REPL for rapid prototyping of effectful snippets.

Conclusion

Koka demonstrates that algebraic effect types can make functional programming more practical for everyday software development. By making effects explicit in the type system, it gives you the safety of pure functions while retaining the ability to perform I/O, handle errors, and manage mutable state in a disciplined way. Whether you’re building a high‑throughput web service, a DSL, or a data‑processing pipeline, Koka’s effect handlers provide a modular, composable, and testable architecture.

Give Koka a try on a small side project. You’ll quickly notice how the compiler guides you toward cleaner abstractions, and how swapping handlers feels like changing a configuration file rather than rewriting code. As the ecosystem matures, expect more libraries to adopt effect‑centric designs, further reducing boilerplate and increasing confidence in your codebase.

Share this article