# GALA in June: do-notation, applicative validation, and concurrent binds (0.56 -> 0.62)

*A follow-up to [The State of GALA: May 2026](https://martianov.dev/state-of-gala-may-2026).*

In May, GALA hit 0.50.0 with sealed types, exhaustive pattern matching, a real monad stack (`Option`, `Either`, `Try`, `Future`, `IO`), and a functional standard library — all transpiling to plain Go. The one thing that stack was still missing was a decent way to *write* against it.

You had `Map` and `FlatMap`, which are great for a single linear pipeline and get ugly the moment a step needs a value from two steps back. Every intermediate value is trapped inside a closure, so the "graph" shape — where a later step reads an earlier one — pushes you into a staircase of nested `FlatMap` calls.

The headline of the June work (0.56 → 0.62) fixes exactly that. GALA now has **`bind` / `also`** — monadic do-notation, the piece that makes "Scala on Go" actually feel like writing Scala. Here's what shipped.

## `bind`: the flat version of a `FlatMap` chain

Take a small order pipeline. Fetch an order, validate it, charge it, build a receipt — where the receipt needs *both* the original order and the payment. With combinators, that last cross-reference forces nesting:

```gala
fetchOrder(id).FlatMap((o) =>
    validateOrder(o).FlatMap((valid) =>
        chargePayment(valid).FlatMap((payment) =>
            Success(Receipt(o.Id, payment)))))   // `o` survives only via nesting
```

With `bind`, every binding is a normal immutable local that stays in scope for the rest of the block:

```gala
func processOrder(id int) Try[Receipt] {
    bind o       = fetchOrder(id)
    bind valid   = validateOrder(o)
    bind payment = chargePayment(valid)
    Success(Receipt(o.Id, payment))    // `o` still in scope — no nesting
}
```

`bind name = expr` unwraps the monad, binds the success value, and short-circuits the *whole block* on the first `Failure` — so `processOrder(0)` stops at the first `bind` and returns that `Failure` unchanged. This isn't Rust's `?` or Zig's `try`: it's not a hidden return that hijacks the enclosing function. The block is an ordinary expression of type `Try[Receipt]` — you can name it, return it, or pass it around. It just desugars, mechanically, to the nested `FlatMap` chain above.

If you've written a Scala for-comprehension or a Haskell `do` block, this is the same idea with a keyword that tells you it can short-circuit, instead of a `<-` you have to learn to read.

## `also`: independent binds, and why the keyword earns its keep

Not every step depends on the one before it. Validating a name, an email, and an age are three independent checks. Sequencing them with `bind` is a lie about the data flow — and it throws away everything except the *first* error.

`also` marks a bind as independent of its group:

```gala
func sum2(x string, y string) Option[int] {
    bind a = lookup(x)
    also b = lookup(y)      // independent of `a`
    Some(a + b)
}
```

A leading `bind` plus one or more `also` clauses form a *product group*. The clauses may not reference each other — and that non-dependence is exactly what licenses the compiler to do something smarter than sequencing. What "smarter" means is **decided by the type**:

| Type                    | What an `also` group does                     |
|-------------------------|-----------------------------------------------|
| `Try` / `Option` / `Either` | sequential short-circuit on the first failure |
| `Validated`             | **accumulates every error**                   |
| `Future`                | runs the clauses **concurrently**             |

One keyword, three behaviors, no annotations — the block's type picks the semantics.

## `Validated`: collect all the errors, not just the first

Fail-fast is wrong for form validation. If a user submits a blank name, a blank email, and a negative age, you want to tell them all three things at once, not make them fix them one round-trip at a time. That's what the new `Validated[E, A]` type is for — it's a distinct sealed type (`Valid` / `Invalid`), kept deliberately separate from `Either`'s fail-fast semantics.

```gala
import . "martianoff/gala/validation"

func vName(s string)  Validated[string, string] = if (s != "") Valid(s) else InvalidOf("name required")
func vEmail(s string) Validated[string, string] = if (s != "") Valid(s) else InvalidOf("email required")
func vAge(n int)      Validated[string, int]    = if (n >= 0)  Valid(n) else InvalidOf("age negative")

func makePerson(name string, email string, age int) Validated[string, Person] {
    bind n = vName(name)
    also e = vEmail(email)
    also a = vAge(age)
    Valid(Person(n, e, a))
}
```

Because the clauses are `also` (independent), the group lowers to `Validated.Zip3(...).FlatMap(...)`, and `Validated`'s `Zip` pre-collects every clause's errors before combining. So `makePerson("", "", -1)` doesn't stop at the blank name — it reports all three:

```gala
val bad = makePerson("", "", -1)
Println(s"errors: ${bad.GetErrors().Size()}")   // 3
```

Note there isn't a single explicit type argument in that code. `Valid` / `Invalid` fix their phantom type parameter from the declared return type, and `InvalidOf` infers its instantiation from context — implicit typing all the way down, the way GALA code is supposed to read.

## `also` over `Future`: structured concurrency for free

The same `also` over `Future` runs the clauses *concurrently*:

```gala
import . "martianoff/gala/concurrent"

func total() Future[int] {
    bind a = compute(2)
    also b = compute(3)    // these three
    also c = compute(4)    // run in parallel
    Future[int](a + b + c)
}
```

The group lowers to `Future.Zip3(...)`, which starts every future and joins them — so the block waits on all three in parallel instead of threading each through the next. Concurrency is *visible in the source*: sequential `bind`s stay sequential, only an `also` group runs in parallel. The transpiler never auto-parallelizes consecutive `bind`s behind your back.

(While we were in `Future`, the async constructor got simpler too: `Future(...)` is now the way to start an async computation, and the old `FutureApply` spelling is gone.)

## The part I'm proud of: it works over *your* monads

Here's the design constraint that made this interesting. GALA's standing rule is that the standard library gets **no special treatment** — `Try`, `Option`, `Either`, `Future` have to resolve through the same mechanism a third-party type would. And Go's generics can't express a higher-kinded `Bindable[F[_]]` interface, so GALA doesn't fake one.

Instead, `bind`/`also` are resolved **structurally, at transpile time, on the block's concrete type** — before Go ever sees the code. A type becomes bindable by providing exactly one method:

```gala
func (m M[T]) FlatMap[U any](f func(T) M[U]) M[U]
```

That's the whole contract. Here's a user-defined `Step` monad with no relationship to the standard library, and `bind` works over it identically to `Try`:

```gala
sealed type Step[T any] {
    case Go(Value T)
    case Stop(Reason string)
}

func (s Step[T]) FlatMap[U any](f func(T) Step[U]) Step[U] = s match {
    case Go(v)   => f(v)
    case Stop(r) => Stop[U](r)
}

func pipeline(n int) Step[int] {
    bind a = start(n)
    bind b = twice(a)
    Go(a + b)          // `a` still in scope; a `Stop` anywhere short-circuits
}
```

The short-circuit behavior lives entirely inside the author's `FlatMap` — the desugaring never inspects, and never hardcodes, which variant is the "zero." Supply a `Pure`/constructor and you also get auto-lift of trailing plain values and sequential `also`; supply a custom `Zip` and you get concurrency or error accumulation. A brand-new user monad gets `bind` and sequential `also` for free. This is the OCaml `let*` / `and*` model, resolved structurally instead of lexically. There is no built-in list of "blessed" monads.

## The rest of June

Beyond `bind`/`also`, 0.56 → 0.62 was mostly the unglamorous work that a compiler needs to actually be trusted:

- **`Zip` up to `Zip10`** on the product types, so `also` groups can be wide.
- **By-name thunk sugar** for zero-argument function parameters — pass an expression where a `func()` is expected and it's wrapped for you.
- **Race-free concurrent parsing.** Multi-file packages now parse in parallel with a per-parse prediction cache, so the parser is safe under concurrency.
- **A stack of type-inference fixes**: inferring a generic method's result type from a lambda's return under type-parameter name collisions; inferring "phantom" return-only type parameters from the expected type; inferring lambda parameter types in typed contexts (and *rejecting* truly-untyped ones instead of silently widening to `any`); statement-position `match` dispatch with a guarded wildcard before the default.
- **A standard-library cleanup pass** applying GALA's own best practices across `std`.

Every one of those inference fixes shipped with a permanent example test, per the project rule that a transpiler bug becomes a repro before it becomes a fix.

## What's still rough

Same honest list as always:

- **Package registry.** Dependencies resolve — Go and GALA, including third-party Go modules — but there's still no first-class public registry for publishing and discovering GALA libraries. It remains the top open roadmap item.
- **`bind` ergonomics at the edges.** Heterogeneous binds (binding an `Option` inside a `Try` block, say) need a resolvable `Lift`, and lifting a "zero" type into an error type makes you supply the error explicitly. That's by design — no silent coercions — but it's a sharp edge you'll meet.
- **Maturity.** GALA is six months old. It generates readable Go and runs real software — a TUI framework, a multi-agent orchestrator, this project's own tooling — but it hasn't been load-bearing in a large team's production system for a year. Use it with eyes open.

## Try it

`bind`/`also` is the feature I most wanted GALA to have, because it's the one that turns a pile of good combinators into something that reads like the functional languages it's borrowing from. If you've missed for-comprehensions or `do`-notation in Go, this is the part to try first.

**[Try it in your browser](https://gala-playground.fly.dev)** — no install — or grab a binary from [Releases](https://github.com/martianoff/gala/releases) and `gala run main.gala`.
