Esc
Start typing to search...

Error Handling

Keel provides comprehensive error messages with helpful suggestions to improve developer experience.

Fuzzy Matching ("Did you mean?")

When you reference an undefined symbol, Keel suggests similar names:

import IO exposing print

let userName = "Alice"

let userAge = 30

print usrName

-- Error: Variable 'usrName' not found. Did you mean 'userName' or 'userAge'?
Try it

Multiple suggestions when relevant:

import IO exposing print

let userName = "Alice"

let userAge = 30

let userEmail = "alice@example.com"

print usrNme

-- Error: Variable 'usrNme' not found. Did you mean 'userName', 'userAge', or 'userEmail'?
Try it

Suggestions work for:

  • Variables and bindings
  • Function names
  • Module names
  • Enum types and variants
  • Type aliases

Module Function Typos

Suggestions also work for module function names:

import List
List.fold [1, 2, 3] 0
-- Error: Function 'fold' is not declared in module 'List'. Did you mean 'foldl' or 'foldr'?
Try it

Parser Errors

Keel provides clear messages for syntax errors.

Indentation Errors

fn example : Int -> Int
fn example x =
let y = 1    -- Error: Function body must be indented by at least 4 spaces
    x + y
Try it

Block Alignment

if True then
    0
  else         -- Error: 'else' must align with 'if'
    1
Try it

Block Nesting

let x =
x + 1          -- Error: Block must be indented more than parent
               -- Hint: Indent the block to 4 spaces
Try it

Type Definition Errors

enum Direction = North | south    -- Error: Enum variant must start with uppercase
Try it

Record Syntax in Enum

Using a record literal body in an enum declaration instead of named variants:

enum Person = { name: String, age: Int }
-- Error: Expected enum variant, found record syntax
-- Hint: Use an inline record type annotation instead.
Try it

Enum Variant Parentheses

Forgetting parentheses in enum variant data:

enum Result a e = Ok a | Err e
-- Result OkType ErrType: first parameter is success, second is error
-- Error: Enum variant 'Ok' appears to have data without parentheses
-- Hint: Use `Ok(a)` instead of `Ok a`. Variant data requires parentheses.
Try it

Delimiter Balancing

Keel checks for mismatched, unclosed, and unexpected delimiters before parsing, producing precise error messages:

let x = (1 + 2
-- Error: Unclosed '(' — expected ')'
Try it
let x = [1, 2)
-- Error: Mismatched delimiter — '(' expected ')' but found ')'
Try it
let x = 1 + 2]
-- Error: Unexpected ']' — no matching '['
Try it

Tuple Pattern Type Annotations

Annotating a tuple pattern as a whole instead of its elements:

let addPair = |(x, y): (Int, Int)| x + y
-- Error: Type annotations on tuple patterns must be on individual elements
-- Hint: Use `|(x: Int, y: Int)|` instead of `|(x, y): (Int, Int)|`.
Try it

Block Missing Indentation

A function body or block expression must be indented by at least 4 spaces relative to its parent definition:

fn greet : String -> String
fn greet name =
"Hello, " ++ name   -- Error: body must be indented by at least 4 spaces
Try it

Function Missing Type Signature

Every fn-style function definition requires a matching type signature (fn name : Type -> Type) before the implementation. A definition without a preceding signature is rejected:

fn double x =        -- Error: missing type signature
    x * 2
Try it

Else Must Align With If

The else branch must start at the same column as the corresponding if. A misaligned else produces a parser error:

let x = 10
let result =
    if x > 5 then "big"
        else "small"   -- Error: else must align with if
Try it

Compiler Errors

Undeclared Symbols

let x = unknownVar    -- Error: Variable 'unknownVar' is not declared
Try it

Type Mismatches

fn add : Int -> Int -> Int
fn add x y =
    x + y

add "hello" 5  -- Error: Expected Int but got String
Try it

Generic Type Annotations Required

Functions that return a generic type (like Json.parse) require a type annotation on the let binding so the compiler knows the concrete type:

import Json

let data = Json.parse "{\"x\": 1}"

-- Error: Generic function result requires a type annotation for variable 'data'

-- Hint: Add a type annotation to specify the expected type
Try it

Fix by adding a type annotation:

import Json

let data : Result { x : Int } String = Json.parse "{\"x\": 1}"

data
Try it

Scope Violations

fn example : Int -> Int
fn example x =
    let inner = x + 1
    inner

inner  -- Error: Variable 'inner' is not in scope
Try it

Helpful Suggestions for Common Mistakes

Keel detects common syntax patterns from other languages and provides helpful suggestions.

JavaScript-Style Record Syntax

{ name: "Alice", age: 30 }
-- Error: JavaScript-style record syntax is not allowed
-- Hint: Use `=` instead of `:` for record field assignment
-- Example: `{ name = "Alice" }`
Try it

Correct syntax:

{ name = "Alice", age = 30 }
Try it

Boolean Literals from Other Languages

let x = true     -- Error: 'true' is not a Keel keyword
                 -- Hint: Use `True` for boolean true
Try it

Correct syntax:

let x = True

let y = False

x && y
Try it

Null/Nil/None from Other Languages

let x = null     -- Error: 'null' is not a Keel keyword
                 -- Hint: Use `Nothing` for absent values
Try it

Correct syntax:

let present = Just 42

let absent = Nothing

case present of
    Just n -> "Got a value"
    Nothing -> "Nothing"
Try it

Keywords from Other Languages

return 5         -- Hint: Keel is expression-based; the last expression is the return value
match x of       -- Hint: Use `case ... of` for pattern matching
function add     -- Hint: Use `fn` to define functions
var x = 5        -- Hint: Use `let` for variable bindings
Try it

Correct syntax examples:

fn double : Int -> Int
fn double x =
    x * 2

-- Pattern matching uses case...of

let value = Just 5

case value of
    Just n -> n * 2
    Nothing -> 0
Try it

Pattern Matching Errors

Type Mismatch in Patterns

case 42 of
    "hello" -> 1  -- Error: Pattern type mismatch: expected Int, but pattern is String
    _ -> 0
Try it

Wrong Constructor Type

case Just 5 of
    Ok n -> n  -- Error: Pattern type mismatch: expected Maybe, but pattern is Result (Ok)
    _ -> 0
Try it

Guard Type Errors

let x = 2

case x of
    n if n + 1 -> "bad"  -- Error: Guard expression must be Bool, found Int
    _ -> "ok"
Try it

Correct guard syntax:

let x = 5

case x of
    n if n > 0 -> "positive"
    n if n < 0 -> "negative"
    _ -> "zero"
Try it

Branch Type Mismatch

let x = 2

case x of
    1 -> 42  -- Int
    2 -> "hello"  -- Error: Case branches have incompatible types: Int and String
    _ -> 0
Try it

Correct (all branches return same type):

let x = 2

case x of
    1 -> "one"
    2 -> "two"
    _ -> "other"
Try it

Type Errors

Function Argument Type Mismatch

When you pass the wrong type to a function, Keel shows the expected and actual types along with the full function signature:

import List

List.map 42 [1, 2, 3]

-- Error: Type mismatch: expected (a -> b), found Int

-- Hint: Function signature: (a -> b) -> [a] -> [b]

-- Expected argument type: (a -> b), but got: Int
Try it
import String

String.length 42

-- Error: Type mismatch: expected String, found Int

-- Hint: Function signature: String -> Int

-- Expected argument type: String, but got: Int
Try it

The function signature in the hint helps you understand what the function expects, especially for higher-order functions with type variables.

Runtime Errors

Collection Bounds

let items = [1, 2, 3]

items[10]  -- Error: Index 10 out of bounds for list of size 3
Try it

Unsafe access (use List.nth for safe access):

let items = [1, 2, 3]

items[0]  -- 1
Try it

The ? Operator

The ? postfix operator unwraps a Result value or short-circuits when the value is Err.

  • If the expression is Ok v, ? evaluates to v (the unwrapped value).
  • If the expression is Err e, the overall result becomes Err e.
-- Unwrap Ok with ?, short-circuit on Err
let addOne = |x: Int| Ok (x + 1)
let result = 5 |> addOne
result?
Try it

When the value is Err, ? propagates the error:

-- When ? is applied to Err, the result is Err (short-circuit)
let mayFail = |x: Int| Err "something failed"
let result = 5 |> mayFail
result?
Try it

The ? operator can only be applied to Result values. Applying it to any other type (such as Int, String, or Maybe) is a compile error.

Composable Error Handling

Keel provides the Result and Maybe modules for functional, composable error handling without nested pattern matching.

Result Module

Transform and chain operations that might fail:

import Result

-- Transform success values
let mapped : Result Int String = Ok 5 |> Result.map (|x| x * 2)

-- Chain operations that might fail
let chained : Result Int String =
    Ok 5 |> Result.andThen (|x| if x > 0 then
        Ok x
    else
        Err "negative")

-- Provide default values
let defaulted = Err "failed" |> Result.withDefault 0

-- Transform error values
Err "bad" |> Result.mapError (|e| "Error: " ++ e)
Try it

Maybe Module

Transform and chain optional values:

import Maybe

-- Transform present values
let mapped = Just 5 |> Maybe.map (|x| x * 2)

-- Chain operations that might return Nothing
let chained =
    Just 5 |> Maybe.andThen (|x| if x > 0 then
        Just x
    else
        Nothing)

-- Provide default values

Nothing |> Maybe.withDefault 0
Try it

Converting Between Types

import Result

-- Maybe to Result (with error message)
let a = Just 5 |> Result.fromMaybe "was nothing"

-- Result to Maybe (discards error)
let c : Maybe Int = Ok 5 |> Result.toMaybe

let d : Result Int String = Err "x"

d |> Result.toMaybe
Try it

Best Practices

  1. Read error messages carefully — they often contain the exact fix
  2. Check the hints — suggestions point to Keel-specific syntax
  3. Use the playground — experiment with syntax in the browser
  4. Handle all cases — the compiler warns about incomplete pattern matches
  5. Use Result/Maybe modules — prefer map and andThen over nested pattern matching
  6. Understand scope — inner scopes can shadow outer variables; check for typos in variable names

Next Steps

Explore the Standard Library to see available modules and functions.