· 6 min read ·

Functional Types, Go Binaries, and What Sky Is Actually Trying to Solve

Source: lobsters

The premise fits in a subtitle: Elm-inspired syntax, Hindley-Milner types, server-driven UI, and a single Go binary as output. The sky language is an early-stage project, but the combination of choices it makes is worth thinking through carefully, because each one has a non-obvious rationale that the others reinforce.

What Elm Got Right, and Where It Stops

Elm is one of the cleaner stories in language design over the past decade. It took a strict subset of Haskell’s type system, constrained it aggressively, and produced something with remarkably good ergonomics for browser UI. The Elm Architecture (TEA), built around Model, Msg, update, and view, gave developers a single mental model for state management that React and Redux effectively ported to JavaScript several years later.

The key piece Elm contributed beyond syntax was its Hindley-Milner type system. HM, descended from Robin Milner and Luis Damas’s 1982 formalization of Algorithm W, is a system for complete type inference over a polymorphic lambda calculus. You write almost no type annotations, and the compiler deduces the types of every expression. Unlike TypeScript’s type inference, which is bidirectional but incomplete and depends heavily on annotations at boundaries, HM inference in the ML tradition is principled: it either infers a type or tells you there is not one. Elm inherits this from its Haskell lineage and adds the practical constraint that there are no type classes, a deliberate simplification that trades expressiveness for error messages that are actually legible to someone new to the language.

Where Elm stops is the browser. The language compiles to JavaScript, and its entire architectural model assumes that the client is the application. The server is an API endpoint. This is a fine model for many things, but it creates a deployment surface that involves JavaScript tooling, bundlers, asset pipelines, and a client runtime you do not fully control. Writing Elm means accepting the JavaScript ecosystem as your delivery mechanism, even if you never write a line of JavaScript yourself.

Go as a Compilation Target

Go is not an obvious choice for a functional language compiler target. It has no algebraic data types in the sum type sense, no native pattern matching, limited type inference, and an object system built around structural interfaces rather than discriminated unions. Everything that makes Elm’s type system pleasant is absent from Go’s design.

What Go does have is a deployment story that few runtimes match. A go build produces a statically linked binary with no runtime dependencies. It runs on every major platform. Deployment is: copy the file. There is no JVM to version, no Node.js to install, no Python environment to activate. For a language that targets server-side web applications, this matters enormously in practice.

Languages that compile to other languages have a long history of making pragmatic choices about their targets. CoffeeScript targeted JavaScript because that was the only runtime available in the browser. TypeScript targeted JavaScript for the same reason, layering type information that the runtime cannot see. Haskell’s GHC historically targeted C for portability before developing its own native code generator. Kotlin targets the JVM to inherit its ecosystem and tooling. Each of these choices trades something for something else.

Sky’s bet is that Go’s deployment simplicity is worth accepting Go’s type system limitations at the IR level. The sky compiler translates functional constructs, algebraic types, and HM-inferred type information into Go code that the Go compiler then optimizes and packages. The resulting binary carries the Go runtime, giving you goroutines and a reasonably efficient garbage collector, without requiring the developer to think about either.

A useful comparison is Scala on the JVM: you get a type system several orders of magnitude more expressive than Java’s, plus the entire Java ecosystem, in exchange for the JVM’s startup overhead and the need to manage a JVM installation. Sky’s trade-off is narrower. You get Go’s binary format and nothing else from Go’s ecosystem, but that binary format is genuinely one of Go’s strongest properties.

Server-Driven UI and the Return of the Server

The architectural choice that distinguishes sky from simply being a typed functional language that happens to target Go is the server-driven UI model. Instead of generating JavaScript that maintains application state in the browser, sky pushes state and view rendering to the server, sending the client only what it needs to update the display.

This pattern has been building momentum under different names for several years. Phoenix LiveView (Elixir) maintains a persistent WebSocket connection between the browser and server, re-rendering components server-side on state changes and sending minimal diffs to the client. Hotwire and Turbo (the Rails ecosystem) replace full-page reloads with partial HTML updates over HTTP or WebSocket. HTMX takes this to its logical extreme: any element can trigger a server request and replace itself with the response, with no JavaScript application layer required.

The common thread in all of these is that complex client-side state management is often the wrong abstraction for a large class of web applications. If the server already owns the authoritative state, making the client responsible for re-implementing and synchronizing a copy of that state creates duplication, bugs at the boundary, and coordination overhead that grows with application complexity. Server-driven UI eliminates the synchronization problem by keeping state on the server and treating the client as a rendering surface.

Sky applies the Elm Architecture to this model. The TEA cycle of Model -> update -> view maps cleanly onto a server loop: receive a message (a user interaction event), run the update function (server-side state transition), produce a new view (an HTML or DOM patch). The client sends typed events; the server responds with view updates. Elm invented this data flow for the browser; sky relocates it to the server without changing the programming model.

This is not old-school server-side rendering where every interaction triggers a full page reload. Modern server-driven UI frameworks send incremental patches over a persistent connection, achieving the interactivity of a single-page application with the state management simplicity of a server-side framework. Sky’s claim is that a typed functional language with HM inference is a better fit for this model than the dynamically typed or partially typed languages that LiveView and HTMX typically run against.

The Type System Across the Network Boundary

Bringing HM inference to a server-driven UI framework has implications beyond syntax ergonomics. Type-safe message passing between client and server is one of the harder problems in full-stack web development. TypeScript on both ends, through tools like tRPC or Next.js server actions, is the current mainstream approach, but it requires either code generation or careful manual discipline to keep types synchronized across the client-server boundary.

In sky’s model, there is no boundary in that sense: the application is a single typed program. Messages from the client are typed. The update function’s return type is checked. The view function’s output is constrained. This is the same guarantee Elm gives you within the browser, extended to cover the entire interaction cycle.

HM inference means the compiler catches mismatches without requiring the developer to annotate every function signature. In a language without complete inference, type errors at the client-server boundary tend to surface at runtime, often in production. With HM, they surface at compile time, in a single pass over the whole program. This is not a marginal ergonomic improvement; it changes the category of bugs that can reach production.

Where the Project Stands

Sky is early. The repository has the shape of an exploration rather than a production tool: limited documentation, a small example set, and implementation details that are still settling. There are open questions the current code probably cannot answer: how does it handle database access, authentication, asset bundling, and incremental compilation of large programs? How does the Go output look in practice, and what are the performance characteristics at scale?

These are fair questions for a project at this stage. The more interesting observation is that the design space sky is exploring is real and underserved. The combination of Elm’s type safety and architectural discipline with Go’s deployment model and a server-driven UI approach is a coherent thesis. Each choice addresses a specific, well-documented pain point: HM types eliminate a class of runtime errors; Go’s build system eliminates a class of deployment problems; server-driven UI eliminates a class of client-server synchronization problems.

The prior art shows that each component of this idea has been validated separately. LiveView demonstrates that the server-driven UI model is practical for complex interactive applications. Elm demonstrates that HM types are viable for UI programming. Go demonstrates that single-binary deployment is valuable enough that developers accept significant language constraints to get it.

What remains to be shown is whether these three ideas compose cleanly in a single language with developer ergonomics good enough to attract real use. Sky is one attempt at that composition. It may not be the last.

Was this interesting?