Skip to content

Commit 683117d

Browse files
committed
Add cursor rules
1 parent d1a90ed commit 683117d

33 files changed

Lines changed: 3978 additions & 0 deletions
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
---
2+
description:
3+
globs:
4+
alwaysApply: false
5+
---
6+
# Ox Direct-Style Programming
7+
8+
Ox uses direct-style programming where effectual computations return values directly without wrapper types like `Future`, `IO`, or `Task`. This leverages Java 21 virtual threads for high-performance concurrency.
9+
10+
## Core Principles
11+
12+
- **Direct return values**: Methods return actual values, not wrapped types
13+
- **Built-in control flow**: Use standard language constructs (if/else, for loops, try/catch)
14+
- **Virtual threads**: Blocking operations are cheap and efficient
15+
- **No coloring**: No need for special syntax like `async`/`await`
16+
17+
## Good Examples
18+
19+
```scala
20+
// Direct-style: values returned directly
21+
def fetchData(): String =
22+
val response = httpClient.get("https://api.example.com/data")
23+
response.body
24+
25+
def processData(): List[String] =
26+
val data = fetchData()
27+
data.split(",").toList
28+
29+
// Combining operations naturally
30+
def pipeline(): Int =
31+
val data = fetchData()
32+
val processed = processData()
33+
processed.length
34+
```
35+
36+
## Avoid
37+
38+
```scala
39+
// Don't use wrapper types like Future, IO, Task
40+
def fetchData(): Future[String] = ???
41+
def fetchData(): IO[String] = ???
42+
def fetchData(): Task[String] = ???
43+
44+
// Don't use callback-style programming
45+
def fetchData(callback: String => Unit): Unit = ???
46+
```
47+
48+
## Key Benefits
49+
50+
- **Natural error handling**: Use try/catch for exceptions
51+
- **Simple control flow**: Standard if/else, loops, pattern matching
52+
- **Performance**: Virtual threads provide high throughput without callback complexity
53+
- **Readability**: Code reads like synchronous code but runs asynchronously
54+
55+
## Integration
56+
57+
Direct-style works seamlessly with blocking I/O operations, database calls, and HTTP requests, all efficiently managed by virtual threads under the hood.
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
---
2+
description:
3+
globs:
4+
alwaysApply: false
5+
---
6+
# Ox Structured Concurrency
7+
8+
All concurrent operations in Ox must happen within concurrency scopes that guarantee proper cleanup and resource management. Scopes define the lifetime of forks (threads) through code structure.
9+
10+
## Scope Types
11+
12+
- **`supervised`**: Default scope with error propagation
13+
- **`supervisedError`**: For application errors that should propagate as values
14+
- **`unsupervised`**: Manual error management, no automatic cancellation
15+
16+
## Good Examples
17+
18+
```scala
19+
import ox.{fork, supervised, sleep}
20+
import scala.concurrent.duration.*
21+
22+
// Basic supervised scope
23+
supervised {
24+
val f1 = fork {
25+
sleep(2.seconds)
26+
1
27+
}
28+
val f2 = fork {
29+
sleep(1.second)
30+
2
31+
}
32+
(f1.join(), f2.join())
33+
}
34+
35+
// Helper methods requiring Ox capability
36+
def forkComputation(p: Int)(using Ox): Fork[Int] = fork {
37+
sleep(p.seconds)
38+
p + 1
39+
}
40+
41+
supervised {
42+
val f1 = forkComputation(2)
43+
val f2 = forkComputation(4)
44+
(f1.join(), f2.join())
45+
}
46+
```
47+
48+
## Scope Guarantees
49+
50+
1. **Cleanup**: All forks are interrupted when scope ends
51+
2. **Completion**: Scope waits for all forks to complete before returning
52+
3. **Error propagation**: Exceptions in any fork can end the entire scope
53+
4. **Resource safety**: No leaked threads or resources
54+
55+
## Fork Types
56+
57+
- `fork`: Daemon fork (scope doesn't wait if body completes)
58+
- `forkUser`: User fork (scope waits for completion)
59+
- `forkError`/`forkUserError`: With application error support
60+
- `forkUnsupervised`: Manual management (only in unsupervised scopes)
61+
62+
## Avoid
63+
64+
```scala
65+
// Never fork outside of scopes
66+
val f = fork { ??? } // Compile error!
67+
68+
// Don't create long-running global scopes
69+
val globalScope = supervised { ??? } // Bad practice
70+
71+
// Don't ignore scope requirements
72+
def helper(): Fork[Int] = fork { ??? } // Missing Ox capability
73+
```
74+
75+
## Best Practices
76+
77+
- Keep scopes as small as possible
78+
- Create scopes for single requests, messages, or jobs
79+
- Nest scopes when needed for complex operations
80+
- Use appropriate scope type for your error handling needs
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
---
2+
description:
3+
globs:
4+
alwaysApply: false
5+
---
6+
# Ox Virtual Threads
7+
8+
Ox leverages Java 21 virtual threads, making blocking operations cheap and efficient. Don't be afraid to block or create many concurrent forks.
9+
10+
## Key Principles
11+
12+
- **Blocking is cheap**: Virtual threads have low memory footprint and fast context switching
13+
- **Create many forks**: Thousands of virtual threads can run efficiently
14+
- **No thread pool management**: Virtual threads are managed by the JVM runtime
15+
- **Direct blocking**: No need for asynchronous programming patterns
16+
17+
## Good Examples
18+
19+
```scala
20+
import ox.{fork, supervised, sleep}
21+
import scala.concurrent.duration.*
22+
23+
// Creating many concurrent operations
24+
supervised {
25+
val forks = (1 to 1000).map { i =>
26+
fork {
27+
sleep(1.second) // Blocking is fine!
28+
processItem(i)
29+
}
30+
}
31+
forks.map(_.join())
32+
}
33+
34+
// Blocking I/O operations
35+
def fetchFromDatabase(): List[User] =
36+
// This blocks, but that's perfectly fine with virtual threads
37+
databaseConnection.query("SELECT * FROM users")
38+
39+
def processFiles(): Unit =
40+
supervised {
41+
val files = listFiles()
42+
files.map { file =>
43+
fork {
44+
// Blocking file I/O
45+
val content = Files.readString(file.toPath)
46+
processContent(content)
47+
}
48+
}.foreach(_.join())
49+
}
50+
```
51+
52+
## Benefits of Virtual Threads
53+
54+
- **High throughput**: Handle millions of concurrent requests
55+
- **Low memory usage**: ~200 bytes per virtual thread vs ~2MB per platform thread
56+
- **Fast creation**: Creating virtual threads is very cheap
57+
- **Familiar programming model**: Use blocking APIs without performance penalties
58+
59+
## Blocking Operations That Are Efficient
60+
61+
```scala
62+
// All of these are efficient with virtual threads:
63+
val data = httpClient.get(url) // HTTP requests
64+
val result = database.query(sql) // Database queries
65+
val content = Files.readString(path) // File I/O
66+
val response = socket.read() // Network I/O
67+
Thread.sleep(duration) // Sleeping/delays
68+
```
69+
70+
## Avoid Unnecessary Complexity
71+
72+
```scala
73+
// Don't use callback-style when you can block
74+
// Bad:
75+
httpClient.getAsync(url, callback)
76+
77+
// Good:
78+
val response = httpClient.get(url)
79+
80+
// Don't limit concurrent operations artificially
81+
// Bad:
82+
val executor = Executors.newFixedThreadPool(10)
83+
84+
// Good: Create as many forks as needed
85+
supervised {
86+
items.map(item => fork { process(item) })
87+
}
88+
```
89+
90+
## Usage Tips
91+
92+
- Virtual threads shine with I/O-bound operations
93+
- CPU-bound tasks might be better served by running on a dedicated thread pool
94+
- Use `forkUser` when you need to wait for all operations to complete
95+
- Prefer high-level operations (`par`, `race`) which avoid creating scopes manually
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
---
2+
description: Ox uses two distinct channels for errors: exceptions for bugs/unexpected situations, and application errors as values (`Either`, union types).
3+
globs:
4+
alwaysApply: false
5+
---
6+
# Ox Error Handling: Dual Channel
7+
8+
Ox uses two distinct channels for error handling: exceptions for bugs and unexpected situations, and application errors represented as values.
9+
10+
## Two Error Channels
11+
12+
1. **Exceptions**: For bugs, unexpected situations, integration with Java libraries
13+
2. **Application errors**: Represented as values using `Either`, union types, or custom data types
14+
15+
## Exception Handling
16+
17+
Exceptions are handled by computation combinators and propagate normally:
18+
19+
```scala
20+
import ox.{par, race, supervised, fork}
21+
22+
// Exceptions propagate through high-level operations
23+
val result = par(
24+
computation1(), // might throw
25+
computation2() // cancelled if computation1 throws
26+
)
27+
28+
// Exceptions end supervised scopes
29+
supervised {
30+
fork {
31+
sleep(1.second)
32+
println("This won't print")
33+
}
34+
fork {
35+
sleep(500.millis)
36+
throw new RuntimeException("boom!") // Ends entire scope
37+
}
38+
}
39+
40+
// Use try/catch for exception handling
41+
try {
42+
riskyOperation()
43+
} catch {
44+
case e: SpecificException => handleError(e)
45+
}
46+
```
47+
48+
## Application Errors as Values
49+
50+
For type-safe error handling, use `Either` and error modes:
51+
52+
```scala
53+
import ox.either
54+
import ox.either.ok
55+
56+
case class User()
57+
case class Organization()
58+
case class Assignment(user: User, org: Organization)
59+
60+
def lookupUser(id: Int): Either[String, User] = ???
61+
def lookupOrganization(id: Int): Either[String, Organization] = ???
62+
63+
// Use either block to unwrap values
64+
val result: Either[String, Assignment] = either:
65+
val user = lookupUser(1).ok()
66+
val org = lookupOrganization(2).ok()
67+
Assignment(user, org)
68+
```
69+
70+
## Union Types for Multiple Error Types
71+
72+
```scala
73+
import ox.either
74+
import ox.either.ok
75+
76+
val v1: Either[DatabaseError, String] = ???
77+
val v2: Either[NetworkError, String] = ???
78+
79+
// Errors are combined in union type
80+
val result: Either[DatabaseError | NetworkError, String] = either:
81+
v1.ok() ++ v2.ok()
82+
```
83+
84+
## Error Modes for Advanced Scenarios
85+
86+
```scala
87+
import ox.{supervisedError, EitherMode}
88+
89+
// Use supervisedError for application error propagation
90+
given ErrorMode[String] = EitherMode[String]
91+
92+
supervisedError {
93+
val f1 = forkError {
94+
Left("application error") // Propagated as value, not exception
95+
}
96+
f1.join() // Either[String, ResultType]
97+
}
98+
```
99+
100+
## Best Practices
101+
102+
### Use Exceptions For
103+
- Bugs and programming errors
104+
- Unexpected system failures
105+
- Integration with Java libraries that throw
106+
- Violations of preconditions/invariants
107+
108+
### Use Application Errors For
109+
- Expected business logic failures
110+
- Validation errors
111+
- API error responses
112+
- Type-safe error handling
113+
114+
### Working with Eithers
115+
- use the `either` block within which `Either` values can be unwrapped using `.ok()`
116+
117+
## Exception Conversion
118+
119+
Convert exceptions to values when needed:
120+
121+
```scala
122+
import ox.either
123+
124+
// Convert exception-throwing code to Either
125+
val result: Either[Throwable, String] = either.catching {
126+
riskyOperationThatThrows()
127+
}
128+
129+
// Handle specific exception types
130+
val result2: Either[SQLException, List[User]] = either.catching {
131+
databaseQuery()
132+
}.recover {
133+
case _: ConnectionException => Left(ConnectionError)
134+
}
135+
```
136+
137+
## Avoid
138+
139+
```scala
140+
// Don't mix error handling approaches inconsistently
141+
def badExample(): Either[String, User] = {
142+
if (condition) throw new Exception("mixed approach") // Bad!
143+
else Right(user)
144+
}
145+
146+
// Don't swallow exceptions without handling
147+
try {
148+
riskyOperation()
149+
} catch {
150+
case _ => // Bad - don't ignore errors
151+
}
152+
```

0 commit comments

Comments
 (0)