Skip to main content
Elara features a strong, static type system based on Hindley-Milner type inference. Types are checked at compile time, ensuring type safety without requiring explicit type annotations everywhere.

Type annotations

While Elara can infer most types, you can (and should) provide explicit type signatures for top-level declarations:
def add : Int -> Int -> Int
let add x y = x + y

def greeting : String
let greeting = "Hello"
Type signatures use def followed by the name, a colon, and the type. The implementation uses let.

Primitive types

Elara provides several built-in primitive types:

Int

Integer numbers:
def age : Int
let age = 25

def negative : Int
let negative = -42

Float

Floating-point numbers:
def pi : Float
let pi = 3.14159

String

Text values:
def name : String
let name = "Alice"

Char

Single characters:
def letter : Char
let letter = 'a'

Bool

Boolean values:
def isValid : Bool
let isValid = True

Unit

The unit type () represents no meaningful value:
def doNothing : ()
let doNothing = ()

Function types

Functions are first-class values with types written using the arrow -> operator:
-- Function taking one Int and returning an Int
def increment : Int -> Int
let increment n = n + 1

-- Function taking two arguments
def add : Int -> Int -> Int
let add x y = x + y

-- Higher-order function
def apply : (a -> b) -> a -> b
let apply f x = f x
All functions in Elara are curried. Int -> Int -> Int is actually Int -> (Int -> Int), meaning you can partially apply functions.

Compound types

List

Lists are homogeneous collections of values:
def numbers : List Int
let numbers = [1, 2, 3, 4, 5]

def names : List String
let names = ["Alice", "Bob", "Charlie"]

def emptyList : List a
let emptyList = []

Tuple

Tuples contain a fixed number of heterogeneous values:
def pair : (Int, String)
let pair = (42, "answer")

def triple : (Int, Bool, String)
let triple = (1, True, "test")

def nested : ((Int, Int), String)
let nested = ((1, 2), "coordinates")

Record

Records are first-class types with named fields:
def person : { name: String, age: Int }
let person = { name = "Alice", age = 30 }

def point : { x: Int, y: Int }
let point = { x = 10, y = 20 }

Type variables

Type variables (also called generic types) are lowercase identifiers that represent any type:
-- Identity function works for any type
def id : a -> a
let id x = x

-- The type of the first element in a tuple
def fst : (a, b) -> a
let fst pair = match pair with
    (x, y) -> x

Polymorphism

Elara supports parametric polymorphism through type variables:
def map : (a -> b) -> List a -> List b
let map f list =
    match list with
        [] -> []
        x :: xs -> f x :: map f xs

def length : List a -> Int
let length list =
    match list with
        [] -> 0
        x :: xs -> 1 + length xs
The type variable a can be instantiated to any concrete type like Int, String, or even another polymorphic type.

Type inference

Elara can infer types automatically in most cases:
-- Type is inferred as Int -> Int
let double x = x * 2

-- Type is inferred as List Int
let numbers = [1, 2, 3]

-- Type is inferred as (a -> b) -> a -> b
let apply f x = f x
While type inference is powerful, it’s best practice to provide explicit type signatures for top-level functions to improve code clarity and error messages.

The forall quantifier

Polymorphic types use universal quantification, which can be written explicitly:
-- Explicit forall (optional)
def id : forall a. a -> a
let id x = x

-- Implicit forall (preferred)
def id : a -> a
let id x = x

Type aliases

Create synonyms for existing types:
type alias Name = String

type alias Age = Int

type alias Person = { name: Name, age: Age }
From the real examples:
type Number = Int

def assertNumber : Int -> Number
let assertNumber n = n

-- Type aliases with parameters
type Identity s = s

def makeIdentity : a -> Identity a
let makeIdentity x = x

IO type

The IO type represents computations with side effects:
def main : IO ()
let main = println "Hello, World!"

def readAndPrint : IO ()
let readAndPrint = 
    getArgs >>= (\args -> println (toString args))
Values of type IO a represent actions that, when executed, will produce a value of type a and may perform I/O operations.

Build docs developers (and LLMs) love