| Related pages: Why GALA? | Code Examples | Immutable Collections | Mutable Collections | Streams | String Utilities | Time Utilities | Dependency Management |
GALA (Go Alternative LAnguage) is a modern programming language that transpiles to Go. It combines Go’s efficiency and simplicity with features inspired by Scala and other functional languages, such as immutability by default, pattern matching, and concise expression syntax.
Note: This is the complete language specification. For the full, continuously updated source, see the GALA.MD file on GitHub. The content below is a faithful copy of that specification.
GALA files use the .gala extension. Every file must start with a package declaration, followed by an empty line. All GALA files in the same directory must belong to the same package.
GALA supports Go-style imports, including aliases and dot imports. Import declarations must also be followed by an empty line.
package main
import (
"fmt"
m "math"
. "net/http"
)
A GALA package can span multiple .gala files. Types, sealed types, and functions defined in one file are available in all other files of the same package. Each file must declare the same package name.
shapes/
types.gala # struct Point, sealed type Shape
ops.gala # func (p Point) String(), func Describe(s Shape)
In Bazel, list all source files in srcs:
gala_library(
name = "shapes",
srcs = ["shapes/types.gala", "shapes/ops.gala"],
importpath = "myapp/shapes",
)
gala_binary(
name = "app",
srcs = ["main.gala", "helpers.gala"],
)
GALA distinguishes between immutable and mutable variables.
val)Variables declared with val are immutable. They must be initialized at declaration time.
val x = 10
val a, b = 1, 2
// x = 20 // Compile error: cannot assign to immutable variable
var)Variables declared with var are mutable and can be reassigned.
var y = 20
y = 30 // OK
Inside functions, := declares immutable variables.
func main() {
z := 40
// z = 50 // Compile error: cannot assign to immutable variable
}
GALA supports both Go-style block functions and Scala-style expression functions.
func add(a int, b int) int {
return a + b
}
func square(x int) int = x * x
Function parameters can be marked as val or var. By default, they are val (immutable).
func process(val data string, var count int) {
// data = "new" // Error
count = count + 1 // OK
}
Function calls support named arguments. Named arguments can appear in any order — the compiler reorders them to match the function signature.
func divide(dividend int, divisor int) int = dividend / divisor
divide(divisor = 4, dividend = 20) // 5 — reordered to divide(20, 4)
divide(20, 4) // 5 — positional works too
Named arguments work with struct construction, Copy() method overrides, and regular function calls.
Parameters can have default values. When a function is called without providing a defaulted argument, the default expression is injected at the call site. Default parameters must come after required parameters.
func connect(host string, port int = 8080, tls bool = true) {
Println(s"Connecting to $host:$port (tls=$tls)")
}
connect("localhost") // port=8080, tls=true
connect("localhost", 3000) // tls=true
connect("localhost", 3000, false) // all explicit
// Named arguments + defaults: skip any defaulted parameter
connect("localhost", tls = false) // port=8080
connect(host = "localhost", port = 443) // tls=true
Default expressions are evaluated at each call site (not once at definition time):
func log(msg string, ts time.Time = time.Now()) {
Println(s"[$ts] $msg")
}
Expression functions also support defaults:
func greet(name string, greeting string = "Hello") string = s"$greeting, $name!"
The compiler validates defaults at compile time:
Functions are first-class values:
func apply(x int, f func(int) int) int = f(x)
func main() {
val double = (x int) => x * 2
val result = apply(5, double) // result = 10
}
func sum(numbers ...int) int {
var total = 0
var i = 0
for ; i < len(numbers) ; {
total = total + numbers[i]
i = i + 1
}
return total
}
type Box[T any] struct { Value T }
func (b Box[T]) GetValue() T = b.Value
func (b Box[T]) Transform[U any](f func(T) U) Box[U] = Box[U](Value = f(b.Value))
struct Person(name string, age int, var score int)
type Person struct {
Name string // Immutable
age int // Immutable
var Score int // Mutable
}
val p1 = Person{Name: "Alice", Age: 30} // Named fields (Go-style)
val p2 = Person("Bob", 25) // Positional (Functional-style)
val p3 = Person(age = 20, name = "Charlie") // Named arguments
val p1 = Person("Alice", 30)
val p2 = p1.Copy(age = 31) // p2 is Person("Alice", 31)
val same = p1.Equal(p2) // false
sealed type Shape {
case Circle(Radius float64)
case Rectangle(Width float64, Height float64)
case Point()
}
val c = Circle(3.14)
val desc = c match {
case Circle(radius) => fmt.Sprintf("radius=%.2f", radius)
case Rectangle(w, h) => fmt.Sprintf("%fx%f", w, h)
case Point() => "point"
}
Generic sealed types:
sealed type Result[T any] {
case Ok(Value T)
case Err(Error error)
}
type Shaper interface {
Area() float64
}
struct Rect(width float64, height float64)
func (r Rect) Area() float64 = r.width * r.height
val status = if (score > 50) "pass" else "fail"
val result = x match {
case 1 => "one"
case 2 => "two"
case n => "Value is " + fmt.Sprintf("%d", n)
case _ => "other"
}
// Boolean exhaustive match
val desc = flag match {
case true => "enabled"
case false => "disabled"
}
val res = x match {
case s: string => "Found string: " + s
case i: int => "Found int: " + fmt.Sprintf("%d", i)
case _ => "Unknown type"
}
type Even struct {}
func (e Even) Unapply(i int) Option[int] = if (i % 2 == 0) Some(i) else None[int]()
42 match {
case Even(n) => s"$n is even"
case _ => "odd"
}
val res = x match {
case i: int if i > 100 => "Large integer"
case Person(name, age) if age < 18 => name + " is a minor"
case _ => "Other"
}
val arr = ArrayOf(1, 2, 3, 4, 5)
val res = arr match {
case Array(head, tail...) => fmt.Sprintf("Head: %d, Tail size: %d", head, tail.Size())
case _ => "Empty"
}
for i := 0; i < 10; i++ {
fmt.Println(i)
}
for count < 5 {
count++
}
for _, v := range items {
fmt.Println(v)
}
val f = (x int) => x * x
// Inferred parameter types
val doubled = opt.Map((x) => x * 2)
// Void closures
opt.ForEach((x) => { fmt.Println(x) })
val pf = { case 1 => "one" case 2 => "two" }
val r1 = pf(1) // Some("one")
val r2 = pf(5) // None[string]
// Use with Collect
val evenDoubled = numbers.Collect({ case n if n % 2 == 0 => n * 2 })
func identity[T any](x T) T = x
type Box[T any] struct { Value T }
val x = Some(10)
val y = None[int]()
val msg = x match {
case Some(v) => s"Got: $v"
case None() => "Empty"
}
val result = x.Map((i) => i * 2)
val value = x.GetOrElse(0)
val t = (1, "hello")
val (a, b) = t // a = 1, b = "hello"
val e = Right[int, string]("success")
val msg = e match {
case Left(code) => s"Error code: $code"
case Right(s) => "Result: " + s
}
val result = Try(() => riskyDivide(10, 0))
val dir = Try(os.TempDir) // function reference sugar
val msg = result match {
case Success(n) => s"Got: $n"
case Failure(e) => s"Error: ${e.Error()}"
}
import . "martianoff/gala/concurrent"
val async = FutureApply[int](() => expensiveComputation())
val result = async.Await() // Returns Try[int]
val doubled = async.Map((v) => v * 2)
Prefer GALA collections over Go slices for most use cases. See Immutable Collections.
// PREFERRED: GALA collections
val nums = ArrayOf(1, 2, 3, 4, 5)
val doubled = nums.Map((x) => x * 2)
// GO INTEROP: When you need []T
val goSlice = SliceOf(1, 2, 3)
val name = "Alice"
val age = 30
Println(s"Hello $name!") // Hello Alice!
Println(s"$name is $age years old") // Alice is 30 years old
Println(s"Next year: ${age + 1}") // Next year: 31
Println(s"Price: $$99") // Price: $99
// Explicit format specs
Println(f"$count%04d items") // 0007 items
Println(f"Total: $$$price%.2f") // Total: $19.99
val asterisk = '*'
val space = ' '
val newline = '\n'
val n = int64(42)
val f = float64(10)
val r = rune(65) // 'A'
val s = string(r) // "A"
Go’s built-in functions are available: len, cap, make, new, append, delete, close, panic, recover.
For most use cases, prefer GALA’s collection types and their methods over Go built-ins.
var data = 42
val ptr1 = &data // immutable pointer binding
*ptr1 = 100 // OK: can modify data through pointer
// ptr1 = &other // ERROR: cannot reassign immutable pointer
val data = 42
val ptr = &data // ptr is ConstPtr[int]
val value = *ptr // OK: read
// *ptr = 100 // ERROR: cannot write through ConstPtr
GALA supports Go-style imports with aliases and dot imports.
import m "math"
import . "martianoff/gala/examples/mathlib"
func main() {
val res = m.Sqrt(16.0)
val sum = Add(10, 20) // from mathlib via dot import
}
See Dependency Management for managing external packages.
embed val readme = "README.md" // string
embed val static EmbeddedFS = "static/*" // EmbeddedFS
val content = static.ReadString("static/index.html")
GALA provides a test framework with 22 assertions, panic recovery, timing, table-driven test support, and benchmarking.
package main
import . "martianoff/gala/test"
func TestAddition(t T) T {
val x = 1 + 1
return Eq(t, x, 2)
}
func TestDouble(t T) T {
return RunCases[int, int](t,
(sub T, input int, expected int) => Eq(sub, input * 2, expected),
Case[int, int](Name = "zero", Input = 0, Expected = 0),
Case[int, int](Name = "positive", Input = 5, Expected = 10),
)
}
func main() {
RunBenchmarks(
BenchFunc(Name = "BenchmarkAdd", Func = (b B) => {
for i := 0; i < b.N; i++ {
val x = 1 + 1
}
}),
)
}
val over var - Use mutable variables only when necessaryCopy() for updates - person.Copy(age = 31)match over if-else chains for type/value dispatchcase Some(x) => not if opt.IsDefined() { x := opt.Get() }Some(42) not Some[int](42)s"..." over fmt.Sprintf - s"Hello $name" not fmt.Sprintf("Hello %s", name)Array or List from collection_immutableOption[T] for nullable values, Try[T] for operations that may failGALA provides a module system similar to Go modules. See Dependency Management for the full guide.
gala mod init github.com/user/project
gala mod add github.com/example/utils@v1.2.3
gala mod add github.com/google/uuid@v1.6.0 --go
gala mod tidy
A basic IntelliJ IDEA plugin is available in ide/intellij.
bazel build //ide/intellij:pluginThe plugin provides syntax highlighting, file type recognition (.gala), and basic code structure support.
Full specification: This page contains a summary of the GALA language specification with all major features documented. For the complete, unabridged specification with every detail and edge case, see the GALA.MD source file on GitHub.