· 6 min read ·

Go Is the Last Major Runtime Without a Functional Language Targeting It, and Sky Wants to Fix That

Source: lobsters

The compile-to-X pattern has been one of the most productive ideas in language design over the past decade. Elm compiles to JavaScript and brings algebraic data types, Hindley-Milner inference, and The Elm Architecture to the browser. ReScript and PureScript do similar work in the same space. Gleam brings the same ideas to the BEAM. F# brings them to .NET. The pattern is consistent: take a runtime with a large ecosystem, operational maturity, and deployment ubiquity, then give developers a better language to target it.

Go has sat largely outside this conversation. That is surprising given how prominent Go has become as the infrastructure language of choice — Kubernetes, Docker, Terraform, Prometheus, and much of the cloud-native ecosystem are written in it. A new experimental project called Sky is reaching for that gap directly: an Elm-inspired language with Hindley-Milner types, server-driven UI semantics, and single binary output, all compiled to Go.

What Hindley-Milner Actually Gives You

Go’s type system is explicit by design. You declare types. The compiler checks them. Inference exists in limited form — := infers a variable’s type from the right-hand side — but nothing resembling full type inference. Generic functions added in Go 1.18 help, but the fundamental model remains annotation-heavy.

Hindley-Milner inference, by contrast, is complete. The algorithm — formalized by Roger Hindley in 1969 and later by Robin Milner in 1978 — works by assigning type variables to all expressions, generating equality constraints based on how those expressions are used, then solving the constraints via unification. Every expression gets its principal type: the most general type that is correct. The key property is completeness. If a program is typeable at all, the algorithm finds the most general type without requiring any annotations.

In practice this means you can write code like the following in an HM language without a single type annotation, and the type checker will verify the full program:

type Result err ok = Ok ok | Err err

parse : String -> Result ParseError Int
parse input =
  case String.toInt input of
    Just n  -> Ok n
    Nothing -> Err (InvalidInput input)

The type of parse is inferred. The exhaustiveness of the case is checked. The ParseError variant structure is verified. None of this requires runtime reflection or panics — it is resolved entirely at compile time. Go has none of this. Error handling is a convention, not a type-level guarantee. Exhaustive switches exist only via iota + linter tools. Sum types require interface{} workarounds or code generation.

This is the core value proposition of languages like Sky. You get the Go runtime — its concurrency model, its binary output, its ecosystem — but you write in a language where entire classes of bugs are structurally impossible.

The Elm Architecture on the Server

Elm introduced The Elm Architecture (TEA) as a pattern for managing application state: a Model describing all state, an Update function of type Msg -> Model -> (Model, Cmd Msg), and a View function of type Model -> Html Msg. Every state change flows through Update; the view is a pure function of state. This pattern directly inspired Redux and later Vuex, NgRx, and virtually every major frontend state management library.

The interesting move Sky makes is applying this pattern server-side. In this framing, the server owns the Model. When state changes, the server computes the new view and pushes it to clients. Clients are thin renderers rather than stateful applications.

This is not a new idea, but it is having a significant moment. Phoenix LiveView does exactly this in Elixir: the server holds state, handles events, and pushes diffs over WebSocket. HTMX returns HTML fragments from the server rather than JSON, treating the server as the canonical source of UI truth. React Server Components render component trees on the server and send serialized output to the client. Airbnb, Shopify, and others have published extensively on their server-driven UI systems for mobile, where the server sends layout descriptors that native clients render.

Sky’s bet is that the Elm Architecture is the right conceptual model for this server-driven approach, and that Go’s concurrency primitives (goroutines, channels, a cooperative scheduler that handles millions of connections) make it a better server runtime for this workload than the BEAM or Node.js in many deployment contexts.

Go as a Compilation Target

The case for targeting Go is straightforward and concrete. go build produces a single static binary. No JVM installation, no node_modules, no Python interpreter. Cross-compilation is built into the toolchain: GOOS=linux GOARCH=amd64 go build works from any platform. A typical Go HTTP server compiles to 8-12 MB. The Docker story becomes FROM scratch plus the binary.

Beyond deployment, Go’s performance characteristics are well-understood. Garbage collector pause times have been sub-millisecond since Go 1.14. Goroutines start at a 2KB stack versus 1MB for OS threads, enabling high concurrency at low cost. The standard library covers HTTP, TLS, JSON, and database access without external dependencies. Compile times are fast enough to treat as part of a development loop rather than a build pipeline.

Generated code benefits from gofmt, which means Sky’s compiler output is always idiomatic and readable. This matters more than it might seem — when a language compiles to another language, the quality and legibility of the generated output determines how debuggable production issues will be.

The Historical Precedent

Sky is not the first attempt at this specific niche. Oden, created by Oskar Wickström around 2016-2017, was an ML-influenced language targeting Go. It had similar goals: bring Hindley-Milner inference and functional programming ergonomics to Go’s runtime. Oden was eventually abandoned; Wickström cited design difficulties and lack of community momentum.

The abandonment of Oden points at the real tension in this space. Go’s design philosophy is deliberate simplicity. The Go team has consistently chosen explicit code over clever inference, readable output over expressive syntax, and convention over abstraction. These are not bugs — they are the reasons Go succeeded where more ambitious languages failed to gain traction. Bringing an HM type system and algebraic data types to Go means writing a language that generates code the Go team would not have written.

This creates a practical problem: when things go wrong in production, developers need to read and reason about the generated Go. If the generated output is idiomatic, the abstraction is thin and the value proposition weakens. If the generated output is complex, the debugging story is difficult. Languages like Elm side-step this by targeting JavaScript, where developers already accept significant generated complexity. Go developers have higher expectations about what their generated code looks like.

What Sky Needs to Prove

The project is at an early, experimental stage — the GitHub repository reflects a proof-of-concept rather than a production-ready toolchain. That is fine; every serious language starts this way.

The questions that determine whether Sky finds an audience are concrete. Does the generated Go compile cleanly and pass go vet? How does the server-driven UI model integrate with existing Go HTTP frameworks like Chi or Gin? What does the interop story look like when you need to call existing Go libraries? How are goroutines and channels exposed — or hidden — from the Sky layer?

The type system question is similarly pointed. Elm deliberately excluded type classes to keep the language learnable. Sky’s relationship with type classes determines how much of the HM ecosystem (functor, monad, applicative abstractions) is available. If Sky includes type classes, it gains expressive power at the cost of Elm’s famously gentle learning curve. If it excludes them, it limits what functional patterns are expressible.

None of these questions are dealbreakers. They are the design space that the project needs to navigate. The niche is real: Go is genuinely underserved by expressive type systems, and server-driven UI is a growing architectural pattern with strong theoretical backing in The Elm Architecture. Whether Sky becomes the language that fills that niche depends on execution, documentation, and the willingness to make hard choices about scope.

The compile-to-X pattern has produced some of the most usable programming environments of the past decade. Go deserves a serious entrant in that space, and Sky is at least asking the right questions.

Was this interesting?