feat: rebuild website with full learning funnel
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>
This commit is contained in:
330
docs/SQL_DESIGN_ANALYSIS.md
Normal file
330
docs/SQL_DESIGN_ANALYSIS.md
Normal file
@@ -0,0 +1,330 @@
|
||||
# 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)
|
||||
Reference in New Issue
Block a user