neuron-explorer
Steering-first neuron explorer for Qwen3.5-4B: push every MLP neuron, keep every rollout, browse the results
翻译准备中
Scala on Go. Sealed types, exhaustive pattern matching, Option/Either/Try, bind/also do-notation, and compile-time data-race safety — transpiled to plain Go with full third-party module interop, zero-reflection JSON, and IDE tooling.
翻译准备中
取自仓库 README
正在加载完整 README。
查看完整 README ↗Go, the functional way.
Inspired by Scala: sum types, exhaustive pattern matching, and Option/Try — as a language that compiles to Go, not a library.
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.
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.)
Safe — Go's runtime bugs, caught by the compiler.
match is a build error, not a production panic.nil. Option / Either / Try replace nil checks and naked error returns, and &x is a Ptr[T] that is never nil: a Go pointer that may be nil is converted once (RequirePtr / OptionPtrOf) before it can fill one.collection_mutable values, var fields, raw Go slices/maps/pointers, and opaque Go handles.val bindings, immutable struct fields, no address for a val; always concrete types, never a silent any.Codec[T] is backed by a compiler-generated StructMeta[T] intrinsic — no reflect, no struct tags, and it doubles as a pattern-match extractor.Ergonomic — the functional code you want to write, minus the ceremony.
bind / also do-notation. Flat monadic binding over any monad — sequential bind, plus also for independent steps that accumulate errors (Validated) or run concurrently (Future). No nested FlatMap staircases.Option, Either, Try, Future, IO with Map / FlatMap / Recover, plus immutable List, Array, HashMap, HashSet, TreeSet, TreeMap.s"…" / f"…"), named arguments and default parameters, type inference everywhere, expression functions, and regex extractors that destructure capture groups directly inside match.GALA-Exxxx code, the offending span, and a hint — and runtime panics that report GALA source positions (foo.gala:12), not generated Go. gala build --json and gala explain hand the same diagnostics to tools and coding agents.Compatible — every Go library, no bindings, native binaries.
(T, error) returns are wrapped into Try[T] automatically.gala export writes a GALA library as a plain Go module over go.gala.fyi/stdlib; Go programs go get it and build it with go build, no GALA toolchain needed.gala lsp) for VS Code and Neovim, and a Claude Code plugin — diagnostics, hover with docs, go-to-definition, inlay hints, completion, across GALA and Go stdlib/third-party types. Batched transpilation and analysis caching keep multi-file rebuilds fast.&x is a Ptr[T]: never nil, and a plain Go *T in the generated code. A *T that may be nil cannot fill one (GALA-E0080) until it goes through RequirePtr or OptionPtrOf, and exported functions nil-check Ptr parameters on entry from Go. OptionPtr[T] is the nullable form, the size of one pointer, matched as Some(p) / None(). The standard library uses both.go.gala.fyi/stdlib, and gala export writes a GALA project as a plain Go module. Go callers get generated constructors (NewLineItem(...), With… options, IsCase() predicates), and a GALA method can declare Go's result list, (int, error), to implement io.Writer or json.Marshaler.UserRepo(Find = (id) => None(), Count = () => 0) builds an interface value inline, with lambda types taken from the interface; Go interfaces such as fmt.Stringer work too. Test doubles need no fake struct (Dependency Injection).case 1 | 2 (before 0.88, 1 | 2 was a bitwise OR, so it matched only 3), named sub-patterns , exhaustiveness decided on a pattern matrix (a guarded or partial case no longer covers its variant), and sequence patterns without a rest match an exact length.Also: generic type aliases and doc comments carried into the generated Go for go doc and gopls (0.85), local bidirectional type inference in place of the Hindley-Milner fallback (0.89), fuzzing that runs generated programs and checks their output (0.91), and about 1.2 GB less Go compile memory on a large app (0.92). Every change is in the release notes.
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 (needs Bazelisk):
git clone https://github.com/martianoff/gala.git && cd gala
bazel build //cmd/gala:gala
gala new hello && cd hello
This writes gala.mod, a main.gala, and a .claude/settings.json that enables the Claude Code plugin.
Replace main.gala with:
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 _ => s"Hello, ${p.Name}"
}
func main() {
Println(greet(Person("Alice", 25)))
}
gala run main.gala # transpile, compile, and run
gala build # build a native binary
gala test # run tests
Need help? Ask in GitHub Discussions.
val msg = shape match {
case Circle(r) => f"r=$r%.1f"
case Rectangle(w,h) => f"$w%.0fx$h%.0f"
case Point() => "point"
}
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"
}
val name = user.Name
.Map(strings.ToUpper)
.GetOrElse("ANONYMOUS")
name := "ANONYMOUS"
if user.Name != nil {
name = strings.ToUpper(*user.Name)
}
struct Config(Host string, Port int)
val updated = config.Copy(Port = 8080)
type Config struct {
Host string
Port int
}
updated := config // value copy; both stay mutable
updated.Port = 8080
(T, error) is a Try[T]func loadConfig(path string) Config =
os.ReadFile(path)
.FlatMap((b) => codec.Decode(ToString(b)))
.GetOrElse(Config(Host = "localhost"))
func loadConfig(path string) Config {
data, err := os.ReadFile(path)
if err != nil {
return Config{Host: "localhost", Port: 8080}
}
var cfg Config
if err := json.Unmarshal(data, &cfg); err != nil {
return Config{Host: "localhost", Port: 8080}
}
return cfg
}
os.ReadFile is Go's own function, called directly: its ([]byte, error) result arrives as a Try[[]byte], so the failure path composes instead of branching. Config is struct Config(Host string, Port int = 8080) — the field default fills in Port, and codec is a zero-reflection JSON codec (Codec[Config](SnakeCase())).
An Elm-architecture terminal UI framework, written from scratch in GALA. It is not a wrapper around a Go TUI library such as Bubble Tea or tview: the layout engine, differential renderer, widgets, input parsing, and async runtime are all GALA. Its only Go dependency is golang.org/x/term, for putting the terminal into raw mode. It is also the largest GALA codebase, and it leans on the language's features to do real work:
sealed type, so update is an exhaustive match: add a message and update stops compiling until it handles it. Side effects are data too — Cmd[T] is a sealed type (NoCmd, QuitCmd, BatchCmd, FutureCmd, …) that the runtime interprets.Future for async work. FutureCmd and AsyncTry run work off the UI loop and deliver the result back as a message.golang.org/x/term is imported straight into GALA, with no wrapper layer.Snapshot renders a widget to a string for golden-file tests; Harness runs the real key-decoding, update, and render loop against a fake terminal you can assert on.The demo above is a build-server dashboard: eight screens that between them show every widget, plus a fuzzy command palette, modals, toasts, mouse input, and four themes. The recording is generated from a committed script of keystrokes and clicks, not shot by hand. Run it yourself.
| Project | Description |
|---|---|
| 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. Its interface is a gala-tui app |
| GALA Server | Immutable HTTP server library with builder-pattern configuration, route groups, filters, and pattern matching |
| GALA Playground | Web playground (live) — write and run GALA in the browser |
GALA is dogfooded, not just demoed: its own tooling and these projects are written in the language, which is how its ergonomics get stress-tested.
bind / alsoMap/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
Validated[E, A] is a sealed type (Valid / Invalid) in its own validation package, deliberately separate from Either's fail-fast semantics. It works without bind/also too: Zip2 … Zip10 combine up to ten independent checks and return every failure, and ToEither() converts back when you want to stop accumulating.
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.
Expressions everywhere — single-expression functions skip braces, if is an expression, and a block returns its trailing value, so return is only needed for an early exit.
func square(x int) int = x * x
func label(score int) string {
val grade = if (score >= 90) "A" else if (score >= 75) "B" else "C"
s"$score -> $grade"
}
Named arguments and defaults — on functions and on struct fields. Arguments can come in any order; the compiler reorders them to match the declaration.
func connect(host string, port int = 8080, tls bool = true) Connection = Connection(host, port, tls)
connect("localhost") // port=8080, tls=true
connect("localhost", tls = false) // port=8080, tls=false
connect(host = "db", port = 5432) // tls=true
struct Config(Host string, Port int = 8080, Debug bool = false)
val cfg = Config(Host = "db", Debug = true) // Port = 8080
Sealed defaults — sealed-type cases take field defaults too, and a default case is the type's zero value: what an uninitialized var, or a field left out of a struct literal, holds. A sealed type without one cannot be left unset (GALA-E0075).
sealed type Light {
case On(Power int = 30)
default case Off()
}
var l Light // nothing assigned: the default case
Println(l, On(), On(Power = 80)) // Off() On(30) On(80)
Interface literals — build an interface value inline, one function per method, with the lambda types taken from the interface. It is how GALA fakes a dependency in a test, and Go interfaces work the same way.
type UserRepo interface {
Find(id int) Option[User]
Count() int
}
val fake = UserRepo(
Find = (id) => When(id == 1, User(1, "Ada")),
Count = () => 1,
)
val label = fmt.Stringer(String = () => "a Go interface, too")
Leaving out a method is GALA-E0078. See Dependency Injection.
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)
Guaranteed tail-call elimination — a function that calls itself in tail position compiles to a loop, so it runs in constant stack space.
func sumTo(n int, acc int) int = if (n <= 0) acc else sumTo(n - 1, acc + n)
Println(sumTo(1000000, 0)) // 500000500000 — no stack overflow
The guarantee is deliberately narrow: expression-bodied plain functions (no receiver) whose body is an if-expression with single-expression branches, calling themselves directly in tail position. Method receivers, match- and block-bodied functions, a tail call hidden inside a block branch, and mutual recursion all fall back to ordinary recursion. Verified in examples/tco_deep_sum.gala and examples/tco_factorial.gala.
use — scoped resource binding — binds a resource for the rest of the block and guarantees Close() on every exit path, normal or panic. Multiple bindings release LIFO.
func work() {
use a = open("a")
use b = open("b") // acquired last, closes first
Println(s"body sees ${a.name} and ${b.name}")
}
use is the replacement for Go's defer x.Close() — defer, go, goto, fallthrough, select, and chan are all rejected on the GALA surface (GALA-E0036), routed instead through use, the resource combinators (Using / Bracket / WithLock), and go_interop. Bare Go builtins are rejected the same way (GALA-E0035): len(x) becomes x.Size() (characters) or x.ByteSize() (bytes), and append / make / new / delete become go_interop helpers or GALA collections. Both checks are resolver-aware — a function you declared yourself that happens to share the name is left alone.
Tuples with destructuring — up to Tuple10, with pattern matching.
val pair = (1, "hello")
val (x, y) = pair
Opaque types — type X Y is only an alias; opaque type X Y is a distinct type with Y's representation and operators, so IDs and units stop mixing at no run-time cost. Conversion is explicit both ways, and the codecs write the bare scalar.
opaque type UserID int64
opaque type Millis int64
func (m Millis) Seconds() float64 = float64(m) / 1000.0
func lookup(id UserID) string = s"user ${int64(id)}"
val wait = Millis(1500) + 500
Println(lookup(UserID(42)), wait.Seconds()) // user 42 2
// lookup(int64(42)) is GALA-E0064: write lookup(UserID(n))
Non-null pointers — &x is a Ptr[T], a pointer that is never nil and is a plain *T in the generated Go. Only a var has an address (& of a val is GALA-E0076), and OptionPtr[T] is the pointer that may be absent:
struct Node(Value int, Next OptionPtr[Node]) // a nullable link, one pointer wide
func last(n Ptr[Node]) Ptr[Node] = n.Next match {
case Some(next) => last(next)
case None() => n
}
var data = 42
val p = &data // Ptr[int]
*p = 100 // OK: data is a var
val head = &Node(1, SomePtr(&Node(2, EmptyOptionPtr())))
Println(data, last(head).Value) // 100 2
A Go *T that may be nil cannot fill a Ptr (GALA-E0080); convert it once where it enters GALA code, with RequirePtr(p) (panics on nil) or OptionPtrOf(p).
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})")
val shown = "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 => s"$name is a minor"
case Person(name, age) if age > 65 => s"$name is a senior"
case _ => s"${p.Name} is an adult"
}
Alternative and named sub-patterns — | joins alternatives in one arm (it is never a bitwise OR in a pattern), and Field = p matches a field by name. Exhaustiveness sees both: these matches need no case _.
sealed type Shape {
case Circle(Radius int)
case Rect(Width int, Height int)
case Empty()
}
func kind(s Shape) string = s match {
case Circle(_) | Rect(_, _) => "solid"
case Empty() => "empty"
}
func height(s Shape) int = s match {
case Circle(Radius = r) => 2 * r
case Rect(Height = h) => h
case Empty() => 0
}
// kind(Rect(1, 2)) == "solid", height(Rect(5, 3)) == 3
Alternatives bind no names (case Some(n) | None() is GALA-E0071).
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(), Some(2), None(), Some(3))
val values = options.Collect({ case Some(v) => v * 10 })
// Array(10, 20, 30)
Future[T] is a monad first: Map, FlatMap, Recover, Zip2…Zip10, Sequence, Traverse, plus Succeeded / Failed / Completed extractors for match. On top of that sits structured concurrency — a Future carries an opaque cancellation token, and the scope constructors cancel the work they abandon:
val bounded = slow.WithTimeout(Milliseconds(500)).Recover((e) => 0)
val winner = Race(ArrayOf(a, b)) // first result; losers cancelled
val chain = source.Map(step1).FlatMap(step2)
chain.Cancel() // pending stages fail with CancellationError
Honest limits: cancellation is checked at combinator boundaries, so it short-circuits stages that have not started but cannot preempt a body already running (Go has no goroutine interruption). It is graph-level — a derived chain shares one token — while Race, Sequence, FirstCompletedOf, and WithTimeout open a fresh scope. .Cancel() on a completed Future does nothing. Full semantics: Concurrent.
Sharing a value across goroutines is safe when the value is deeply immutable — concurrent reads of something nobody can mutate never conflict. GALA turns that into a compile-time rule at every goroutine boundary, checking not just the value's type but what the closure captures:
var counter = 0
Future(counter + 1) // GALA-E0037: captures a reassignable `var`
val buffer = collection_mutable.ArrayOf(1, 2, 3)
Future(buffer.Size()) // GALA-E0037: a method call on a mutable value
val xs = collection_immutable.ArrayOf(1, 2, 3)
Future(xs.Size()) // OK — deeply immutable
The check is field-access-sensitive: a closure that reads only immutable (val) fields of shareable type is accepted even when the enclosing value is unshareable as a whole. A boundary is marked by the Sendable[F] type — transparent (Sendable[func() T] is func() T in the generated Go, no wrapper, no cost) and available to any library, not just the standard one. Application code passing closure literals never writes Sendable; only a reusable wrapper annotates its forwarded parameter, pushing the obligation to whoever writes the closure:
func afterCompute(compute Sendable[func() int]) Future[int] = Future(compute() + 1)
val base = 41
afterCompute(() => base).Get() // checked here, at the closure literal
The escape hatch is explicit: go_interop.Spawn / SpawnWithRecover are not Sendable, so raw goroutine plumbing is unchecked and synchronizing it is on you. See Concurrency Safety and GALA-E0037.
Compile errors are framed in the Rust/Elm style: a stable code, the offending span, and a hint.
error[GALA-E0035]: bare Go builtin "len(...)" is not part of GALA's surface
--> foo.gala:4:13
|
4 | val n = len(s)
| ^^^ use `.Size()`
|
= hint: use `.Size()` (logical size — characters for strings) or `.ByteSize()` (raw bytes) instead of `len(...)`
Codes are opaque and never change meaning, so tools and tests can pin to them, and each one has a page explaining when it fires, a minimal repro, and the fix — see the error code index.
The same information is available to tools and coding agents without scraping text:
gala build --json . # diagnostics as JSON: code, message, hint, file, line, column, docsUrl
gala explain GALA-E0035 # a code's full reference page, offline
gala doc collection_immutable.Array # every method on Array, from the terminal (add --json for tools)
Panics report GALA source positions, not generated Go:
panic: runtime error: integer divide by zero
main.divide(...)
foo.gala:4
main.main()
foo.gala:8
This rides on Go's own //line directives, so there is no source-map file and no runtime cost. Imported GALA packages map too — a single trace can span your files and a library's .gala source. The mapping is exact for ordinary statements; it is approximate (right file, line possibly off by a few within the construct) for statements that lower to several Go statements, such as use x = …, and for IIFE-lowered constructs (match, if-expressions, bind/also). Details: Performance and Debugging.
| 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) |
Validated[E, A] | Error-accumulating validation — Valid(a) / Invalid(errors), with Zip2…Zip10 |
Future[T] | Async computation with Map, FlatMap, Zip, Await, WithTimeout, Cancel |
IO[T] | Lazy, composable effect type — Suspend, Map, FlatMap, Recover |
Tuple[A, B] | Tuples with (a, b) syntax and destructuring (up to Tuple10) |
| 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() |
resource | Using / Bracket / WithLock — cleanup that runs on every exit path, including panics |
subprocess.Process | Value-type handle to a spawned child — ReadLine, WriteLine, Wait, Kill, KillAfter |
subprocess also ships ReadLineAsync / WriteLineAsync / CloseStdinAsync, which return a Future and own the goroutine-safety invariant internally — so you drive a child off-thread without capturing the handle across a Sendable boundary. See Subprocess.
| 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.
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 a Go call returning several results is one GALA value: (T, error) is a Try[T], (A, B) a Tuple[A, B].
A Go module built with plain go build can depend on GALA code. Every GALA release publishes the standard library as the Go module go.gala.fyi/stdlib at the same version, and gala export writes a GALA project as an ordinary Go module that requires it:
package pricing
// LineItem is one priced line of an order.
struct LineItem(SKU string, UnitCents int64, Qty int = 1)
// Total returns the line's price in cents.
func (l LineItem) Total() int64 = l.UnitCents * int64(l.Qty)
gala export --go-module github.com/you/pricing --out ../pricing-go
Commit the export to that repository at a vX.Y.Z tag, and a Go program uses it after go get github.com/you/pricing. An exported shorthand struct gets a New… constructor that applies its field defaults, with a With… option for each defaulted field, and the doc comments are carried into the generated Go for go doc and gopls:
import "github.com/you/pricing"
item := pricing.NewLineItem("A-1", 1000, pricing.WithLineItemQty(3))
fmt.Println(item.SKU.Get(), item.Total()) // A-1 3000
Exported sealed types get NewT<Case>(...) constructors and IsCase() predicates the same way. To implement a Go interface whose methods return several results, a GALA method declares Go's result list and gives the Try in its body:
struct LineCounter(var Lines int)
func (c Ptr[LineCounter]) Write(p []byte) (int, error) {
c.Lines = c.Lines + strings.Count(ToString(p), "\n")
Success(p.Size())
}
// var lc = LineCounter(0); fmt.Fprintf(&lc, "a\nb\nc\n") leaves lc.Lines == 3
Publishing, versions, and the current limits (an exported project cannot yet import another GALA module): Using GALA from a plain Go module.
GALA ships with a GoLand/IntelliJ plugin and an LSP server (gala lsp) for editor-agnostic support.
gala-intellij-plugin.zip from releases..gala file is opened.The plugin works locally (ANTLR parser, semantic highlighting, structure view, 13 live templates). The LSP server (gala lsp) adds real-time diagnostics including match-exhaustiveness, hover with inferred types and doc comments, type-aware completion with documentation, signature help, cross-file go-to-definition, find references, and inlay hints — for VS Code and Neovim too.
Go interop is wired end to end: completion and go-to-definition resolve Go stdlib and third-party Go modules, navigating into the module's cached source the same way native GALA symbols do. The .Size() / .ByteSize() sugar type-resolves and completes on Go primitives, the use keyword is highlighted with its binding clickable, and the forbidden-builtin / forbidden-statement rules surface inline as GALA-E0035 / GALA-E0036. Full feature list: IDE Support.
There is no dedicated GALA extension yet. Use a generic LSP-client extension and register gala lsp for .gala files; the exact settings key depends on the extension you pick, for example:
{
"lsp.servers": {
"gala": {
"command": "gala",
"args": ["lsp"],
"filetypes": ["gala"]
}
}
}
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({})
The GALA plugin for Claude Code connects Claude to gala lsp. Claude then gets diagnostics pushed into its context after every edit, and can look up hover types, definitions and references.
The plugin starts the gala binary but does not include it: install the GALA CLI and check that gala version works first. Then, in Claude Code:
/plugin marketplace add martianoff/gala
/plugin install gala@gala
Projects created with gala new include a .claude/settings.json, so Claude Code installs and enables the plugin once you trust the project folder. Details and troubleshooting: ide/claude-code.
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.
Building from source needs Bazelisk, which runs the Bazel version pinned in .bazelversion. The CLI lands at bazel-bin/cmd/gala/gala_/gala.
git clone https://github.com/martianoff/gala.git
cd gala
bazel build //cmd/gala:gala
The flake builds the CLI from source and also provides an overlay and a development shell:
nix profile install github:martianoff/gala
load("@rules_gala//gala:defs.bzl", "gala_binary", "gala_library")
gala_binary(
name = "myapp",
src = "main.gala",
)
bind / also do-notation over any monadGALA-Exxxx diagnostic, with repro and fixSendable boundaryContributions are welcome. Please ensure:
bazel build //... passesbazel test //... passesbazel run //:gazelle has been run if files were added or removedexamples//gala-lint, /simplify, and /reviewSee CONTRIBUTING.MD for details.
AI-assisted PRs, crawlers, and model training are all welcome — see the AI policy. Agents follow AGENTS.md.
Apache License 2.0. See LICENSE for details.
Rect(Height = h)default case Off() marks a sealed type's zero value, case fields take defaults, an uninitialized Option is None (it was Some(0)), and leaving a value of a type with no default unset is an error (GALA-E0075). & of a val is an error too (GALA-E0076).opaque type UserID int64 is a distinct type with the representation of int64: no run-time cost, its own methods, usable as a map key, and encoded by the codecs as the bare scalar.string, Str wrapper, and StringBuilder