Case Studies
This chapter presents complete, real-world applications built in Io. Each case study demonstrates how Io’s features work together to solve practical problems, showing both the elegance and challenges of building substantial systems in the language.
Case Study 1: Web Server #
Building a simple but functional HTTP server demonstrates Io’s networking, concurrency, and string handling:
// HTTP Server Implementation
HttpServer := Object clone
HttpServer init := method(port,
self port := port
self routes := Map clone
self middlewares := list()
self
)
HttpRequest := Object clone
HttpRequest parse := method(rawData,
lines := rawData split("\r\n")
if(lines size == 0, return nil)
// Parse request line
requestLine := lines at(0) split(" ")
self method := requestLine at(0)
self path := requestLine at(1)
self version := requestLine at(2)
// Parse headers
self headers := Map clone
self body := ""
bodyStart := false
lines slice(1) foreach(line,
if(bodyStart,
body = body .. line,
if(line size == 0,
bodyStart = true,
parts := line split(": ")
if(parts size == 2,
headers atPut(parts at(0), parts at(1))
)
)
)
)
// Parse query parameters
self params := Map clone
if(path containsSeq("?"),
parts := path split("?")
self path = parts at(0)
queryString := parts at(1)
queryString split("&") foreach(param,
kv := param split("=")
if(kv size == 2,
params atPut(kv at(0), kv at(1) urlDecode)
)
)
)
self
)
HttpResponse := Object clone
HttpResponse init := method(
self status := 200
self headers := Map clone
self body := ""
headers atPut("Content-Type", "text/html")
headers atPut("Server", "Io-Server/1.0")
self
)
HttpResponse setStatus := method(code,
status = code
self
)
HttpResponse setHeader := method(key, value,
headers atPut(key, value)
self
)
HttpResponse write := method(content,
body = body .. content
self
)
HttpResponse json := method(data,
setHeader("Content-Type", "application/json")
write(data asJson)
self
)
HttpResponse build := method(
statusText := Map with(
200, "OK",
404, "Not Found",
500, "Internal Server Error"
) at(status, "Unknown")
result := "HTTP/1.1 " .. status .. " " .. statusText .. "\r\n"
headers atPut("Content-Length", body size asString)
headers foreach(key, value,
result = result .. key .. ": " .. value .. "\r\n"
)
result .. "\r\n" .. body
)
// Middleware support
HttpServer use := method(middleware,
middlewares append(middleware)
self
)
// Routing
HttpServer route := method(method, path, handler,
key := method .. ":" .. path
routes atPut(key, handler)
self
)
HttpServer get := method(path, handler,
route("GET", path, handler)
)
HttpServer post := method(path, handler,
route("POST", path, handler)
)
// Request handling
HttpServer handleConnection := method(socket,
rawData := socket readUntilSeq("\r\n\r\n")
request := HttpRequest parse(rawData)
if(request isNil,
socket close
return
)
response := HttpResponse clone init
// Run middlewares
middlewares foreach(middleware,
middleware call(request, response)
)
// Find route
key := request method .. ":" .. request path
handler := routes at(key)
if(handler,
e := try(
handler call(request, response)
) catch(Exception, e,
response setStatus(500) write("Internal Server Error: " .. e message)
)
,
// Try pattern matching for dynamic routes
handled := false
routes foreach(routeKey, routeHandler,
parts := routeKey split(":")
routeMethod := parts at(0)
routePath := parts at(1)
if(routeMethod == request method and matchPath(routePath, request path),
routeHandler call(request, response)
handled = true
break
)
)
if(handled not,
response setStatus(404) write("Not Found")
)
)
socket write(response build)
socket close
)
HttpServer matchPath := method(pattern, path,
// Simple pattern matching (e.g., /users/:id)
if(pattern containsSeq(":"),
patternParts := pattern split("/")
pathParts := path split("/")
if(patternParts size != pathParts size, return false)
patternParts foreach(i, part,
if(part beginsWithSeq(":") not,
if(part != pathParts at(i), return false)
)
)
true
,
pattern == path
)
)
HttpServer start := method(
server := Socket clone
server setHost("127.0.0.1")
server setPort(port)
server bind
server listen
("Server listening on port " .. port) println
loop(
client := server accept
@handleConnection(client) // Handle async
)
)
// Example application
app := HttpServer clone init(8080)
// Middleware for logging
app use(block(request, response,
("[" .. Date now .. "] " .. request method .. " " .. request path) println
))
// Static content
app get("/", block(request, response,
response write("<h1>Welcome to Io Web Server</h1>")
response write("<p>A simple server built with Io</p>")
))
// JSON API
app get("/api/info", block(request, response,
info := Map with(
"server", "Io-Server",
"version", "1.0",
"time", Date now asString
)
response json(info)
))
// Dynamic routes
app get("/users/:id", block(request, response,
// Extract ID from path
id := request path split("/") at(2)
response write("<h1>User Profile</h1>")
response write("<p>User ID: " .. id .. "</p>")
))
// Form handling
app post("/submit", block(request, response,
// Parse form data from body
response write("<h1>Form Submitted</h1>")
response write("<p>Data: " .. request body .. "</p>")
))
// Start server
app start
Case Study 2: Database ORM #
A simple object-relational mapper showcasing metaprogramming and DSL capabilities:
// ORM Implementation
ORM := Object clone
// Database connection (simplified)
Database := Object clone
Database connections := Map clone
Database connect := method(name, config,
conn := Connection clone init(config)
connections atPut(name, conn)
conn
)
Connection := Object clone
Connection init := method(config,
self config := config
self tables := Map clone
self
)
Connection execute := method(sql,
("[SQL] " .. sql) println
// Simulate results
list()
)
// Model base class
Model := Object clone
Model tableName := nil
Model fields := Map clone
Model connection := nil
Model field := method(name, type,
fields atPut(name, Map with("type", type, "name", name))
// Generate getter
self setSlot(name, method(
self getSlot("_" .. name)
))
// Generate setter
self setSlot("set" .. name asCapitalized, method(value,
self setSlot("_" .. name, value)
self
))
self
)
Model belongsTo := method(name, targetModel,
fields atPut(name .. "_id", Map with("type", "INTEGER", "name", name .. "_id"))
self setSlot(name, method(
targetModel findById(self getSlot("_" .. name .. "_id"))
))
self
)
Model hasMany := method(name, targetModel, foreignKey,
self setSlot(name, method(
targetModel where(foreignKey .. " = " .. self id)
))
self
)
Model createTable := method(
sql := "CREATE TABLE IF NOT EXISTS " .. tableName .. " (\n"
sql = sql .. " id INTEGER PRIMARY KEY AUTOINCREMENT,\n"
fieldDefs := fields map(name, field,
" " .. name .. " " .. field at("type")
)
sql = sql .. fieldDefs join(",\n") .. "\n);"
connection execute(sql)
self
)
Model dropTable := method(
connection execute("DROP TABLE IF EXISTS " .. tableName)
self
)
Model init := method(
fields foreach(name, field,
self setSlot("_" .. name, nil)
)
self setSlot("_id", nil)
self
)
Model save := method(
if(_id,
update,
insert
)
)
Model insert := method(
columns := list()
values := list()
fields foreach(name, field,
value := self getSlot("_" .. name)
if(value isNil not,
columns append(name)
values append("'" .. value asString .. "'")
)
)
sql := "INSERT INTO " .. tableName .. " (" .. columns join(", ") .. ") VALUES (" .. values join(", ") .. ")"
connection execute(sql)
self _id := connection lastInsertId // Simulated
self
)
Model update := method(
updates := list()
fields foreach(name, field,
value := self getSlot("_" .. name)
if(value isNil not,
updates append(name .. " = '" .. value asString .. "'")
)
)
sql := "UPDATE " .. tableName .. " SET " .. updates join(", ") .. " WHERE id = " .. _id
connection execute(sql)
self
)
Model delete := method(
if(_id,
connection execute("DELETE FROM " .. tableName .. " WHERE id = " .. _id)
self _id := nil
)
self
)
// Class methods
Model all := method(
sql := "SELECT * FROM " .. tableName
rows := connection execute(sql)
rows map(row, fromRow(row))
)
Model findById := method(id,
sql := "SELECT * FROM " .. tableName .. " WHERE id = " .. id
rows := connection execute(sql)
if(rows size > 0,
fromRow(rows first),
nil
)
)
Model where := method(condition,
sql := "SELECT * FROM " .. tableName .. " WHERE " .. condition
rows := connection execute(sql)
rows map(row, fromRow(row))
)
Model fromRow := method(row,
instance := self clone init
instance _id := row at("id")
fields foreach(name, field,
instance setSlot("_" .. name, row at(name))
)
instance
)
// Query builder
QueryBuilder := Object clone
QueryBuilder init := method(model,
self model := model
self selections := list("*")
self conditions := list()
self orderBy := nil
self limitValue := nil
self
)
QueryBuilder select := method(
self selections = call message arguments map(arg,
call sender doMessage(arg) asString
)
self
)
QueryBuilder where := method(condition,
conditions append(condition)
self
)
QueryBuilder order := method(column, direction,
orderBy = column .. " " .. if(direction, direction, "ASC")
self
)
QueryBuilder limit := method(n,
limitValue = n
self
)
QueryBuilder build := method(
sql := "SELECT " .. selections join(", ") .. " FROM " .. model tableName
if(conditions size > 0,
sql = sql .. " WHERE " .. conditions join(" AND ")
)
if(orderBy,
sql = sql .. " ORDER BY " .. orderBy
)
if(limitValue,
sql = sql .. " LIMIT " .. limitValue
)
sql
)
QueryBuilder execute := method(
sql := build
rows := model connection execute(sql)
rows map(row, model fromRow(row))
)
// Define models
User := Model clone
User tableName = "users"
User connection = Database connect("main", Map with("file", "app.db"))
User field("name", "VARCHAR(100)") \
field("email", "VARCHAR(100)") \
field("age", "INTEGER") \
field("created_at", "DATETIME")
User hasMany("posts", Post, "user_id")
Post := Model clone
Post tableName = "posts"
Post connection = User connection
Post field("title", "VARCHAR(200)") \
field("content", "TEXT") \
field("published", "BOOLEAN") \
field("user_id", "INTEGER")
Post belongsTo("user", User)
// Validations
User validate := method(
errors := list()
if(_name isNil or _name size == 0,
errors append("Name is required")
)
if(_email isNil or _email containsSeq("@") not,
errors append("Invalid email")
)
if(_age and (_age < 0 or _age > 150),
errors append("Invalid age")
)
if(errors size > 0,
Exception raise(errors join(", "))
)
true
)
User beforeSave := method(
validate
_created_at := Date now
)
// Usage example
User createTable
Post createTable
user := User clone init
user setName("Alice") setEmail("alice@example.com") setAge(30)
user save
post := Post clone init
post setTitle("First Post") \
setContent("Hello, World!") \
setPublished(true) \
setUserId(user id)
post save
// Query examples
allUsers := User all
youngUsers := User where("age < 25")
userPosts := user posts
// Query builder
query := QueryBuilder clone init(User)
results := query select("name", "email") \
where("age > 21") \
order("name") \
limit(10) \
execute
Case Study 3: Game Engine #
A simple 2D game engine demonstrating real-time systems and graphics:
// Game Engine Core
GameEngine := Object clone
GameEngine init := method(width, height,
self width := width
self height := height
self entities := list()
self systems := list()
self running := true
self fps := 60
self frameTime := 1.0 / fps
self
)
// Entity Component System
Entity := Object clone
Entity init := method(
self id := Random uuid
self components := Map clone
self active := true
self
)
Entity addComponent := method(name, component,
components atPut(name, component)
component entity := self
self
)
Entity getComponent := method(name,
components at(name)
)
Entity hasComponent := method(name,
components hasKey(name)
)
// Components
Component := Object clone
PositionComponent := Component clone
PositionComponent init := method(x, y,
self x := x
self y := y
self
)
VelocityComponent := Component clone
VelocityComponent init := method(dx, dy,
self dx := dx
self dy := dy
self
)
SpriteComponent := Component clone
SpriteComponent init := method(image, width, height,
self image := image
self width := width
self height := height
self
)
ColliderComponent := Component clone
ColliderComponent init := method(width, height,
self width := width
self height := height
self
)
HealthComponent := Component clone
HealthComponent init := method(maxHealth,
self maxHealth := maxHealth
self currentHealth := maxHealth
self
)
// Systems
System := Object clone
System init := method(
self requiredComponents := list()
self
)
System process := method(entity, deltaTime,
// Override in subclasses
)
System canProcess := method(entity,
requiredComponents all(comp,
entity hasComponent(comp)
)
)
MovementSystem := System clone
MovementSystem requiredComponents = list("position", "velocity")
MovementSystem process := method(entity, deltaTime,
pos := entity getComponent("position")
vel := entity getComponent("velocity")
pos x = pos x + vel dx * deltaTime
pos y = pos y + vel dy * deltaTime
)
CollisionSystem := System clone
CollisionSystem requiredComponents = list("position", "collider")
CollisionSystem init := method(
resend
self collisions := list()
self
)
CollisionSystem update := method(entities, deltaTime,
collisions = list()
// Check all pairs
entities foreach(i, e1,
if(canProcess(e1),
entities slice(i + 1) foreach(e2,
if(canProcess(e2) and checkCollision(e1, e2),
collisions append(list(e1, e2))
onCollision(e1, e2)
)
)
)
)
)
CollisionSystem checkCollision := method(e1, e2,
p1 := e1 getComponent("position")
c1 := e1 getComponent("collider")
p2 := e2 getComponent("position")
c2 := e2 getComponent("collider")
// AABB collision
p1 x < p2 x + c2 width and
p1 x + c1 width > p2 x and
p1 y < p2 y + c2 height and
p1 y + c1 height > p2 y
)
CollisionSystem onCollision := method(e1, e2,
("Collision between " .. e1 id .. " and " .. e2 id) println
)
// Rendering (simulated)
RenderSystem := System clone
RenderSystem requiredComponents = list("position", "sprite")
RenderSystem init := method(
resend
self screen := list()
self
)
RenderSystem render := method(entities,
// Clear screen
screen = list()
entities foreach(entity,
if(canProcess(entity),
pos := entity getComponent("position")
sprite := entity getComponent("sprite")
screen append(Map with(
"x", pos x,
"y", pos y,
"image", sprite image
))
)
)
// Draw screen (simulated)
drawScreen
)
RenderSystem drawScreen := method(
"Frame:" println
screen foreach(item,
(" [" .. item at("image") .. "] at (" ..
item at("x") round .. ", " .. item at("y") round .. ")") println
)
)
// Input handling
InputManager := Object clone
InputManager init := method(
self keys := Map clone
self mouseX := 0
self mouseY := 0
self
)
InputManager isKeyPressed := method(key,
keys at(key, false)
)
InputManager setKey := method(key, pressed,
keys atPut(key, pressed)
)
// Game states
GameState := Object clone
GameState enter := method()
GameState exit := method()
GameState update := method(deltaTime)
GameState render := method()
MenuState := GameState clone
MenuState enter := method(
"Entering menu" println
self selectedOption := 0
self options := list("Start Game", "Options", "Quit")
)
MenuState update := method(deltaTime,
// Handle menu input
if(InputManager isKeyPressed("up"),
selectedOption = (selectedOption - 1) max(0)
)
if(InputManager isKeyPressed("down"),
selectedOption = (selectedOption + 1) min(options size - 1)
)
if(InputManager isKeyPressed("enter"),
handleSelection
)
)
MenuState handleSelection := method(
option := options at(selectedOption)
if(option == "Start Game",
GameEngine setState(PlayState)
)
if(option == "Quit",
GameEngine stop
)
)
PlayState := GameState clone
PlayState enter := method(
"Starting game" println
createLevel
)
PlayState createLevel := method(
// Create player
player := Entity clone init
player addComponent("position", PositionComponent clone init(100, 100))
player addComponent("velocity", VelocityComponent clone init(0, 0))
player addComponent("sprite", SpriteComponent clone init("player", 32, 32))
player addComponent("collider", ColliderComponent clone init(32, 32))
player addComponent("health", HealthComponent clone init(100))
GameEngine addEntity(player)
// Create enemies
3 repeat(i,
enemy := Entity clone init
enemy addComponent("position",
PositionComponent clone init(200 + i * 50, 200))
enemy addComponent("velocity",
VelocityComponent clone init(Random value * 20 - 10, Random value * 20 - 10))
enemy addComponent("sprite",
SpriteComponent clone init("enemy", 24, 24))
enemy addComponent("collider",
ColliderComponent clone init(24, 24))
GameEngine addEntity(enemy)
)
)
PlayState update := method(deltaTime,
// Handle player input
player := GameEngine entities first
if(player,
vel := player getComponent("velocity")
vel dx = 0
vel dy = 0
if(InputManager isKeyPressed("left"), vel dx = -100)
if(InputManager isKeyPressed("right"), vel dx = 100)
if(InputManager isKeyPressed("up"), vel dy = -100)
if(InputManager isKeyPressed("down"), vel dy = 100)
)
)
// Main game engine methods
GameEngine addEntity := method(entity,
entities append(entity)
entity
)
GameEngine removeEntity := method(entity,
entities remove(entity)
)
GameEngine addSystem := method(system,
systems append(system)
system
)
GameEngine setState := method(state,
if(hasSlot("currentState") and currentState,
currentState exit
)
currentState := state
currentState enter
)
GameEngine update := method(deltaTime,
// Update current state
if(currentState,
currentState update(deltaTime)
)
// Update systems
systems foreach(system,
if(system hasSlot("update"),
system update(entities, deltaTime),
entities foreach(entity,
if(system canProcess(entity),
system process(entity, deltaTime)
)
)
)
)
// Remove inactive entities
entities = entities select(e, e active)
)
GameEngine render := method(
if(currentState,
currentState render
)
renderSystem render(entities)
)
GameEngine run := method(
lastTime := Date now
while(running,
currentTime := Date now
deltaTime := currentTime - lastTime
if(deltaTime >= frameTime,
update(deltaTime)
render
lastTime = currentTime
)
// Small delay to prevent CPU spinning
wait(0.001)
)
)
GameEngine stop := method(
running = false
)
// Initialize and run game
game := GameEngine clone init(800, 600)
// Add systems
game addSystem(MovementSystem clone)
game addSystem(CollisionSystem clone init)
game renderSystem := RenderSystem clone init
// Set initial state
game setState(MenuState)
// Simulate some gameplay
"=== Game Engine Demo ===" println
MenuState handleSelection // Start game
// Run a few frames
5 repeat(i,
("Frame " .. i) println
game update(game frameTime)
game render
wait(0.1)
)
Case Study 4: Build System #
A build system similar to Make or Rake:
// Build System
BuildSystem := Object clone
BuildSystem init := method(
self tasks := Map clone
self dependencies := Map clone
self executed := list()
self config := Map clone
self
)
Task := Object clone
Task init := method(name, deps, action,
self name := name
self dependencies := if(deps, deps, list())
self action := action
self outputs := list()
self inputs := list()
self
)
Task execute := method(context,
("Executing task: " .. name) println
if(action,
action call(context)
)
)
Task upToDate := method(
if(outputs size == 0 or inputs size == 0,
return false
)
outputTime := outputs map(f, File with(f) lastModified) min
inputTime := inputs map(f, File with(f) lastModified) max
outputTime > inputTime
)
// DSL for defining tasks
BuildSystem task := method(name,
t := Task clone init(name, list(), nil)
tasks atPut(name, t)
self currentTask := t
call evalArgAt(0)
t
)
BuildSystem desc := method(description,
if(currentTask,
currentTask description := description
)
)
BuildSystem depends := method(
deps := call message arguments map(arg,
call sender doMessage(arg) asString
)
if(currentTask,
currentTask dependencies = deps
)
)
BuildSystem action := method(
if(currentTask,
currentTask action = call argAt(0)
)
)
BuildSystem file := method(output, inputs,
name := output
t := Task clone init(name, list(), nil)
t outputs = list(output)
t inputs = if(inputs type == "List", inputs, list(inputs))
tasks atPut(name, t)
self currentTask := t
call evalArgAt(2)
t
)
// Running tasks
BuildSystem run := method(taskName,
executed = list()
executeTask(taskName)
)
BuildSystem executeTask := method(taskName,
if(executed contains(taskName),
return
)
task := tasks at(taskName)
if(task isNil,
Exception raise("Task not found: " .. taskName)
)
// Check if up to date
if(task upToDate,
("Task " .. taskName .. " is up to date") println
return
)
// Execute dependencies first
task dependencies foreach(dep,
executeTask(dep)
)
// Execute the task
task execute(self)
executed append(taskName)
)
// Utilities
BuildSystem sh := method(command,
("$ " .. command) println
System system(command)
)
BuildSystem glob := method(pattern,
Directory with(".") files select(f,
f name matchesRegex(pattern)
) map(name)
)
BuildSystem mkdir := method(path,
Directory with(path) create
)
BuildSystem cp := method(src, dest,
File with(src) copyTo(dest)
)
BuildSystem rm := method(path,
File with(path) remove
)
// Configuration
BuildSystem configure := method(
call message arguments foreach(arg,
key := arg name
value := call sender doMessage(arg arguments at(0))
config atPut(key, value)
)
self
)
// Example Buildfile
build := BuildSystem clone init
build configure(
compiler: "gcc",
flags: "-Wall -O2",
srcDir: "src",
buildDir: "build"
)
build task("clean",
desc("Remove all build artifacts")
action(
rm(config at("buildDir"))
"Cleaned" println
)
)
build task("init",
desc("Initialize build directory")
action(
mkdir(config at("buildDir"))
)
)
build task("compile",
desc("Compile C sources")
depends("init")
action(
sources := glob("src/*.c")
sources foreach(src,
obj := src replaceSeq(".c", ".o") replaceSeq("src/", "build/")
sh(config at("compiler") .. " " .. config at("flags") ..
" -c " .. src .. " -o " .. obj)
)
)
)
build task("link",
desc("Link object files")
depends("compile")
action(
objects := glob("build/*.o") join(" ")
sh(config at("compiler") .. " " .. objects .. " -o build/app")
)
)
build task("test",
desc("Run tests")
depends("link")
action(
sh("./build/app --test")
)
)
build task("default",
depends("link")
)
// File tasks for individual files
build file("build/main.o", list("src/main.c"),
action(
sh(config at("compiler") .. " " .. config at("flags") ..
" -c src/main.c -o build/main.o")
)
)
// Run build
build run("default")
Lessons Learned #
These case studies demonstrate several key insights about building real applications in Io:
Strengths #
Rapid Prototyping: Io’s minimal syntax and dynamic nature make it excellent for quickly building working prototypes.
DSL Creation: The HTTP server’s routing, ORM’s query builder, and build system all show how naturally DSLs emerge in Io.
Flexibility: The ability to modify anything at runtime made it easy to add features like middleware, validations, and hooks.
Concurrency: The
@
operator and coroutines made async request handling in the web server trivial.
Challenges #
Performance: For the game engine, Io’s interpreted nature and message passing overhead would limit frame rates in a real game.
Type Safety: The ORM would benefit from type checking that Io doesn’t provide, leading to potential runtime errors.
Tooling: Lack of IDE support makes maintaining larger codebases challenging.
Libraries: Many features had to be built from scratch due to limited ecosystem.
Best Practices #
Use Prototypes Effectively: Define clear prototype hierarchies (Model -> User, Component -> PositionComponent).
Leverage Message Passing: The game engine’s entity-component system naturally maps to message passing.
Build Abstractions: Each case study built higher-level abstractions (Repository, Task, System) on Io’s primitives.
Embrace DSLs: Don’t fight the language—use its strengths to create domain-appropriate interfaces.
Conclusion #
These case studies show that Io is capable of building real applications, though with trade-offs. Its strengths in metaprogramming, DSL creation, and rapid prototyping make it excellent for certain domains, while performance-critical or large-scale applications might be better served by other languages. The key is understanding these trade-offs and using Io where its unique capabilities provide the most value.