Sum types, exhaustive pattern matching, and Option/Try — the type system Go is missing, as a language, not a library.
Scala on Go.
sealed type Shape {
case Circle(Radius float64)
case Rectangle(Width float64, Height float64)
}
func area(s Shape) string = s match {
case Circle(r) => f"circle area: ${3.14159 * r * r}%.2f"
case Rectangle(w, h) => f"rect area: ${w * h}%.2f"
}
Try it in your browser — no install.
What is GALA?
GALA is a statically typed, functional-first language that transpiles to Go. You get sealed types, exhaustive pattern matching, immutable collections, and a real monad stack (Option, Either, Try, Future, IO) — and you keep the entire Go ecosystem, including third-party modules, working out of the box.
It's aimed at Go developers who have used Scala, Kotlin, F#, or OCaml and miss the type system.
(GALA stands for Go Alternative LAnguage.)
Why GALA? — Safe, Ergonomic, Compatible
Safe — Go's runtime bugs, caught by the compiler.
- Sealed types with exhaustive matching. The compiler rejects incomplete matches at build time — including nested patterns, guards, and generic extractors. An incomplete
matchis a build error, not a production panic. - No
nil.Option/Either/Tryreplace nil checks and naked error returns. - Immutable by default.
valbindings, immutable struct fields, read-onlyConstPtr[T]; always concrete types, never a silentany. - Zero-reflection JSON.
Codec[T]is backed by a compiler-generatedStructMeta[T]intrinsic — noreflect, no struct tags, and it doubles as a pattern-match extractor.
Ergonomic — the functional code you want to write, minus the ceremony.
bind/alsodo-notation. Flat monadic binding over any monad — sequentialbind, plusalsofor independent steps that accumulate errors (Validated) or run concurrently (Future). No nestedFlatMapstaircases.- Functional standard library.
Option,Either,Try,Future,IOwithMap/FlatMap/Recover, plus immutableList,Array,HashMap,HashSet,TreeSet,TreeMap. - Less syntax. String interpolation (
s"…"/f"…"), named arguments and default parameters, type inference everywhere, expression functions, and regex extractors that destructure capture groups directly insidematch.
Compatible — every Go library, no bindings, native binaries.
- Full Go third-party module interop. Any Go package works — not just stdlib. Return types are inferred directly from the Go SDK (no declaration files), and
(T, error)returns are wrapped intoTry[T]automatically. - Shipped IDE tooling. GoLand/IntelliJ plugin and an LSP server (
gala lsp) for VS Code and Neovim — diagnostics, hover, go-to-definition, inlay hints, completion. Batched transpilation and analysis caching keep multi-file rebuilds fast.
Quick Start
1. Install
Download a pre-built binary from Releases, rename it to gala (or gala.exe on Windows), and put it on your PATH.
GALA transpiles to Go, so it needs Go 1.25+ on your PATH to compile programs.
Or build from source:
git clone https://github.com/martianoff/gala.git && cd gala bazel build //cmd/gala:gala
2. Write main.gala
package main
struct Person(Name string, Age int)
func greet(p Person) string = p match {
case Person(name, age) if age < 18 => s"Hey, $name!"
case Person(name, _) => s"Hello, $name"
case _ => "Unknown"
}
func main() {
Println(greet(Person("Alice", 25)))
}
3. Run
gala mod init example.com/hello gala run main.gala # Transpile + compile + run gala build # Build project to a binary gala test # Run tests
Need help? Ask in GitHub Discussions.
GALA vs Go
Pattern Matching vs Switch
| GALA | Go |
|---|---|
|
var msg string switch s := shape.(type) { case Circle: msg = fmt.Sprintf("r=%.1f", s.Radius) case Rectangle: msg = fmt.Sprintf("%.0fx%.0f", s.Width, s.Height) case Point: msg = "point" } |
Option Handling vs nil Checks
| GALA | Go |
|---|---|
|
name := "ANONYMOUS" if user.Name != nil { name = strings.ToUpper(*user.Name) } |
Immutable Structs vs Manual Copying
| GALA | Go |
|---|---|
|
type Config struct { Host string Port int } updated := config // value copy; both stay mutable updated.Port = 8080 |
Default Parameters vs Option Structs
| GALA | Go |
|---|---|
|
type ConnectOptions struct { Host string Port int TLS *bool } func Connect(opts ConnectOptions) Connection { if opts.Port == 0 { opts.Port = 8080 } if opts.TLS == nil { t := true; opts.TLS = &t } // ... } Connect(ConnectOptions{Host: "localhost", TLS: ptrBool(false)}) |
Error Handling: Try vs if-err
| GALA | Go |
|---|---|
|
result, err := divide(10, 2) if err == nil { result, err = divide(result*2, 3) } if err != nil { result = 0 } |
Monadic Binding: bind / also
Map/FlatMap chains nest badly the moment a later step needs an earlier value. bind flattens them — every binding is a normal immutable local that stays in scope, and the block short-circuits on the first failure:
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 nested FlatMap
}
also marks a bind as independent of its group, and the block's type decides what that unlocks. Over Validated it accumulates every error instead of stopping at the first:
import . "martianoff/gala/validation"
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))
}
// makePerson("", "", -1).GetErrors().Size() == 3 — all failures at once
Over Future, an also group runs its clauses concurrently. And it's not special-cased to the standard library: any type with a FlatMap method is bindable, resolved structurally at transpile time — no higher-kinded types. See Monadic binding.
More Language Features
Expression functions — single-expression bodies skip braces and return.
func square(x int) int = x * x
func max(a int, b int) int = if (a > b) a else b
Named arguments — any order; compiler reorders to match the signature.
func connect(host string, port int = 8080, tls bool = true) Connection
connect("localhost") // port=8080, tls=true
connect("localhost", tls = false) // port=8080, tls=false
connect(host = "db", port = 5432) // tls=true
Lambda type inference — parameter types and method type parameters are inferred from context.
val list = ListOf(1, 2, 3)
val doubled = list.Map((x) => x * 2)
val sum = list.FoldLeft(0, (acc, x) => acc + x)
Tuples with destructuring — up to Tuple5, with pattern matching.
val pair = (1, "hello")
val (x, y) = pair
Read-only pointers — ConstPtr[T] prevents accidental mutation through pointers.
val data = 42
val ptr = &data // ConstPtr[int], not *int
val value = *ptr // OK: read
// *ptr = 100 // compile error: cannot write through ConstPtr
String interpolation — s"..." with auto-inferred format verbs and f"..." with explicit format specs. No imports needed.
val name = "Alice"
val age = 30
Println(s"$name is $age years old") // Alice is 30 years old
Println(f"Pi = ${3.14159}%.2f") // Pi = 3.14
Println(s"${nums.MkString(", ")}") // 1, 2, 3
Zero-reflection JSON codec — Codec[T] uses the compiler-generated StructMeta[T] intrinsic for fully typed serialization with no reflection, no struct tags, and pattern matching support.
struct Person(FirstName string, LastName string, Age int)
val codec = Codec[Person](SnakeCase())
val jsonStr = codec.Encode(Person("Alice", "Smith", 30)).Get()
// {"first_name":"Alice","last_name":"Smith","age":30}
val decoded = codec.Decode(jsonStr) // Try[Person]
val name = jsonStr match {
case codec(p) => p.FirstName // pattern matching!
case _ => "unknown"
}
Regex with pattern matching — compile-safe regex with extractors that destructure capture groups directly in match.
val dateRegex = regex.MustCompile("(\\d{4})-(\\d{2})-(\\d{2})")
"2024-01-15" match {
case dateRegex(Array(year, month, day)) => s"$year/$month/$day"
case _ => "not a date"
}
Pattern matching with guards.
val status = p match {
case Person(name, age) if age < 18 => name + " is a minor"
case Person(name, age) if age > 65 => name + " is a senior"
case Person(name, _) => name + " is an adult"
case _ => "Unknown"
}
Type-based pattern matching.
val res = x match {
case s: string => s"string: $s"
case i: int => s"int: $i"
case _ => "unknown"
}
Collect — filter and transform in one pass.
val nums = ArrayOf(1, 2, 3, 4, 5, 6)
val evenDoubled = nums.Collect({ case n if n % 2 == 0 => n * 2 })
// Array(4, 8, 12)
val options = ArrayOf(Some(1), None[int](), Some(2), None[int](), Some(3))
val values = options.Collect({ case Some(v) => v * 10 })
// Array(10, 20, 30)
Standard Library
Functional Types
| Type | Description |
|---|---|
Option[T] |
Optional values — Some(value) / None() |
Either[A, B] |
Disjoint union — Left(a) / Right(b) |
Try[T] |
Failable computation — Success(value) / Failure(err) |
Future[T] |
Async computation with Map, FlatMap, Zip, Await |
IO[T] |
Lazy, composable effect type — Suspend, Map, FlatMap, Recover |
Tuple[A, B] |
Pairs and triples with (a, b) syntax (up to Tuple5) |
ConstPtr[T] |
Read-only pointer with auto-deref field access |
Serialization, Regex, IO
| Type | Description |
|---|---|
Codec[T] |
Zero-reflection JSON codec with Encode, Decode, Rename, Omit, pattern matching |
yaml.Codec[T] |
YAML codec sharing the same StructMeta[T] intrinsic |
Regex |
Compiled regex with Matches, FindFirst, FindAll, ReplaceAll, pattern matching |
IO[T] |
Lazy effect — separates description from execution, re-runs on every .Run() |
Collections
| Type | Kind | Key Operations | Best for |
|---|---|---|---|
List[T] |
Immutable | O(1) prepend, O(n) index | Recursive processing, prepend-heavy workloads |
Array[T] |
Immutable | O(1) random access | General-purpose indexed sequences |
HashMap[K,V] |
Immutable | O(1) lookup | Functional key-value storage |
HashSet[T] |
Immutable | O(1) membership | Unique element collections |
TreeSet[T] |
Immutable | O(log n) sorted ops | Ordered unique elements, range queries |
TreeMap[K,V] |
Immutable | O(log n) sorted ops | Sorted key-value storage, range queries |
All collections support Map, Filter, FoldLeft, ForEach, Exists, Find, Collect, MkString, Sorted, SortWith, SortBy, and more.
TreeMap[K,V] is a Red-Black tree that maintains entries in sorted key order. It provides MinKey, MaxKey, Range(from, to), RangeFrom, RangeTo, and conversion to HashMap, Go maps, or sorted arrays.
Mutable variants of all collection types are available in collection_mutable for performance-sensitive code.
Built with GALA
GALA is dogfooded, not just demoed — its own tooling and these projects are written in the language, which is how the ergonomics get stress-tested. Real applications written in GALA:
| Project | Description |
|---|---|
| GALA Playground | Web playground (live) — write and run GALA in the browser with 9 built-in examples |
| State Machine Example | State machines with sealed types + pattern matching — order FSM, traffic light, vending machine (with Go comparison) |
| Log Analyzer | Structured log parsing with Go stdlib interop (strings, strconv, fmt) + functional pipelines (with Go comparison) |
| GALA Server | Immutable HTTP server library with builder-pattern configuration, route groups, filters, and pattern matching |
| GALA TUI | Elm-architecture TUI framework — immutable widgets, differential renderer, async runtime, fuzzy command palette, markdown, mouse, themes |
| GALA Team | Multi-agent Claude CLI orchestrator — a Team Lead delegates to Engineers and QAs, reviews their work, and hands you a PR for sign-off |
All of the above are written in GALA, not just "use GALA somewhere."
Dependency Management
gala mod init github.com/user/project gala mod add github.com/example/[email protected] gala mod add github.com/google/[email protected] --go gala mod tidy
Third-party Go modules are first-class. GALA reads the Go SDK to infer return types, and multi-return (T, error) patterns are auto-wrapped into Try[T] at the call site.
IDE Support
GALA ships with a GoLand/IntelliJ plugin and an LSP server (gala lsp) for editor-agnostic support.
GoLand / IntelliJ IDEA
- Install GALA CLI: download from releases and add to PATH.
- Install plugin: GoLand > Settings > Plugins > Install from Disk > select
gala-intellij-plugin.zipfrom releases. - Restart GoLand — the LSP server starts automatically when a
.galafile is opened.
The plugin works locally (ANTLR parser, semantic highlighting, structure view, 12 live templates). The LSP server (gala lsp) adds real-time diagnostics including match-exhaustiveness, hover types, cross-file go-to-definition, type-aware completion, and inlay hints — for VS Code and Neovim too. Full feature list: IDE Support.
VS Code
Add to .vscode/settings.json:
{
"lsp.servers": {
"gala": {
"command": "gala",
"args": ["lsp"],
"filetypes": ["gala"]
}
}
}
Neovim
require('lspconfig.configs').gala = { default_config = { cmd = { 'gala', 'lsp' }, filetypes = { 'gala' }, root_dir = require('lspconfig.util').root_pattern('gala.mod', '.git'), }, } require('lspconfig').gala.setup({})
Installation
Pre-built Binaries
Download from Releases:
| Platform | Binary |
|---|---|
| Linux (x64) | gala-linux-amd64 |
| Linux (ARM64) | gala-linux-arm64 |
| macOS (x64) | gala-darwin-amd64 |
| macOS (Apple Silicon) | gala-darwin-arm64 |
| Windows (x64) | gala-windows-amd64.exe |
After downloading, rename the binary to gala (or gala.exe on Windows) and add it to your PATH.
Prerequisite: GALA needs Go 1.25+ on your PATH to compile programs. Install Go before running gala build or gala run.
Build from Source
git clone https://github.com/martianoff/gala.git
cd gala
bazel build //cmd/gala:gala
Using Bazel
load("@rules_gala//gala:defs.bzl", "gala_binary", "gala_library") gala_binary( name = "myapp", src = "main.gala", )
Documentation
- Language Specification — complete language reference
- Why GALA? — feature deep-dive and honest trade-offs
- Examples — code examples for all features
- Type Inference — how type inference works
- JSON Codec — zero-reflection JSON serialization
- Regex — pattern matching with regex extractors
- IO Effect — lazy, composable side effects
- Go Interop — seamless Go library integration
- Concurrent — Future, Promise, and ExecutionContext
- Stream — lazy, potentially infinite sequences
- Immutable Collections — List, Array, HashMap, HashSet, TreeSet, TreeMap
- Mutable Collections — mutable variants for performance
- Strings — free functions over
string,Strwrapper, andStringBuilder - Time Utils — Duration and Instant types
- Dependency Management — module system
Contributing
Contributions are welcome. Please ensure:
bazel build //...passesbazel test //...passes- New features include examples in
examples/ - Documentation is updated for grammar/feature changes
See CONTRIBUTING.MD for details.
License
Apache License 2.0. See LICENSE for details.

0 Comments
Log in to join the conversation.No comments yet. Be the first to share your thoughts.