Modules
Modules help organize code into logical units and control visibility.
File-Level Modules
A file-level module declaration must appear at the start of the file. The module name is derived from the filename:
module exposing (User, createUser, getName)
type alias User = { name: String, age: Int }
fn createUser : String -> Int -> User
fn createUser name age = { name = name, age = age }
fn getName : User -> String
fn getName user = user.name
Exposing
Control what the module exports:
-- norun
-- expect-error: [Runtime] Parse error: Function does not exist inside the module; Function does not exist inside the module; Type does not exist inside the module; File-level module declaration must appear at the start of the file; File-level module declaration must appear at the start of the file
-- Expose specific items
module exposing (functionA, functionB, TypeA)
-- Expose all
module exposing (..)
-- Expose nothing (private module)
module exposing ()
Named Inline Modules
Named inline modules must have their content indented:
module CustomMath exposing (add, multiply)
fn add: Int -> Int -> Int
fn add x y =
x + y
fn multiply: Int -> Int -> Int
fn multiply x y =
x * y
CustomMath.add 3 4
-- 7
Try itImportant: Non-indented content after an inline module declaration is an error:
-- Error: Inline module content must be indented
module CustomMath exposing (add)
fn add x y = x + y -- Wrong: not indented
Try itCorrect:
module CustomMath exposing (add)
fn add: Int -> Int -> Int
fn add x y =
x + y
CustomMath.add 5 3
-- 8
Try itRecursive Functions in Modules
Functions defined inside modules can call themselves recursively:
module CustomMath exposing (factorial, fibonacci)
fn factorial: Int -> Int
fn factorial n =
if n <= 1 then 1
else n * factorial (n - 1)
fn fibonacci: Int -> Int
fn fibonacci n =
if n <= 1 then n
else fibonacci (n - 1) + fibonacci (n - 2)
CustomMath.factorial 5
-- 120
Try itmodule CustomMath exposing (fibonacci)
fn fibonacci: Int -> Int
fn fibonacci n =
if n <= 1 then n
else fibonacci (n - 1) + fibonacci (n - 2)
CustomMath.fibonacci 10
-- 55
Try itModule Access
Access module members with dot notation:
module CustomMath exposing (add, multiply)
fn add: Int -> Int -> Int
fn add x y =
x + y
fn multiply: Int -> Int -> Int
fn multiply x y =
x * y
CustomMath.add 1 2
-- 3
Try itmodule CustomMath exposing (multiply)
fn multiply: Int -> Int -> Int
fn multiply x y =
x * y
CustomMath.multiply 3 4
-- 12
Try itImports
Import modules to use their exports:
import Html
import Html.Attributes
Importing Standard Library Modules
Keel provides built-in modules that can be imported directly:
import List
import String
import Result
import Maybe
import Json
List.map (|x: Int| x * 2) [1, 2, 3]
Json.encode { name = "Alice", age = 30 }
Ok 5 |> Result.map (|x: Int| x + 1)
Try itOr import specific functions directly:
import List exposing (map, filter)
map (|x: Int| x + 1) [1, 2, 3]
Try itSee the Standard Library for all available modules and functions.
Import with Alias
Create shorter aliases for modules using the as keyword:
import Html.Attributes as Attr
Aliases provide convenient shorthand throughout your code:
-- Modules and import aliases
import Math as M
import List as L
M.abs (L.length [1, 2, 3])
Try itAliases work with the exposing clause:
import List as L exposing (length)
length [1, 2]
Try itNote: Aliases are additive — the original module name remains available. Aliases must be uppercase and are inherited by child scopes.
Import with Exposing
Import specific items directly:
import List exposing (map)
-- Using built-in list functions directly
[1, 2, 3]
|> map (|x| x * 2) -- [2, 4, 6]
Try itUser-Defined File Modules
When you import a module that isn't part of the standard library, Keel looks for a matching .kl file in the modules/ directory relative to your entry file. Module names use PascalCase but filenames use snake_case:
my-project/
├── src/
│ └── main.kl
└── modules/
├── greeter.kl -- import Greeter
├── data_utils.kl -- import DataUtils
└── data/
├── list.kl -- import Data.List
└── nested/
└── deep.kl -- import Data.Nested.Deep
Defining a File Module
Each module file needs a module declaration with an exposing clause:
-- modules/math.kl
module exposing (double, square)
fn double : Int -> Int
fn double x = x * 2
fn square : Int -> Int
fn square x = x * x
Importing File Modules
Import file modules the same way you import stdlib modules — all import forms work:
-- Qualified access
import Math
Math.double 5 -- 10
-- With alias
import Math as M
M.square 3 -- 9
-- Exposing specific functions
import Math exposing (double)
double 5 -- 10
-- Exposing everything
import Math exposing (..)
square 4 -- 16
Nested Modules
Dots in the module name map to directory separators:
-- Imports modules/data/list.kl
import Data.List
Data.List.head [1, 2, 3]
-- Imports modules/data/nested/deep.kl
import Data.Nested.Deep
Modules Importing Other Modules
File modules can import both other user modules and stdlib modules:
-- modules/uses_stdlib.kl
module exposing (greeting)
import List
fn greeting : String -> String
fn greeting name =
let parts = ["Hello", name]
List.join ", " parts
Name Resolution
PascalCase module segments are converted to snake_case filenames:
| Module name | File path |
|---|---|
Greeter | modules/greeter.kl |
DataUtils | modules/data_utils.kl |
Data.List | modules/data/list.kl |
Data.Nested.Deep | modules/data/nested/deep.kl |
Circular Dependencies
Keel detects circular imports at compile time:
-- modules/cycle_a.kl
module exposing (a)
import CycleB -- Error: circular dependency
let a = CycleB.b
Example: Complete Module
import Math
module Geometry exposing (Shape, area, perimeter)
type Shape = Circle(Float) | Rectangle(Float, Float) | Triangle(Float, Float, Float)
fn area: Shape -> Float
fn area shape = case shape of
Circle r -> 3.14159 * r * r
Rectangle w h -> w * h
Triangle a b c ->
let s = (a + b + c) / 2.0
Math.sqrt (s * (s - a) * (s - b) * (s - c))
fn perimeter: Shape -> Float
fn perimeter shape = case shape of
Circle r -> 2.0 * 3.14159 * r
Rectangle w h -> 2.0 * (w + h)
Triangle a b c -> a + b + c
import Geometry exposing (Shape, area)
let circle = Shape::Circle(5.0)
area circle
Try itmodule Geometry exposing (perimeter)
fn perimeter: Float -> Float
fn perimeter r =
2.0 * 3.14159 * r
Geometry.perimeter 5.0
-- 31.4159
Try itExporting and Importing Enums
Enums defined in a module can be exported and used in importing code:
module Shapes exposing (Shape, area)
type Shape = Circle(Float) | Square(Float)
fn area : Shape -> Float
fn area shape = case shape of
Circle r -> 3.14 * r * r
Square s -> s * s
import Shapes exposing (Shape, area)
let c = Shape::Circle(2.0)
area c
Try itInclude the type name in the exposing list to make the enum and its constructors available to importers. Both exposing (..) and specific exposing (Type, fn) syntax work.
Shadowing Standard Library Modules
User-defined modules can shadow stdlib module names if the stdlib version is not imported:
module Math exposing (double)
fn double : Int -> Int
fn double x = x * 2
Math.double 5
Try itIf you import a stdlib module (List, String, Math, IO, Http, Json, DataFrame, Result, Maybe), you cannot define a module with the same name.
File Composition with Tasks
For composing a program from multiple files at compile time, Keel provides tasks. While modules define reusable namespaces with exports, tasks let you run another file's code and receive its exposed values as a record:
let x = 5
let { result } = Task.run "compute.kl" (x)
result -- computed by compute.kl
The target file uses a Task.define declaration to define its interface:
-- compute.kl
Task.define (x : Int) -> (result : Int)
let result = x * 2
See the tasks guide for the full details on passing variables, record destructuring, and mutation propagation.
Best Practices
- One concept per module — keep modules focused
- Use descriptive names —
User.AuthenticationnotUA - Minimize exports — only expose what's needed
- Prefer qualified access — clearer where things come from
- Keep module content indented for inline modules
Next Steps
- Learn about tasks for composing programs from multiple files
- Learn about error handling to understand Keel's helpful error messages