# result: Concurrency API — open design problem ## Current API ```go Async[T](fn func() T) <-chan Expect[T] AsyncOf[T](fn func() Expect[T]) <-chan Expect[T] Bind[T, A](fn func(A) Expect[T], arg A) func() Expect[T] ``` Typical call site: ```go hostCh := result.AsyncOf(result.Bind(parseHost, "localhost")) portCh := result.AsyncOf(func() result.Expect[int] { return parsePort("8080") }) ``` ## The problem Every async call requires either an explicit closure or a `Bind` call. For single-arg library functions `Bind` works cleanly; for zero-arg and multi-arg functions the caller must write a closure, introducing visual noise that does not add information. ### Why `result.Async(parsePort("8080"))` does not work `parsePort("8080")` is an eager call — it evaluates immediately on the calling goroutine and returns an `int`. `Async` expects `func() int`. The compiler rejects it. There is no way in Go to pass a call expression as a deferred computation without wrapping it in a closure. ## Options explored ### 1. `AsyncBind` / `AsyncBind2` — collapse `AsyncOf` + `Bind` ```go func AsyncBind[T, A any](fn func(A) Expect[T], a A) <-chan Expect[T] func AsyncBind2[T, A, B any](fn func(A, B) Expect[T], a A, b B) <-chan Expect[T] ``` Reduces call site to `result.AsyncBind(parsePort, "8080")`. **Downside:** still need `AsyncBind3`, etc. for higher arities; `Bind` itself becomes redundant. ### 2. Higher-level combinator — `Join2`, `Join3`, … Hides channels entirely. Runs N computations concurrently, collects results, calls a combiner only on success: ```go url, err := result.Join2( result.Bind(parseHost, "localhost"), result.Bind(parsePort, "8080"), func(host string, port int) string { return fmt.Sprintf("http://%s:%d", host, port) }, ).Unwrap() ``` **Upside:** no channels, no closures in application code; most declarative. **Downside:** loses the "start goroutines, do other work, collect later" pattern; still needs `Join2`/`Join3`/… per arity; heterogeneous type combinations explode the generic signature. ### 3. Reflection / `any` variadic A single `AsyncCall(fn any, args ...any)` using `reflect.Call`. **Downside:** no type safety, runtime panics on arity/type mismatch, poor IDE support. Not worth it. ## Root constraint Go generics have no variadic type parameters. There is no way to write a single type-safe function that accepts an arbitrary function and its arguments. Every approach above hits this wall and works around it by duplicating the function for each arity (N = 1, 2, 3, …). ## Recommended direction when revisiting Decide first which usage pattern dominates: - **"Fire and forget, collect later"** — keep channels visible; `AsyncBind` / `AsyncBind2` are the minimal improvement. - **"Run N things, combine result"** — adopt `Join2`/`Join3`; channels disappear from application code entirely. A mixed API (both families) is possible but adds surface area. Keep it small.