Types

Status: the Rust compiler (rust/, cargo build --release -p sky) is the primary Sky compiler; the Haskell compiler is preserved under legacy-haskell-compiler/. Verified by the example sweep + compiler test suite (cargo test + xtask gates). See ../compiler/versions.md for the changelog.

Sky's type system is Hindley-Milner with algebraic data types, records, and concrete Go interop types. There are no type classes, no higher-kinded types, no row polymorphism.

Primitives

SkyGo
Intint
Floatfloat64
Stringstring
Boolbool
Charrune (int32)
Bytes[]byte

Type aliases

type alias Point =
    { x : Int
    , y : Int
    }

type alias UserId = String
type alias Tags = List String

Every record type alias auto-generates a positional constructor:

origin : Point
origin =
    Point 0 0     -- constructor args in field-declaration order

Algebraic data types

type Shape
    = Circle Float
    | Rect Float Float
    | Polygon (List Point)


area : Shape -> Float
area shape =
    case shape of
        Circle r ->
            3.14159 * r * r

        Rect w h ->
            w * h

        Polygon points ->
            -- exhaustiveness-checked at compile time
            polygonArea points

Pattern matches are exhaustive — missing variants are build errors.

Tuples

Fixed-arity product types. Arity 2 emits rt.SkyTuple2 ({V0, V1}); arity 3 emits rt.SkyTuple3 ({V0, V1, V2}); arity 4+ falls back to the slice-backed rt.SkyTupleN. The mapping is performed in Sky.Generate.Go.Type.typeToGo and is invisible at the source level — destructuring patterns work the same way at every arity.

pair : ( Int, String )
pair =
    ( 42, "answer" )

Lists & dicts

numbers : List Int
numbers =
    [ 1, 2, 3 ]

usersByEmail : Dict String User
usersByEmail =
    Dict.empty
        |> Dict.insert "alice@example.com" alice

Dict is map[string]any at runtime. Non-String keys are stringified. Arithmetic on Dict Int v keys returned by Dict.toList silently produces strings — iterate via Dict.get over known key ranges instead.

Maybe & Result

Maybe a
    = Just a
    | Nothing

Result e a
    = Ok a
    | Err e

Use Maybe for optional values, Result for fallible pure computations. Both are generic in their payload type.

Since v0.9, every public fallible surface uses Result Error a (not Result String a). See ../errors/error-system.md.

Task

Task e a is the Sky effect type. Every effectful operation — file I/O, HTTP, DB, println — returns Task Error a. Run one with Task.perform.

readConfig : Task Error String
readConfig =
    File.readFile "./config.json"
        |> Task.onError (\_ -> Task.succeed "{}")

Type annotations

Annotations are load-bearing:

Generics

Polymorphic HM-inferred functions lower to Go generics:

identity : a -> a
identity x = x
func Identity[T1 any](x T1) T1 { return x }

solvedTypeToGo TVar falls back to any at expression positions (Go's type parameters can't appear outside enclosing function signatures). This is by design, not an escape hatch.

Parametric record aliases (v0.15+) lower to Go-generic structs with one type parameter per HM type variable:

type alias Cfg msg =
    { onSubmit : msg
    , label : String
    }
type Cfg_R[T1 any] struct {
    OnSubmit T1
    Label    string
}

Per-instance construction takes the type args: Cfg_R[Msg], Cfg_R[Int]. Callback fields keep their typed callee parameter — no func(any) any fallback at parametric-record slots. The same-module polymorphic re-instantiation rule lets f : Cfg msg -> msg be called with msg=Int AND msg=Bool in the same module without pinning.

Type variables with constraints

Intentionally unsupported. Sky's HM is unconstrained; typeclass-style operations are provided implicitly via runtime helpers:

There are no Eq / Ord / Show constraints to opt into — every operator just works on every type.