Esc
Start typing to search...

Lexical Structure

This document describes the lexical structure of Keel source code.

Character Set

Keel source files are UTF-8 encoded. Identifiers can contain Unicode letters.

Comments

Line Comments

-- This is a line comment
let x = 42  -- Inline comment
Try it

Block Comments

{- This is a
   block comment -}

{- Block comments
   {- can be nested -}
   like this -}

Doc Comments

Doc comments use {-| ... -} syntax. They are used by tooling (LSP hover, documentation generation) and are distinguished from regular block comments:

{-| Increments a number by one. -}
fn inc : Int -> Int
fn inc x = x + 1

{-| A multiline doc comment
    explaining complex behavior. -}
fn double : Int -> Int
fn double x = x * 2

Whitespace and Indentation

Keel uses indentation to define block structure. Indentation is significant for:

  • Function bodies
  • Case expression branches
  • Inline module content
fn example : Int -> Int
fn example x =
    let y = 1    -- Must be indented
    x + y
Try it

Identifiers

Value Identifiers

Start with lowercase letter or underscore:

name
_private
camelCase
snake_case
x1

Type Identifiers

Start with uppercase letter:

Int
String
Maybe
MyCustomType

Keywords

Reserved words that cannot be used as identifiers:

case    of      if      then      else
let     in      fn      type      mut
module  import  exposing as       alias
inline  passing
True    False

Note: mut is reserved for use in file inlining exposing clauses (to mark variables whose mutations propagate back to the caller). It cannot be used for mutable bindings — Keel is a pure functional language where all bindings are immutable.

Literals

Integer Literals

42          -- Decimal
-7          -- Negative
Try it

Float Literals

3.14
Try it

Decimal Literals

Decimal literals use the d suffix for arbitrary-precision decimal values:

42d         -- Integer decimal
3.14d       -- Fractional decimal
-0.001d     -- Negative decimal
Try it

Character Literals

'a'
'\n'        -- Newline
'\t'        -- Tab
'\\'        -- Backslash
'\''        -- Single quote
Try it

String Literals

"Hello, World!"
"Line 1\nLine 2"
"Tab\there"
"Quote: \"quoted\""
Try it

Boolean Literals

True
False
Try it

Unit Literal

Operators

Arithmetic Operators

OperatorDescription
+Addition
-Subtraction
*Multiplication
/Division
//Integer division
%Modulo
^Power

Comparison Operators

OperatorDescription
==Equal
!=Not equal
<Less than
<=Less than or equal
>Greater than
>=Greater than or equal

Logical Operators

OperatorDescription
&&Logical AND
||Logical OR
notLogical NOT

Function Operators

OperatorDescription
>>Compose (left to right)
<<Compose (right to left)
|>Pipe forward
<|Pipe backward

List Operators

OperatorDescription
::Cons (prepend)
++Concatenation

Delimiters

(  )    -- Grouping, tuples, unit
[  ]    -- Lists, index access
{  }    -- Records, blocks, block comments
,       -- Separator
:       -- Type annotation
->      -- Function type, case branches
=       -- Definition, record fields
|       -- Pattern alternatives, enum variants, lambda start

Lambda Syntax

Lambdas use pipe characters:

|x| x + 1
|x y| x + y
|x: Int| x + 1
|(x, y)| x + y

Pattern Matching Syntax

Case expressions with guards:

case value of
    pattern -> result
    n if n > 0 -> "positive"
    _ -> "default"

Record Syntax

Records use = for field assignment:

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

Record update uses |:

{ person | age = 31 }

Module Syntax

-- File-level module
module exposing (foo, bar)

-- Named inline module (content must be indented)
module Math exposing (add)
    fn add : Int -> Int -> Int
    fn add x y = x + y

-- Parameterized module (callee file for file inlining)
module (x : Int) exposing (result : Int)

File Inlining Syntax

File inlining is resolved at compile time. The inline expression returns a record of exposed values:

-- Inline a file, passing variables and destructuring the result
let { result } = inline "file.kl" passing (x, y)

-- Pass all variables
let { result } = inline "file.kl" passing (..)

-- No variables to pass (passing clause is optional)
let { answer } = inline "file.kl"

Type Definition Syntax

-- Type alias
type alias Name = String

-- Enum type (single line)
type Direction = North | South | East | West

-- Enum type (multi-line)
type Shape
    = Circle(Float)
    | Rectangle(Float, Float)

-- Enum with record variant
type User
    = Guest
    | Member { name: String, id: Int }

Enum Construction

Enum variants can be constructed with parenthesized or space-separated syntax (single argument only):

Shape::Circle(5.0)            -- parenthesized
Shape::Circle 5.0             -- space-separated (single arg)
Shape::Rectangle(10.0, 20.0)  -- multi-arg requires parentheses

Qualified Enum Syntax

Enum variants use :: for qualified access, both in expressions and patterns:

let color = Color::Green

case color of
    Color::Red   -> "fire"
    Color::Green -> "nature"
    Color::Blue  -> "sky"