Scala on Go. Same Go runtime, same Go libraries — different source language.

GALA vs Go – Side-by-Side Comparison

GALA is not replacing Go – it is adding expressiveness on top of it. GALA transpiles to standard Go code, so you get the same runtime, the same libraries, the same single-binary deployments, and the same performance. The difference is what you write to get there.

This page shows real code comparisons between GALA and the equivalent idiomatic Go. Every GALA snippet on this page uses only syntax from the language specification.

▶ Run GALA in the Playground View the code on GitHub


Pattern Matching vs Switch

GALA sealed types define closed hierarchies. The compiler enforces exhaustive matching – if you forget a case, it will not compile. Idiomatic Go models the same closed set with a marker-method interface and a type switch – but nothing checks that you handled every variant.

GALA

sealed type Shape {
    case Circle(Radius float64)
    case Rectangle(Width float64, Height float64)
    case Point()
}

val msg = shape match {
    case Circle(r)       => f"r=$r%.1f"
    case Rectangle(w, h) => f"$w%.0fx$h%.0f"
    case Point()         => "point"
}

Go

type Shape interface{ isShape() }
type Circle struct{ Radius float64 }
type Rectangle struct{ Width, Height float64 }
type Point struct{}
func (Circle) isShape()    {}
func (Rectangle) isShape() {}
func (Point) isShape()     {}

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"
}

GALA destructures values directly into named bindings (r, w, h) and produces a compile-time error if you forget a case. The idiomatic Go equivalent needs a marker-method interface (isShape() on every variant) to define the closed set, and its type switch is not exhaustiveness-checked – drop the Point case and it still compiles, silently leaving msg empty.


Option Handling vs nil Checks

GALA’s Option[T] type makes the presence or absence of a value explicit. You chain operations with Map, FlatMap, and GetOrElse instead of checking for nil.

GALA

val name = user.Name
    .Map((n) => strings.ToUpper(n))
    .GetOrElse("ANONYMOUS")

Go

name := "ANONYMOUS"
if user.Name != nil {
    name = strings.ToUpper(*user.Name)
}

With Option[T], you never forget a nil check. The type system enforces it. You can also pattern match on options directly:

val res = opt match {
    case Some(v) => s"got $v"
    case None()  => "nothing"
}

Immutable Structs vs Mutable Copies

GALA structs are immutable by default. Fields cannot be reassigned after construction. The compiler auto-generates a Copy() method for creating modified copies, and an Equal() method for structural comparison.

GALA

struct Config(Host string, Port int)
val updated = config.Copy(Port = 8080)

Go

type Config struct {
    Host string
    Port int
}
updated := config // shallow value copy
updated.Port = 8080
// config is still mutable here --
// nothing stops a later reassignment

Go’s value copy is concise, but the copy and the original stay fully mutable – and if Config holds a slice or map, the shallow copy shares it. GALA makes immutability the guarantee: fields can never be reassigned, Copy() is an expression you can drop straight into a pipeline, and the compiler also generates a structural Equal() (Go’s == breaks the moment a field is a slice or map).


Error Handling: Try Chain vs if-err

GALA’s Try[T] type wraps computations that can fail. Instead of checking if err != nil after every call, you chain operations with Map, FlatMap, and Recover.

GALA

val result = divide(10, 2)
    .Map((x) => x * 2)
    .FlatMap((x) => divide(x, 3))
    .Recover((e) => 0)

Go

result, err := divide(10, 2)
if err == nil {
    result, err = divide(result*2, 3)
}
if err != nil {
    result = 0
}

The GALA version reads as a linear pipeline: divide, double, divide again, recover on failure. The Go version threads an err variable through every step and interleaves the happy path with error checks. Both compile to equivalent logic, but GALA keeps the intent front and center – and each stage composes, so adding a step is one more .Map/.FlatMap, not another if err.

You can also pattern match on Try[T]:

val msg = result match {
    case Success(v) => s"got $v"
    case Failure(e) => s"error: $e"
}

Collection Pipelines vs Manual Loops

GALA provides immutable functional collections with Map, Filter, FoldLeft, Collect, and more. Go requires manual loops with append.

GALA

val nums = ArrayOf(1, 2, 3, 4, 5)
val result = nums
    .Filter((x) => x % 2 == 0)
    .Map((x) => x * 2)

Go

nums := []int{1, 2, 3, 4, 5}
var result []int
for _, x := range nums {
    if x%2 == 0 {
        result = append(result, x*2)
    }
}

GALA’s Collect combines filter and transform in a single pass using partial functions:

val evenDoubled = nums.Collect({ case n if n % 2 == 0 => n * 2 })

Other collection operations:

val sum = nums.FoldLeft(0, (acc, x) => acc + x)
val sorted = nums.SortWith((a, b) => a > b)
val csv = nums.MkString(", ")

String Interpolation vs fmt.Sprintf

GALA has built-in string interpolation with s"..." for auto-inferred format verbs and f"..." for explicit format specs. No imports needed.

GALA

val name = "Alice"
val age = 30
Println(s"$name is $age years old")
Println(f"Pi = ${3.14159}%.2f")

Go

name := "Alice"
age := 30
fmt.Printf("%s is %d years old\n", name, age)
fmt.Printf("Pi = %.2f\n", 3.14159)

GALA also provides Println and Print as built-in functions – no fmt import required.


JSON Serialization: Codec vs Struct Tags

GALA’s Codec[T] uses the compiler-generated StructMeta[T] intrinsic for fully typed JSON serialization — no reflection, no struct tags, and pattern matching support out of the box.

GALA

struct Person(
    FirstName string,
    LastName string,
    Age int,
)

val codec = Codec[Person](SnakeCase())
val jsonStr = codec.Encode(person).Get()
// {"first_name":"Alice",...}

val decoded = codec.Decode(jsonStr)
// Try[Person] — fully typed

// Builder: rename, omit
val custom = codec
    .Rename("FirstName", "given_name")
    .Omit("Age")

Go

type Person struct {
    FirstName string `json:"first_name"`
    LastName  string `json:"last_name"`
    Age       int    `json:"age"`
}

data, err := json.Marshal(person)
if err != nil {
    // handle error
}

var decoded Person
err = json.Unmarshal(data, &decoded)
if err != nil {
    // handle error
}

// Rename or omit? New struct +
// custom MarshalJSON method

GALA’s codec is resolved entirely at compile time — zero reflection overhead at runtime. The builder pattern lets you rename or omit fields without defining a new struct or writing custom marshal methods. You can even pattern match on JSON strings directly using codec(p) as an extractor.


Default Parameter Values

Go has no default parameters. The common workaround is the “functional options” pattern, which requires a struct, variadic functions, and option types. GALA adds defaults directly to the function signature.

GALA

func connect(
    host string,
    port int = 8080,
    tls bool = true,
) Connection {
    // ...
}

connect("localhost")
connect("db", tls = false)

Go

type ConnectOption func(*connectOpts)
type connectOpts struct {
    port int; tls bool
}
func WithPort(p int) ConnectOption { ... }
func WithTLS(t bool) ConnectOption { ... }

func Connect(host string,
    opts ...ConnectOption,
) Connection { ... }

Connect("localhost")
Connect("db", WithTLS(false))

Named arguments let callers skip parameters with defaults. The compiler validates default types at compile time and gives clear errors for mismatches.


When to Choose GALA

GALA is a strong fit when you want:

When to Stick with Go

Go is the better choice when you need:

Both are valid choices. GALA and Go produce the same binaries and use the same runtime – the difference is in the source language you write.

Interoperability

GALA uses Go libraries directly. There are no wrappers, no bindings, and no FFI layer. If a Go package exists, you can import it and call it from GALA:

import "strings"
import "os"

val upper = strings.ToUpper("hello")
val dir = Try(os.TempDir)

Go types, interfaces, and functions are all available. GALA adds its own type system on top – sealed types, Option[T], immutable structs – but the underlying Go interop is seamless.


Convinced? Run it in your browser. No install needed.

▶ Open the Playground Star on GitHub

Dive Deeper