Write FP. Ship Go.

Sky is a pure functional ML-family language that compiles to Go.
Hindley-Milner types, algebraic data types, fully-typed Go codegen — one binary, three UI backends.

curl -fsSL https://raw.githubusercontent.com/anzellai/sky/main/install.sh | sh
Get Started View on GitHub
Main.sky
module Main exposing (main)

import Std.Ui as Ui exposing (Element)
import Std.Ui.Font as Font
import Std.Live exposing (app, route)

type alias Model = { count : Int }
type Msg = Increment | Decrement | Reset

init _ = ({ count = 0 }, Cmd.none)

update msg model =
    case msg of
        Increment -> ({ model | count = model.count + 1 }, Cmd.none)
        Decrement -> ({ model | count = model.count - 1 }, Cmd.none)
        Reset     -> ({ model | count = 0 }, Cmd.none)

view : Model -> Element Msg
view model =
    Ui.column [ Ui.spacing 16, Ui.padding 24 ]
        [ Ui.el [ Font.size 48, Font.bold ]
            (Ui.text (String.fromInt model.count))
        , Ui.row [ Ui.spacing 8 ]
            [ Ui.button [] { onPress = Just Decrement, label = Ui.text "-" }
            , Ui.button [] { onPress = Just Reset,     label = Ui.text "reset" }
            , Ui.button [] { onPress = Just Increment, label = Ui.text "+" }
            ]
        ]

main = app { init = init, update = update, view = view
           , subscriptions = \_ -> Sub.none
           , routes = [ route "/" () ], notFound = () }

One Model. Three backends.

The same init / update / view runs on three runtimes — pick at the entry point, no view-code changes:

  • Sky.Live — server-driven web UI with DOM diffing over SSE, persistent sessions, async Cmd.perform
  • Sky.Tui — same Element tree rendered to ANSI cells in a terminal (logical-pixel canvas, mouse + scroll)
  • Sky.Cli — line-oriented stdin event loop for non-interactive shells

Same view, three runtimes

Std.Ui is a typed, no-CSS layout DSL. Swap one import to retarget — web, terminal, CLI — with zero changes to view, update, or model.

Sky.Live

web
import Std.Live exposing (app)

main = app
    { init = init, update = update
    , view = view, subscriptions = subs
    , routes = [ route "/" () ]
    , notFound = () }

HTTP + SSE wire. Persistent sessions across deploys (memory / SQLite / Redis / Postgres / Firestore). Async Cmd.perform goroutines. Input-authority protocol preserves typed input across re-renders.

Sky.Tui

terminal
import Std.Tui as Tui

main = Tui.app
    { init = init, update = update
    , view = view, subscriptions = subs
    , onKey = KeyPressed
    } |> Task.run

Render Element trees to ANSI cells. Logical-pixel canvas (1280×720 default), mouse left-press + scroll wheel, wide chars + emoji via grapheme-cluster width. Diff renderer; no flicker.

Sky.Cli

stdin
import Std.Cli as Cli

main = Cli.program
    { init = init, update = update
    , view = view, subscriptions = subs
    , onLine = LineRead
    } |> Task.run

Line-oriented event loop for non-TTY shells. Same TEA shape; view returns a string each turn. Echo-suppressed password reads via Cli.readPassword for auth flows.

Why Sky

If it compiles, it works

Hindley-Milner type inference, exhaustive pattern matching, no null, no exceptions. Undefined names caught at compile time with line:col positions.

Single binary

Compiles to a native Go binary. Your fullstack app — API, database, server-rendered UI — ships as one file. The compiler itself is also a single binary (Haskell).

Multi-backend UI

Std.Ui is a typed no-CSS layout DSL. The same view renders to a web app (Sky.Live), a terminal UI (Sky.Tui), or a stdin loop (Sky.Cli). One model, three runtimes.

Go ecosystem

Import any Go package with auto-generated type-safe bindings. Stripe, Firebase, SQLite, PostgreSQL — all with panic recovery and nil safety.

Fully-typed Go output

v0.13 codegen emits typed Go for every reachable Sky symbol — no interface{} for user functions, vars, lambdas, or ADTs. Stripe-scale FFI surfaces tree-shaken whole-program (76k symbols → only what you reach).

Batteries included

Built-in database (Std.Db), authentication (Std.Auth), JSON encode/decode with pipeline, HTTP server, formatter, LSP with hover + diagnostics.

See it in action

Std.Ui forms

-- Typed onSubmit decodes formData
-- directly into a record arg
Ui.form [ Ui.onSubmit DoSignIn ]
    [ Input.email [] cfg.email
    , Input.currentPassword [] cfg.pwd
    , Ui.button [] btn
    ]

Pattern matching

describe shape =
    case shape of
        Circle r ->
            "circle r=" ++ String.fromFloat r
        Rectangle w h ->
            "rect"

Database

import Std.Db as Db

-- reads sky.toml [database]
todos = Db.connect ()
    |> Task.andThenResult (\db ->
        Db.queryDecode db
            "SELECT * FROM todos"
            [] todoDecoder)

Async commands

-- Run tasks in background goroutines
update msg model =
    case msg of
        FetchData ->
            ( model, Cmd.perform
                (Http.get "/api") GotData )

Concurrency

-- Parallel HTTP requests (goroutines)
fetch =
    Task.parallel
        [ Http.get url1
        , Http.get url2
        ]
        |> Task.andThen handleAll

Go interop

import Stripe

-- whole-program DCE strips
-- unused symbols (76k → only used)
charge = Stripe.newCharge ()
    |> Result.andThen ...
24 Examples
267 Compiler tests
3 UI backends
Haskell Compiler (GHC 9.4+)

Built with Sky

Standalone tools, libraries, and full applications.

Ready to try Sky?

Install in seconds. Create your first project. Ship a binary.

curl -fsSL https://raw.githubusercontent.com/anzellai/sky/main/install.sh | sh
sky init my-app
cd my-app
sky run
Get Started