· 6 min read ·

Elm Ideas for the Server Side: What Sky's Compile-to-Go Bet Is Really About

Source: lobsters

Sky is a small experimental language by Andrea Anzellai that sits at the intersection of three independently compelling ideas: the Elm Architecture as a design foundation, Go as a compilation target, and server-driven UI as the intended use case. Each of those choices is interesting on its own. Together they describe a design space that the mainstream ecosystem has been circling without quite committing to.

The project is clearly experimental, and nobody should mistake it for production infrastructure. But the combination of choices it makes is coherent in a way that rewards closer inspection.

The Elm Architecture and What It Enforces

The Elm Architecture is the thing Elm is most responsible for popularizing, more than any particular language feature. Redux, Vuex, the Flux pattern, and much of what passed for “state management” in the mid-2010s descended from TEA. The architecture is simple enough to state in a paragraph: every application has a Model representing its state, an update function that takes a message and a model and returns a new model, and a view function that turns a model into a description of the UI. The runtime handles the loop. Nothing is mutable in place.

type Msg = Increment | Decrement

type alias Model = { count : Int }

update : Msg -> Model -> Model
update msg model =
    case msg of
        Increment -> { model | count = model.count + 1 }
        Decrement -> { model | count = model.count - 1 }

What makes this enforcing rather than just prescriptive is Elm’s type system. Because the language has Hindley-Milner type inference, every Msg variant must be handled in update, every Model field must be a known type, and the view function must return the correct structure. The compiler rejects programs that violate these constraints with error messages that are more readable than most documentation.

Hindley-Milner inference means you rarely write type annotations in practice. The compiler infers the most general type of every expression through a process called unification: it assigns type variables to unknowns, generates constraints from how those unknowns are used, and solves the constraint system. The result is that identity x = x is inferred as a -> a for any type a, and a function that maps over a list is inferred as (a -> b) -> List a -> List b without a single annotation. Languages that use HM inference include Standard ML, OCaml, Haskell, and Elm itself. The practical effect is zero annotation burden in most code combined with exhaustive type checking.

Sky brings this inference approach to a language targeting Go, which means the type safety lives in the source language and the Go compiler provides a second layer of checking on the generated output.

Go as a Compilation Target

Most experimental functional languages target JavaScript, LLVM IR, or the JVM. Targeting Go is unusual, and the trade-offs are worth spelling out.

On the benefit side: Go compiles extremely quickly, so the full pipeline from sky source to binary remains fast. Go’s standard library covers HTTP, JSON, crypto, and concurrency without external dependencies. Go’s cross-compilation is first-class, requiring only environment variables rather than a separate toolchain. And, most significantly, Go’s linker produces statically linked binaries by default.

GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o myapp ./cmd/myapp
ldd myapp  # → not a dynamic executable

A pure-Go program has no runtime dependencies. The garbage collector, scheduler, and standard library are compiled into the binary. A typical Go web service is 10 to 30 megabytes. You copy it to a machine and run it. There is no JVM to install, no Python environment to configure, no node_modules directory. For container deployments this means images can start from scratch, resulting in images that are essentially the size of the binary itself.

Any language that compiles to Go inherits this property for free. The sky developer does not need to implement a linker, design a runtime distribution format, or worry about dependency isolation on the target machine. That is a substantial amount of work that disappears because of the compilation target choice.

The costs are real too. Go does not optimize tail calls, which is a genuine problem for functional languages that lean on recursion. A naive translation of recursive functions in a language that assumes TCO will produce Go code that stack overflows on large inputs. The idioms that Go code uses, mutable variables and for loops, are also the idioms that generate efficient Go, and a functional language generator must work harder to avoid either producing unreadable generated code or fighting the underlying model. Go did not gain generics until version 1.18 in 2022, and generating idiomatic polymorphic code before that required either interface{} casting or code duplication.

The only other serious attempt at a general-purpose functional language compiling to Go was Oden, by Oskar Wickström, which also drew on Haskell-style type inference and targeted Go 1.x. Oden was abandoned around 2017, partly because the impedance mismatch between functional idioms and Go’s imperative substrate was harder to bridge than anticipated. Sky takes a more constrained scope, which may be why it avoids some of those problems.

Server-Driven UI: When the View Lives on the Server

The SDUI angle is what distinguishes sky from just being a functional transpiler to Go. In standard Elm, the view function produces HTML that the Elm runtime renders in the browser’s DOM. In sky, the view function produces a UI description that the server serializes and sends to clients. The server decides what to render; the client has a pre-registered component library and renders whatever the server describes.

This pattern has been deployed at scale. Airbnb’s Ghost Platform uses server-driven UI across their iOS and Android apps, with the server returning typed component trees that the client renders. Spotify uses a similar approach with their Encore design system. The key benefit in both cases is that UI changes can be deployed without a new app release, which matters enormously when app store review cycles run several days. Feature flags and A/B tests become server configuration rather than app builds.

React Server Components are philosophically adjacent: the server renders component trees and streams them to the client, reducing the logic that lives in client-side JavaScript. HTMX goes further in the hypermedia direction, having the server return HTML fragments directly.

The sky approach differs from all of these in that the UI description emerges from a typed functional language rather than from JSX, template strings, or JSON schema. The type system ensures the server cannot emit a component with the wrong props. The Elm Architecture ensures the state transitions that drive UI updates are pure functions with no side effects. The compile-to-Go step means the server component of this system is a static binary with no runtime dependencies.

That combination is not something any of the mainstream SDUI frameworks offer.

The Broader Context

Elm’s own compiler has not had a major release since 0.19.1 in 2019. The language is remarkably stable, which its advocates consider a feature and its critics consider stagnation. The ecosystem has grown slowly. The lack of a typeclass mechanism, a deliberate design choice by Evan Czaplicki, means certain abstractions that Haskell programmers reach for are simply unavailable, which limits how far libraries can generalize. Elm remains excellent for what it targets, browser applications with strong reliability guarantees, but the design decisions that make it excellent there are also the constraints that make it difficult to extend.

Sky is not trying to extend Elm. It is borrowing Elm’s architectural ideas and applying them to a different target: Go-compiled servers serving UI descriptions. The project is small and experimental, and the question of whether it can handle the gap between functional idioms and Go’s generated code is open. But the design is not arbitrary. Each choice, HM inference for safety, TEA for state management discipline, Go for deployment simplicity, SDUI for decoupling server logic from client rendering, fits the others.

The interesting thing about small experimental language projects is not whether they ship to production. It is whether the combination of ideas they explore turns out to be something larger projects later converge on independently. The Elm Architecture already did that once. The compile-to-functional-server idea is still waiting for its moment.

Was this interesting?