Skip to content

Commit 3acd02c

Browse files
authored
Improve documentation clarity and reorganize examples (#41)
1 parent 72244bc commit 3acd02c

File tree

6 files changed

+286
-243
lines changed

6 files changed

+286
-243
lines changed

README.md

Lines changed: 183 additions & 155 deletions
Large diffs are not rendered by default.

example123_test.go

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,11 @@ func ExampleFromSeq() {
1818

1919
// Transform each number
2020
// Concurrency = 3
21-
results := rill.Map(numbers, 3, func(x int) (int, error) {
22-
return doSomethingWithNumber(x), nil
21+
squares := rill.Map(numbers, 3, func(x int) (int, error) {
22+
return square(x), nil
2323
})
2424

25-
printStream(results)
25+
printStream(squares)
2626
}
2727

2828
func ExampleFromSeq2() {
@@ -40,11 +40,11 @@ func ExampleFromSeq2() {
4040

4141
// Transform each number
4242
// Concurrency = 3
43-
results := rill.Map(numbers, 3, func(x int) (int, error) {
44-
return doSomethingWithNumber(x), nil
43+
squares := rill.Map(numbers, 3, func(x int) (int, error) {
44+
return square(x), nil
4545
})
4646

47-
printStream(results)
47+
printStream(squares)
4848
}
4949

5050
func ExampleToSeq2() {
@@ -53,12 +53,12 @@ func ExampleToSeq2() {
5353

5454
// Transform each number
5555
// Concurrency = 3
56-
results := rill.Map(numbers, 3, func(x int) (int, error) {
57-
return doSomethingWithNumber(x), nil
56+
squares := rill.Map(numbers, 3, func(x int) (int, error) {
57+
return square(x), nil
5858
})
5959

6060
// Convert the stream into an iterator and use for-range to print the results
61-
for val, err := range rill.ToSeq2(results) {
61+
for val, err := range rill.ToSeq2(squares) {
6262
if err != nil {
6363
fmt.Println("Error:", err)
6464
break // cleanup is done regardless of early exit

example_test.go

Lines changed: 78 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ func Example_batching() {
6767
}, nil)
6868

6969
// Group IDs into batches of 5
70-
idBatches := rill.Batch(ids, 5, 1*time.Second)
70+
idBatches := rill.Batch(ids, 5, -1)
7171

7272
// Bulk fetch users from the API
7373
// Concurrency = 3
@@ -108,7 +108,7 @@ func Example_batching() {
108108
// emits partial batches, ensuring that updates are delayed by at most 100ms.
109109
//
110110
// For simplicity, this example does not have retries, error handling and synchronization
111-
func Example_batchingWithTimeout() {
111+
func Example_batchingRealTime() {
112112
// Start the background worker that processes the updates
113113
go updateUserTimestampWorker()
114114

@@ -170,20 +170,13 @@ func Example_ordering() {
170170
// The string to search for in the downloaded files
171171
needle := []byte("26")
172172

173-
// Manually generate a stream of URLs from http://example.com/file-0.txt to http://example.com/file-999.txt
174-
urls := make(chan rill.Try[string])
175-
go func() {
176-
defer close(urls)
177-
for i := 0; i < 1000; i++ {
178-
// Stop generating URLs after the context is canceled (when the file is found)
179-
// This can be rewritten as a select statement, but it's not necessary
180-
if err := ctx.Err(); err != nil {
181-
return
182-
}
173+
// Start with a stream of numbers from 0 to 999
174+
fileIDs := streamNumbers(ctx, 0, 1000)
183175

184-
urls <- rill.Wrap(fmt.Sprintf("https://example.com/file-%d.txt", i), nil)
185-
}
186-
}()
176+
// Generate a stream of URLs from http://example.com/file-0.txt to http://example.com/file-999.txt
177+
urls := rill.OrderedMap(fileIDs, 1, func(id int) (string, error) {
178+
return fmt.Sprintf("https://example.com/file-%d.txt", id), nil
179+
})
187180

188181
// Download and process the files
189182
// At most 5 files are downloaded and held in memory at the same time
@@ -251,17 +244,16 @@ func sendMessage(message string, server string) error {
251244
return nil
252245
}
253246

254-
// This example demonstrates using [FlatMap] to accelerate paginated API calls. Instead of fetching all users sequentially,
255-
// page-by-page (which would take a long time since the API is slow and the number of pages is large), it fetches users from
256-
// multiple departments in parallel. The example also shows how to write a reusable streaming wrapper around an existing
257-
// API function that can be used on its own or as part of a larger pipeline.
258-
func Example_parallelStreams() {
247+
// This example demonstrates using [FlatMap] to fetch users from multiple departments concurrently.
248+
// Additionally, it demonstrates how to write a reusable streaming wrapper over paginated API calls - the StreamUsers function
249+
func Example_flatMap() {
259250
ctx := context.Background()
260251

261-
// Convert a list of all departments into a stream
262-
departments := rill.FromSlice(mockapi.GetDepartments())
252+
// Start with a stream of department names
253+
departments := rill.FromSlice([]string{"IT", "Finance", "Marketing", "Support", "Engineering"}, nil)
263254

264-
// Use FlatMap to stream users from 3 departments concurrently.
255+
// Stream users from all departments concurrently.
256+
// At most 3 departments at the same time.
265257
users := rill.FlatMap(departments, 3, func(department string) <-chan rill.Try[*mockapi.User] {
266258
return StreamUsers(ctx, &mockapi.UserQuery{Department: department})
267259
})
@@ -276,7 +268,7 @@ func Example_parallelStreams() {
276268

277269
// StreamUsers is a reusable streaming wrapper around the mockapi.ListUsers function.
278270
// It iterates through all listing pages and returns a stream of users.
279-
// This function is useful on its own or as a building block for more complex pipelines.
271+
// This function is useful both on its own and as part of larger pipelines.
280272
func StreamUsers(ctx context.Context, query *mockapi.UserQuery) <-chan rill.Try[*mockapi.User] {
281273
res := make(chan rill.Try[*mockapi.User])
282274

@@ -309,43 +301,38 @@ func StreamUsers(ctx context.Context, query *mockapi.UserQuery) <-chan rill.Try[
309301
return res
310302
}
311303

312-
// This example demonstrates how to use a context for pipeline termination.
313-
// The FindFirstPrime function uses several concurrent workers to find the first prime number after a given number.
314-
// Internally it creates a pipeline that starts from an infinite stream of numbers. When the first prime number is found
315-
// in that stream, the context gets canceled, and the pipeline terminates gracefully.
304+
// This example demonstrates how to gracefully stop a pipeline on the first error.
305+
// The CheckAllUsersExist uses several concurrent workers and returns an error as soon as it encounters a non-existent user.
306+
// Such early return triggers the context cancellation, which in turn stops all remaining users fetches.
316307
func Example_context() {
317-
p := FindFirstPrime(10000, 3) // Use 3 concurrent workers
318-
fmt.Println("The first prime after 10000 is", p)
308+
ctx := context.Background()
309+
310+
// ID 999 doesn't exist, so fetching will stop after hitting it.
311+
err := CheckAllUsersExist(ctx, 3, []int{1, 2, 3, 4, 5, 999, 7, 8, 9, 10})
312+
fmt.Printf("Check result: %v\n", err)
319313
}
320314

321-
// FindFirstPrime finds the first prime number after the given number, using several concurrent workers.
322-
func FindFirstPrime(after int, concurrency int) int {
323-
ctx, cancel := context.WithCancel(context.Background())
315+
// CheckAllUsersExist uses several concurrent workers to checks if all users with given IDs exist.
316+
func CheckAllUsersExist(ctx context.Context, concurrency int, ids []int) error {
317+
// Create new context that will be canceled when this function returns
318+
ctx, cancel := context.WithCancel(ctx)
324319
defer cancel()
325320

326-
// Generate an infinite stream of numbers starting from the given number
327-
numbers := make(chan rill.Try[int])
328-
go func() {
329-
defer close(numbers)
330-
for i := after + 1; ; i++ {
331-
select {
332-
case <-ctx.Done():
333-
return // Stop generating numbers when the context is canceled
334-
case numbers <- rill.Wrap(i, nil):
335-
}
321+
idsStream := rill.FromSlice(ids, nil)
322+
323+
// Fetch users concurrently.
324+
users := rill.Map(idsStream, concurrency, func(id int) (*mockapi.User, error) {
325+
u, err := mockapi.GetUser(ctx, id)
326+
if err != nil {
327+
return nil, fmt.Errorf("failed to fetch user %d: %w", id, err)
336328
}
337-
}()
338329

339-
// Filter out non-prime numbers, preserve the order
340-
primes := rill.OrderedFilter(numbers, concurrency, func(x int) (bool, error) {
341-
fmt.Println("Checking", x)
342-
return isPrime(x), nil
330+
fmt.Printf("Fetched user %d\n", id)
331+
return u, nil
343332
})
344333

345-
// Get the first prime and cancel the context
346-
// This stops number generation and allows goroutines to exit
347-
result, _, _ := rill.First(primes)
348-
return result
334+
// Return the first error (if any) and cancel remaining fetches via context
335+
return rill.Err(users)
349336
}
350337

351338
// --- Function examples ---
@@ -591,10 +578,10 @@ func ExampleForEach() {
591578
// Convert a slice of numbers into a stream
592579
numbers := rill.FromSlice([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, nil)
593580

594-
// Do something with each number and print the result
581+
// Square each number and print the result
595582
// Concurrency = 3
596583
err := rill.ForEach(numbers, 3, func(x int) error {
597-
y := doSomethingWithNumber(x)
584+
y := square(x)
598585
fmt.Println(y)
599586
return nil
600587
})
@@ -610,15 +597,15 @@ func ExampleForEach_ordered() {
610597
// Convert a slice of numbers into a stream
611598
numbers := rill.FromSlice([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, nil)
612599

613-
// Do something with each number
600+
// Square each number
614601
// Concurrency = 3; Ordered
615-
results := rill.OrderedMap(numbers, 3, func(x int) (int, error) {
616-
return doSomethingWithNumber(x), nil
602+
squares := rill.OrderedMap(numbers, 3, func(x int) (int, error) {
603+
return square(x), nil
617604
})
618605

619606
// Print results.
620607
// Concurrency = 1; Ordered
621-
err := rill.ForEach(results, 1, func(y int) error {
608+
err := rill.ForEach(squares, 1, func(y int) error {
622609
fmt.Println(y)
623610
return nil
624611
})
@@ -633,11 +620,11 @@ func ExampleMap() {
633620

634621
// Transform each number
635622
// Concurrency = 3
636-
results := rill.Map(numbers, 3, func(x int) (int, error) {
637-
return doSomethingWithNumber(x), nil
623+
squares := rill.Map(numbers, 3, func(x int) (int, error) {
624+
return square(x), nil
638625
})
639626

640-
printStream(results)
627+
printStream(squares)
641628
}
642629

643630
// The same example as for the [Map], but using ordered versions of functions.
@@ -647,11 +634,11 @@ func ExampleOrderedMap() {
647634

648635
// Transform each number
649636
// Concurrency = 3; Ordered
650-
results := rill.OrderedMap(numbers, 3, func(x int) (int, error) {
651-
return doSomethingWithNumber(x), nil
637+
squares := rill.OrderedMap(numbers, 3, func(x int) (int, error) {
638+
return square(x), nil
652639
})
653640

654-
printStream(results)
641+
printStream(squares)
655642
}
656643

657644
func ExampleMapReduce() {
@@ -709,11 +696,11 @@ func ExampleToSlice() {
709696

710697
// Transform each number
711698
// Concurrency = 3; Ordered
712-
results := rill.OrderedMap(numbers, 3, func(x int) (int, error) {
713-
return doSomethingWithNumber(x), nil
699+
squares := rill.OrderedMap(numbers, 3, func(x int) (int, error) {
700+
return square(x), nil
714701
})
715702

716-
resultsSlice, err := rill.ToSlice(results)
703+
resultsSlice, err := rill.ToSlice(squares)
717704

718705
fmt.Println("Result:", resultsSlice)
719706
fmt.Println("Error:", err)
@@ -735,15 +722,8 @@ func ExampleUnbatch() {
735722

736723
// --- Helpers ---
737724

738-
// helper function that squares the number
725+
// helper function that checks if a number is prime
739726
// and simulates some additional work using sleep
740-
func doSomethingWithNumber(x int) int {
741-
randomSleep(500 * time.Millisecond) // simulate some additional work
742-
return x * x
743-
}
744-
745-
// naive prime number check.
746-
// also simulates some additional work using sleep
747727
func isPrime(n int) bool {
748728
randomSleep(500 * time.Millisecond) // simulate some additional work
749729

@@ -758,6 +738,29 @@ func isPrime(n int) bool {
758738
return true
759739
}
760740

741+
// helper function that squares the number
742+
// and simulates some additional work using sleep
743+
func square(x int) int {
744+
randomSleep(500 * time.Millisecond) // simulate some additional work
745+
return x * x
746+
}
747+
748+
// helper function that creates a stream of numbers [start, end) and respects the context
749+
func streamNumbers(ctx context.Context, start, end int) <-chan rill.Try[int] {
750+
out := make(chan rill.Try[int])
751+
go func() {
752+
defer close(out)
753+
for i := start; i < end; i++ {
754+
select {
755+
case <-ctx.Done():
756+
return
757+
case out <- rill.Try[int]{Value: i}:
758+
}
759+
}
760+
}()
761+
return out
762+
}
763+
761764
// printStream prints all items from a stream (one per line) and an error if any.
762765
func printStream[A any](stream <-chan rill.Try[A]) {
763766
fmt.Println("Result:")

mockapi/README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Mock API
2+
3+
A minimal mock API implementation used in runnable examples on [pkg.go.dev](https://pkg.go.dev/github.com/destel/rill).
4+
5+
This package simulates common API patterns like:
6+
- Single and bulk item fetching
7+
- Pagination
8+
- Network delay simulation
9+
- Realistic error scenarios
10+
11+
The package is intentionally kept public to enable running and experimenting with examples in the Go Playground.

mockapi/users.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
// Package mockapi provides a very basic mock API client for examples and demos.
2-
// Unfortunately it must live outside the internal folder to be accessible from runnable examples and go playground.
1+
// Package mockapi provides a very basic mock API for examples and demos.
2+
// It's intentionally kept public to enable running and experimenting with examples in the Go Playground.
33
package mockapi
44

55
import (
@@ -54,11 +54,12 @@ func GetDepartments() ([]string, error) {
5454

5555
// GetUser returns a user by ID.
5656
func GetUser(ctx context.Context, id int) (*User, error) {
57-
randomSleep(ctx, 500*time.Millisecond)
5857
if err := ctx.Err(); err != nil {
5958
return nil, err
6059
}
6160

61+
randomSleep(ctx, 500*time.Millisecond)
62+
6263
mu.RLock()
6364
defer mu.RUnlock()
6465

util.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ func Drain[A any](in <-chan A) {
77
core.Drain(in)
88
}
99

10-
// DrainNB is a non-blocking version of [Drain]. Is does draining in a separate goroutine.
10+
// DrainNB is a non-blocking version of [Drain]. It does draining in a separate goroutine.
1111
func DrainNB[A any](in <-chan A) {
1212
core.DrainNB(in)
1313
}

0 commit comments

Comments
 (0)