Website rebuilt from scratch based on analysis of 11 beloved language websites (Elm, Zig, Gleam, Swift, Kotlin, Haskell, OCaml, Crystal, Roc, Rust, Go). New website structure: - Homepage with hero, playground, three pillars, install guide - Language Tour with interactive lessons (hello world, types, effects) - Examples cookbook with categorized sidebar - API documentation index - Installation guide (Nix and source) - Sleek/noble design (black/gold, serif typography) Also includes: - New stdlib/json.lux module for JSON serialization - Enhanced stdlib/http.lux with middleware and routing - New string functions (charAt, indexOf, lastIndexOf, repeat) - LSP improvements (rename, signature help, formatting) - Package manager transitive dependency resolution - Updated documentation for effects and stdlib - New showcase example (task_manager.lux) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
331 lines
8.4 KiB
Markdown
331 lines
8.4 KiB
Markdown
# SQL in Lux: Built-in Effect vs Package
|
|
|
|
## Executive Summary
|
|
|
|
This document analyzes whether SQL database access should be a built-in language feature (as it currently is) or a separate package. After comparing approaches across 12+ languages, the recommendation is:
|
|
|
|
**Keep SQL as a built-in effect, but refactor the implementation to be more modular.**
|
|
|
|
## Current Implementation
|
|
|
|
Lux currently implements SQL as a built-in effect:
|
|
|
|
```lux
|
|
fn main(): Unit with {Console, Sql} = {
|
|
let db = Sql.openMemory()
|
|
Sql.execute(db, "CREATE TABLE users (...)")
|
|
let users = Sql.query(db, "SELECT * FROM users")
|
|
Sql.close(db)
|
|
}
|
|
```
|
|
|
|
The implementation uses rusqlite (SQLite) compiled directly into the Lux binary.
|
|
|
|
## How Other Languages Handle Database Access
|
|
|
|
### Languages with Built-in Database Support
|
|
|
|
| Language | Approach | Notes |
|
|
|----------|----------|-------|
|
|
| **Python** | `sqlite3` in stdlib | Most languages have SQLite in stdlib |
|
|
| **Ruby** | `sqlite3` gem + AR are common | ActiveRecord is de facto standard |
|
|
| **Go** | `database/sql` interface in stdlib | Drivers are packages |
|
|
| **Elixir** | Ecto as separate package | But universally used |
|
|
| **PHP** | PDO in core | Multiple backends |
|
|
|
|
### Languages with Package-Only Database Support
|
|
|
|
| Language | Approach | Notes |
|
|
|----------|----------|-------|
|
|
| **Rust** | rusqlite, diesel, sqlx packages | No stdlib database |
|
|
| **Node.js** | pg, mysql2, better-sqlite3 | Packages only |
|
|
| **Haskell** | postgresql-simple, persistent | Packages only |
|
|
| **OCaml** | caqti, postgresql-ocaml | Packages only |
|
|
|
|
### Analysis of Each Approach
|
|
|
|
#### Go's Model: Interface in Stdlib + Driver Packages
|
|
|
|
```go
|
|
import (
|
|
"database/sql"
|
|
_ "github.com/lib/pq" // PostgreSQL driver
|
|
)
|
|
|
|
db, _ := sql.Open("postgres", "...")
|
|
rows, _ := db.Query("SELECT * FROM users")
|
|
```
|
|
|
|
**Pros:**
|
|
- Standard interface for all databases
|
|
- Type-safe at compile time
|
|
- Drivers are swappable
|
|
|
|
**Cons:**
|
|
- Requires understanding interfaces
|
|
- Need external packages for actual database
|
|
|
|
#### Python's Model: SQLite in Stdlib
|
|
|
|
```python
|
|
import sqlite3
|
|
conn = sqlite3.connect('example.db')
|
|
c = conn.cursor()
|
|
c.execute('SELECT * FROM users')
|
|
```
|
|
|
|
**Pros:**
|
|
- Zero dependencies for getting started
|
|
- Great for learning/prototyping
|
|
- Always available
|
|
|
|
**Cons:**
|
|
- Other databases need packages
|
|
- stdlib vs package API differences
|
|
|
|
#### Rust's Model: Everything is Packages
|
|
|
|
```rust
|
|
use rusqlite::{Connection, Result};
|
|
|
|
fn main() -> Result<()> {
|
|
let conn = Connection::open("test.db")?;
|
|
conn.execute("CREATE TABLE users (...)", [])?;
|
|
Ok(())
|
|
}
|
|
```
|
|
|
|
**Pros:**
|
|
- Minimal core language
|
|
- Best-in-class implementations
|
|
- Clear ownership
|
|
|
|
**Cons:**
|
|
- Cargo.toml management
|
|
- Version conflicts possible
|
|
- Learning curve for package ecosystem
|
|
|
|
#### Elixir's Model: Strong Package Ecosystem
|
|
|
|
```elixir
|
|
# Ecto is technically a package but universally used
|
|
Repo.all(from u in User, where: u.age > 18)
|
|
```
|
|
|
|
**Pros:**
|
|
- Best API emerges naturally
|
|
- Core team can focus on language
|
|
- Community ownership
|
|
|
|
**Cons:**
|
|
- Package can become outdated
|
|
- Multiple competing solutions
|
|
|
|
## Arguments For Built-in SQL
|
|
|
|
### 1. Effect System Integration
|
|
|
|
The most compelling argument: **SQL fits naturally into Lux's effect system.**
|
|
|
|
```lux
|
|
// The effect signature documents database access
|
|
fn fetchUser(id: Int): User with {Sql} = { ... }
|
|
|
|
// Handlers enable testing without mocks
|
|
handler testDatabase(): Sql { ... }
|
|
```
|
|
|
|
This is harder to achieve with packages - they'd need to integrate deeply with the effect system.
|
|
|
|
### 2. Zero-Dependency Getting Started
|
|
|
|
New users can immediately:
|
|
- Follow tutorials that use databases
|
|
- Build real applications
|
|
- Learn effects with practical examples
|
|
|
|
```bash
|
|
lux run database_example.lux
|
|
# Just works - no package installation
|
|
```
|
|
|
|
### 3. Guaranteed API Stability
|
|
|
|
Built-in effects have stable, documented APIs. Package APIs can change between versions.
|
|
|
|
### 4. Teaching Functional Effects
|
|
|
|
SQL is an excellent teaching example for effects:
|
|
- Clear side effects (I/O to database)
|
|
- Handler swapping for testing
|
|
- Transaction scoping
|
|
|
|
### 5. Practical Utility
|
|
|
|
90%+ of real applications need database access. Making it trivial benefits most users.
|
|
|
|
## Arguments For SQL as Package
|
|
|
|
### 1. Smaller Binary Size
|
|
|
|
rusqlite adds significant binary size (~2-3MB). Package-based approach lets users opt-in.
|
|
|
|
### 2. Database Backend Choice
|
|
|
|
Currently locked to SQLite. A package ecosystem could offer:
|
|
- `lux-sqlite`
|
|
- `lux-postgres`
|
|
- `lux-mysql`
|
|
- `lux-mongodb`
|
|
|
|
### 3. Faster Core Language Evolution
|
|
|
|
Core team focuses on language; community builds integrations.
|
|
|
|
### 4. Better Specialization
|
|
|
|
Dedicated package maintainers might build better database tooling than core team.
|
|
|
|
### 5. Multiple Competing Implementations
|
|
|
|
Competition drives quality. The best SQL package wins adoption.
|
|
|
|
## Comparison Matrix
|
|
|
|
| Factor | Built-in | Package |
|
|
|--------|----------|---------|
|
|
| Effect integration | Excellent | Needs design work |
|
|
| Learning curve | Low | Medium |
|
|
| Binary size | Larger | User controls |
|
|
| Database options | Limited | Unlimited |
|
|
| API stability | Guaranteed | Version-dependent |
|
|
| Getting started | Instant | Requires install |
|
|
| Testing story | Built-in handlers | Package-specific |
|
|
| Maintenance burden | Core team | Community |
|
|
|
|
## Recommendation
|
|
|
|
### Keep SQL as Built-in Effect, With Changes
|
|
|
|
**Rationale:**
|
|
|
|
1. **Effect system is Lux's differentiator** - SQL showcases it perfectly
|
|
2. **Practicality matters** - 90% of apps need databases
|
|
3. **Teaching value** - SQL is ideal for learning effects
|
|
4. **Handler testing** - Built-in integration enables powerful testing
|
|
|
|
### Proposed Architecture
|
|
|
|
```
|
|
Core Lux
|
|
├── Sql effect (interface only)
|
|
│ ├── open/close
|
|
│ ├── execute/query
|
|
│ └── transaction operations
|
|
│
|
|
└── Default SQLite handler (built-in)
|
|
└── Uses rusqlite
|
|
|
|
Future packages (optional)
|
|
├── lux-postgres -- PostgreSQL handler
|
|
├── lux-mysql -- MySQL handler
|
|
└── lux-redis -- Redis (key-value, not Sql)
|
|
```
|
|
|
|
### Specific Changes to Consider
|
|
|
|
1. **Make SQLite compilation optional**
|
|
```toml
|
|
# Cargo.toml
|
|
[features]
|
|
default = ["sqlite"]
|
|
sqlite = ["rusqlite"]
|
|
```
|
|
|
|
2. **Define stable Sql effect interface**
|
|
```lux
|
|
effect Sql {
|
|
fn open(path: String): SqlConn
|
|
fn close(conn: SqlConn): Unit
|
|
fn execute(conn: SqlConn, sql: String): Int
|
|
fn query(conn: SqlConn, sql: String): List<SqlRow>
|
|
// ...
|
|
}
|
|
```
|
|
|
|
3. **Allow package handlers to implement Sql**
|
|
```lux
|
|
// In lux-postgres package
|
|
handler postgresHandler(connStr: String): Sql { ... }
|
|
|
|
// Usage
|
|
run myApp() with {
|
|
Sql -> postgresHandler("postgres://...")
|
|
}
|
|
```
|
|
|
|
4. **Add connection pooling to core**
|
|
Important for production, should be standard.
|
|
|
|
## Comparison to Similar Decisions
|
|
|
|
### Console Effect
|
|
|
|
Console is built-in. Nobody questions this because:
|
|
- Universally needed
|
|
- Simple interface
|
|
- Hard to get wrong
|
|
|
|
SQL is similar but more complex.
|
|
|
|
### HTTP Effect
|
|
|
|
HTTP client is built-in in Lux. This was the right call because:
|
|
- Most apps need HTTP
|
|
- Complex to implement well
|
|
- Effect system integration important
|
|
|
|
SQL follows same reasoning.
|
|
|
|
### File Effect
|
|
|
|
File I/O is built-in. Same rationale applies.
|
|
|
|
## What Other Effect-System Languages Do
|
|
|
|
| Language | Database | Built-in? |
|
|
|----------|----------|-----------|
|
|
| **Koka** | No database support | N/A |
|
|
| **Eff** | No database support | N/A |
|
|
| **Frank** | No database support | N/A |
|
|
| **Unison** | Abilities + packages | Both |
|
|
|
|
Lux is pioneering practical effects. Built-in SQL makes sense.
|
|
|
|
## Conclusion
|
|
|
|
SQL should remain a built-in effect in Lux because:
|
|
|
|
1. It demonstrates the power of effects for real-world use
|
|
2. It enables the handler-based testing story
|
|
3. It removes friction for most applications
|
|
4. It serves as a teaching example for effects
|
|
|
|
However, the implementation should evolve to:
|
|
- Support multiple database backends via handlers
|
|
- Make SQLite optional for minimal binaries
|
|
- Provide connection pooling
|
|
- Add parameterized query support
|
|
|
|
This hybrid approach gives users the best of both worlds: immediate productivity with built-in SQLite, and flexibility through package-provided handlers for other databases.
|
|
|
|
---
|
|
|
|
## Future Work
|
|
|
|
1. **Parameterized queries** - Critical for SQL injection prevention
|
|
2. **Connection pooling** - Required for production servers
|
|
3. **PostgreSQL handler** - Most requested database
|
|
4. **Migration support** - Schema evolution tooling
|
|
5. **Type-safe queries** - Compile-time SQL checking (ambitious)
|