GALA is statically typed, but you rarely need to write type annotations. The compiler infers variable types, lambda parameter types, generic type arguments, and accumulator types from context. The result is code that reads like a dynamic language but compiles with full type safety.
val nums = ArrayOf(1, 2, 3, 4, 5)
val doubled = nums.Map((x) => x * 2)
val sum = nums.FoldLeft(0, (acc, x) => acc + x)
In the snippet above, GALA infers:
nums is Array[int] from the arguments to ArrayOfx in the Map lambda is int from the collection’s element typeacc in FoldLeft is int from the zero value 0x in FoldLeft is int from the collection’s element typeNo type annotations needed. No any or interface{} in the generated Go code.
The type of val and var bindings is inferred from the right-hand side:
val x = 42 // int
val name = "Alice" // string
val pi = 3.14159 // float64
val active = true // bool
val pair = Tuple(1, "two") // Tuple[int, string]
val opt = Some(42) // Option[int]
You can add an explicit type annotation when needed — for example, to assign to an interface type or to use a wider type:
val x float64 = 42 // float64 (not int)
val s Shaper = Circle(5.0) // interface type
When a lambda is passed to a method with known parameter types, the lambda’s parameter types are inferred from the method signature. This is GALA’s most impactful inference feature — it eliminates the most common type annotations in functional code.
// The compiler knows Array[int].Map takes func(int) U
// So (x) must be int
val doubled = ArrayOf(1, 2, 3).Map((x) => x * 2)
// The compiler knows Array[int].Filter takes func(int) bool
// So (x) must be int
val evens = ArrayOf(1, 2, 3).Filter((x) => x % 2 == 0)
Lambda parameter inference works in these contexts:
Generic receiver types — Methods on Array[T], List[T], Option[T], HashMap[K,V], and other generic types. The receiver’s type parameters are resolved and substituted into the method signature:
val opt = Some(42)
val doubled = opt.Map((x) => x * 2) // x inferred as int
opt.ForEach((x) => { Println(x) }) // x inferred as int
val positive = opt.Filter((x) => x > 0) // x inferred as int
Non-generic wrapper types — Methods on concrete types that take function parameters:
val s = S("hello")
val upper = s.Map((r) => r - 32) // r inferred as rune
val hasVowel = s.Exists((r) => r == 'a') // r inferred as rune
Free function calls — Lambda parameters are also inferred when passed to generic free functions:
val result = identity((x) => x * 2)
When you call a generic method, GALA infers the method’s type parameters from the concrete argument types. You almost never need to write them explicitly:
// Map[U] — U is inferred as int from the lambda return type
val doubled = ArrayOf(1, 2, 3).Map((x) => x * 2)
// Instead of the explicit form:
// val doubled = ArrayOf(1, 2, 3).Map[int]((x int) => x * 2)
This works for all generic methods including Map, FlatMap, Filter, FoldLeft, Zip, Collect, and more.
The accumulator type parameter U in FoldLeft[U](zero U, f func(U, T) U) is inferred from the zero value argument:
val nums = ArrayOf(1, 2, 3)
// acc is int because the zero value is 0 (int)
val sum = nums.FoldLeft(0, (acc, x) => acc + x)
// acc is string because the zero value is "" (string)
val csv = nums.FoldLeft("", (acc, x) => acc + s"$x,")
// acc is float64 because the zero value is 0.0 (float64)
val avg = nums.FoldLeft(0.0, (acc, x) => acc + float64(x))
No explicit type annotation on the accumulator parameter, the zero value, or the generic type parameter.
When constructing generic types, type parameters are inferred from the arguments:
// Inferred: Some[int]
val x = Some(42)
// Inferred: Right[string, int]
val r = Right[string, int](42)
// Inferred: Tuple[int, string]
val t = Tuple(1, "hello")
// Inferred: ListOf creates List[int]
val list = ListOf(1, 2, 3)
// Inferred: HashMapOf creates HashMap[string, int]
val m = HashMapOf(("a", 1), ("b", 2))
Write Some(42), not Some[int](42). Write ListOf(1, 2, 3), not ListOf[int](1, 2, 3).
Sealed type variant constructors infer their types from the arguments:
sealed type Shape {
case Circle(Radius float64)
case Rectangle(Width float64, Height float64)
case Point()
}
val c = Circle(5.0) // Shape, no type annotation needed
val r = Rectangle(3.0, 4.0) // Shape
GALA does not infer types in these contexts — you must provide explicit annotations:
Top-level function parameters and return types:
// Parameters MUST have type annotations
func add(a int, b int) int = a + b
// Return type MUST be specified
func greet(name string) string = s"Hello, $name"
Interface implementations — when a value needs to satisfy an interface, you may need to annotate the variable:
val s Shaper = Circle(5.0) // explicit interface type
Ambiguous literals — when the compiler cannot determine which numeric type you mean:
val x float64 = 42 // 42 would default to int without annotation
| Context | Recommendation |
|---|---|
val x = 42 |
Omit — inferred as int |
val x = Some(42) |
Omit — inferred as Option[int] |
| Lambda parameters in pipelines | Omit — inferred from method signature |
| FoldLeft accumulator | Omit — inferred from zero value |
| Generic method type params | Omit — inferred from arguments |
| Function parameters | Annotate — always required |
| Function return types | Annotate — always required |
| Interface variable assignment | Annotate — needed for interface dispatch |
| Wider numeric types | Annotate — val x float64 = 42 |
The general rule: omit types inside function bodies, annotate types at function boundaries.
Under the hood, GALA uses a two-layer inference system:
Layer 1 (Pattern-based) — Fast inference that handles 90%+ of cases: literals, scope lookups, function call return types, struct field types, and operator results.
Layer 2 (Hindley-Milner) — Full unification-based inference for complex cases: generic function instantiation, lambda parameter inference, and polymorphic type schemes.
The compiler tries Layer 1 first for speed. If it returns an unresolved type (containing type parameters like T), Layer 2 takes over with Algorithm W unification. This two-layer approach keeps compile times fast while handling complex generic code correctly.