Elm Core Guide: Everything You Need to Know
Elm’s “Core” libraries are the foundation of any Elm application. They provide the essential building blocks for data structures, basic operations, and the fundamental types you’ll use everywhere. Understanding the Core libraries is crucial for becoming proficient in Elm. This guide provides a comprehensive overview of the key modules and concepts within Elm’s Core.
What is “Elm Core”?
Elm Core isn’t a single module, but rather a collection of core libraries automatically included with every Elm project. These libraries are meticulously designed for reliability, performance, and consistency. They are so fundamental that you’ll find yourself importing parts of them in virtually every Elm file you write.
Key Modules and Concepts
Let’s dive into the most important modules within Elm Core and the core concepts they introduce:
1. Basics
(implicitly imported)
This module is so fundamental that its contents are available without an explicit import
statement. Basics
provides:
- Basic Types:
Int
,Float
,Bool
,Char
,String
- Comparison Operators:
==
,/=
,<
,>
,<=
,>=
- Logical Operators:
&&
(and),||
(or),not
- Arithmetic Operators:
+
,-
,*
,/
,//
(integer division),^
(exponentiation),modBy
- Misc. Functions:
abs
,sqrt
,round
,floor
,ceiling
,toString
,identity
,never
- The Pipe Operator:
|>
(forward pipe) and<|
(backward pipe) – essential for function composition and readable code. - Tuples:
()
(unit, representing no value), pairs, triples, etc. (e.g.,(1, "hello")
) - Function Application
elm
-- Basics examples
5 + 3 -- Addition
"Hello" ++ " World" -- String concatenation
True && False -- Logical AND
5 |> toString -- Piping 5 to the toString function (equivalent to toString 5)
let x = 10 in x -- 'let in' is used to declare a variable.
(1, "hello", True) -- A tuple
identity 5 == 5
2. Maybe
The Maybe
type represents a value that might be present or might be absent. It’s Elm’s way of handling optional values and avoiding null pointer exceptions.
- Type Definition:
type Maybe a = Just a | Nothing
Just a
: Represents a value of typea
.Nothing
: Represents the absence of a value.- Key Functions:
Maybe.map
: Apply a function to the value inside aJust
, leavingNothing
unchanged.Maybe.andThen
: Chain operations that might returnMaybe
values. Essential for handling sequences of potentially failing operations.Maybe.withDefault
: Provide a default value to use if theMaybe
isNothing
.Maybe.map2
,Maybe.map3
, …,Maybe.map5
: Apply functions that take multiple Maybe arguments.
“`elm
— Maybe examples
safeDivide : Int -> Int -> Maybe Float
safeDivide divisor dividend =
if divisor == 0 then
Nothing
else
Just (toFloat dividend / toFloat divisor)
result1 = safeDivide 2 10 — Just 5.0
result2 = safeDivide 0 5 — Nothing
— Using Maybe.map
doubledResult = Maybe.map (\x -> x * 2) result1 — Just 10.0
— Using Maybe.andThen
chainedResult =
safeDivide 2 10
|> Maybe.andThen (\result -> safeDivide 5 (round result)) — Just 1.0
— safeDivide 0 10 |> Maybe.andThen would result in Nothing
— Using Maybe.withDefault
safeResult = safeDivide 0 10 |> Maybe.withDefault 0 — 0
–Using Maybe.map2
maybeSum : Maybe Int -> Maybe Int -> Maybe Int
maybeSum a b =
Maybe.map2 (+) a b
sumResult1 = maybeSum (Just 3) (Just 5) –Just 8
sumResult2 = maybeSum (Just 3) Nothing — Nothing
“`
3. Result
The Result
type represents the outcome of an operation that could either succeed or fail. It’s more informative than Maybe
because it carries information about why an operation failed.
- Type Definition:
type Result error value = Ok value | Err error
Ok value
: Represents a successful result with a value of typevalue
.Err error
: Represents a failure with an error value of typeerror
.- Key Functions:
Result.map
: Apply a function to the value inside anOk
, leavingErr
unchanged.Result.mapError
: Apply a function to the error inside anErr
, leavingOk
unchanged.Result.andThen
: Chain operations that might returnResult
values.Result.withDefault
: Provide a default value for the success case.Result.toMaybe
: Convert a Result to a Maybe.Result.fromMaybe
: Convert a Maybe to a Result.Result.map2
,Result.map3
, …,Result.map5
“`elm
— Result examples
parsePositiveInt : String -> Result String Int
parsePositiveInt str =
case String.toInt str of
Just num ->
if num > 0 then
Ok num
else
Err “Number must be positive”
Nothing ->
Err “Invalid integer”
successResult = parsePositiveInt “123” — Ok 123
failureResult = parsePositiveInt “-5” — Err “Number must be positive”
invalidResult = parsePositiveInt “abc” — Err “Invalid integer”
parseResult = parsePositiveInt “5” |> Result.map (\x -> x * 2) –Ok 10
parseError = parsePositiveInt “-1” |> Result.mapError (\x -> “Error: ” ++ x) — Err “Error: Number must be positive”
— Using Result.andThen
chainedParseResult =
parsePositiveInt “10”
|> Result.andThen (\num -> parsePositiveInt (toString (num * 2))) — Ok 20
— Result.andThen with a failing function would result in Err
— Using Result.withDefault
defaultValue = parsePositiveInt “-5” |> Result.withDefault 0 — 0
“`
4. List
Elm’s List
is an immutable, singly-linked list. It’s the fundamental collection type in Elm.
- Type:
List a
(a list of elements of typea
) - Creation:
[]
: The empty list.[1, 2, 3]
: A list containing 1, 2, and 3.1 :: [2, 3]
: Prepending (consing) 1 to the list[2, 3]
.::
is the cons operator.
- Key Functions (from
List
module):List.isEmpty
: Check if a list is empty.List.length
: Get the number of elements in a list.List.head
: Get the first element (returns aMaybe a
).List.tail
: Get the list without the first element (returns aMaybe (List a)
).List.map
: Apply a function to each element of the list.List.filter
: Create a new list containing only elements that satisfy a predicate.List.foldl
/List.foldr
: Reduce a list to a single value (left and right folds).List.append
/(++)
: Concatenate two lists.List.reverse
: Reverse the order of elements.List.any
/List.all
: Check if any or all elements satisfy a predicate.List.member
: Check if an element is present in the list.List.take
/List.drop
: Take or drop a specified number of elements.List.concatMap
: Map a function that returns a list over each element, then flatten the result.
“`elm
— List examples
myList = [1, 2, 3, 4, 5]
emptyList = []
consList = 0 :: myList — [0, 1, 2, 3, 4, 5]
List.isEmpty emptyList — True
List.length myList — 5
List.head myList — Just 1
List.tail myList — Just [2, 3, 4, 5]
doubledList = List.map (\x -> x * 2) myList — [2, 4, 6, 8, 10]
evenNumbers = List.filter (\x -> x % 2 == 0) myList — [2, 4]
sum = List.foldl (+) 0 myList — 15 (0 + 1 + 2 + 3 + 4 + 5)
reversed = List.reverse myList — [5,4,3,2,1]
combinedList = List.append myList [6,7] — [1,2,3,4,5,6,7]
List.any (\x -> x > 3) myList — True (because 4 and 5 are greater than 3)
List.all (\x -> x > 0) myList — True (because all elements are greater than 0)
List.member 3 myList — True
firstTwo = List.take 2 myList — [1,2]
remaining = List.drop 2 myList — [3,4,5]
squaresAndCubes = List.concatMap (\x -> [x*x, x*x*x]) [1, 2] -- [1, 1, 4, 8]
“`
5. Array
Arrays, unlike Lists, provide constant-time access to elements by index. They are also immutable, but offer better performance for random access. Use Arrays when you need fast indexed access; use Lists for sequential processing.
- Type:
Array a
- Creation:
Array.empty
: The empty array.Array.repeat 5 0
: An array of length 5, filled with 0s.Array.fromList [1, 2, 3]
: Create an array from a list.
- Key Functions (from
Array
module):Array.isEmpty
: Check if an array is empty.Array.length
: Get the number of elements.Array.get
: Get an element at a specific index (returns aMaybe a
).Array.set
: Create a new array with a value at a specific index updated.Array.push
: Append an element to the end (creates a new array).Array.map
: Apply a function to each element.Array.indexedMap
: Map a function, providing both the index and the element.Array.slice
: Extract a portion of the array.Array.append
Array.filter
Array.foldl
/Array.foldr
: Reduce an Array to a single value.
“`elm
— Array examples
myArray = Array.fromList [1, 2, 3]
emptyArray = Array.empty
repeatedArray = Array.repeat 3 “hello” — Array.fromList [“hello”, “hello”, “hello”]
Array.isEmpty emptyArray — True
Array.length myArray — 3
Array.get 1 myArray — Just 2
Array.get 5 myArray — Nothing
newArray = Array.set 1 10 myArray — Array.fromList [1, 10, 3] (myArray is unchanged)
biggerArray = Array.push 4 myArray — Array.fromList [1, 2, 3, 4]
doubledArray = Array.map (\x -> x * 2) myArray — Array.fromList [2, 4, 6]
indexed = Array.indexedMap (\i x -> String.fromInt i ++ “: ” ++ String.fromInt x ) myArray — Array.fromList [“0: 1″,”1: 2″,”2: 3”]
slicedArray = Array.slice 1 3 myArray — Array.fromList [2, 3]
appended = Array.append myArray (Array.repeat 2 0) — Array.fromList [1,2,3,0,0]
filtered = Array.filter (\x -> x>1) myArray — Array.fromList [2,3]
summed = Array.foldl (+) 0 myArray — 6
“`
6. Dict
(Dictionary)
Dictionaries (or Maps) store key-value pairs. Keys must be comparable (e.g., Int
, String
, Char
). They provide efficient lookup by key.
- Type:
Dict comparable v
(a dictionary with keys of typecomparable
and values of typev
) - Creation:
Dict.empty
: An empty dictionary.Dict.singleton "a" 1
: A dictionary with one key-value pair (“a” -> 1).Dict.fromList [ ("a", 1), ("b", 2) ]
: Create a dictionary from a list of key-value pairs.
- Key Functions (from
Dict
module):Dict.isEmpty
: Check if empty.Dict.member
: Check if a key exists.Dict.get
: Get the value associated with a key (returns aMaybe v
).Dict.insert
: Insert or update a key-value pair (creates a new dictionary).Dict.remove
: Remove a key-value pair (creates a new dictionary).Dict.update
: Update the value associated with a key using a function.Dict.keys
: Get a list of all keys.Dict.values
: Get a list of all values.Dict.map
: Apply a function to all values.Dict.toList
: Convert the dictionary to a list of key-value pairs.
“`elm
— Dict examples
import Dict
myDict = Dict.fromList [ (“a”, 1), (“b”, 2), (“c”, 3) ]
emptyDict = Dict.empty
singleDict = Dict.singleton “x” 42
Dict.isEmpty emptyDict — True
Dict.member “b” myDict — True
Dict.get “b” myDict — Just 2
Dict.get “z” myDict — Nothing
newDict = Dict.insert “d” 4 myDict — myDict is unchanged; newDict contains the new entry
updatedDict = Dict.insert “b” 20 myDict –Replaces the b
removedDict = Dict.remove “b” myDict — newDict is [ (“a”, 1), (“c”, 3) ]
updated = Dict.update “b” (\maybeValue -> Maybe.map (\v -> v + 1) maybeValue) myDict –Increment b
keys = Dict.keys myDict — [“a”, “b”, “c”]
values = Dict.values myDict — [1, 2, 3]
doubledValues = Dict.map (\k v -> v * 2) myDict
pairs = Dict.toList myDict — [ (“a”, 1), (“b”, 2), (“c”, 3) ]
“`
7. Set
Sets are collections of unique, comparable values. They are efficient for checking membership and performing set operations (union, intersection, difference).
- Type:
Set comparable
- Creation:
Set.empty
: An empty set.Set.singleton 1
: A set containing only the value 1.Set.fromList [1, 2, 3, 2]
: Create a set from a list (duplicates are removed).
- Key Functions (from
Set
module):Set.isEmpty
: Check if empty.Set.member
: Check if a value is in the set.Set.insert
: Add a value (creates a new set).Set.remove
: Remove a value (creates a new set).Set.union
: Combine two sets.Set.intersect
: Find the common elements of two sets.Set.diff
: Find the elements in the first set but not the second.Set.toList
: Convert the set to a list.
“`elm
— Set examples
import Set
mySet = Set.fromList [1, 2, 3, 2, 1] — Duplicates are removed; mySet contains {1, 2, 3}
emptySet = Set.empty
singletonSet = Set.singleton 5
Set.isEmpty emptySet — True
Set.member 2 mySet — True
Set.member 4 mySet — False
newSet = Set.insert 4 mySet — mySet is unchanged; newSet contains {1, 2, 3, 4}
removedSet = Set.remove 2 mySet — {1, 3}
set1 = Set.fromList [1, 2, 3]
set2 = Set.fromList [3, 4, 5]
unionSet = Set.union set1 set2 — {1, 2, 3, 4, 5}
intersectionSet = Set.intersect set1 set2 — {3}
differenceSet = Set.diff set1 set2 — {1, 2}
listFromSet = Set.toList mySet — [1, 2, 3] (order might vary)
“`
8. String
The String
module provides functions for working with text. Elm strings are UTF-8 encoded.
- Type:
String
- Key Functions (from
String
module):String.isEmpty
: Check if a string is empty.String.length
: Get the number of characters.String.append
/(++)
: Concatenate strings.String.slice
: Extract a substring.String.left
/String.right
: Get a specified number of characters from the beginning/end.String.contains
: Check if a string contains a substring.String.startsWith
/String.endsWith
: Check prefix/suffix.String.toUpper
/String.toLower
: Convert case.String.trim
/String.trimLeft
/String.trimRight
: Remove whitespace.String.split
: Split a string into a list of substrings based on a delimiter.String.join
: Join a list of strings into a single string.String.toInt
/String.toFloat
: Attempt to convert a string to a number (returnsMaybe
).String.fromInt
,String.fromFloat
String.repeat
“`elm
— String examples
myString = “Hello, world!”
emptyString = “”
String.isEmpty emptyString — True
String.length myString — 13
combinedString = String.append “Hello” “, world!” — “Hello, world!”
combinedString2 = “Hello” ++ “, world!”
substring = String.slice 7 12 myString — “world”
firstFive = String.left 5 myString — “Hello”
lastThree = String.right 3 myString — “ld!”
String.contains “world” myString — True
String.startsWith “Hello” myString — True
String.endsWith “!” myString — True
upperCase = String.toUpper myString — “HELLO, WORLD!”
lowerCase = String.toLower myString — “hello, world!”
trimmedString = String.trim ” spaces ” — “spaces”
words = String.split “,” “Hello, world!” — [“Hello”, ” world!”]
joinedString = String.join “-” [“a”, “b”, “c”] — “a-b-c”
parsedInt = String.toInt “123” — Just 123
parsedFloat = String.toFloat “3.14” — Just 3.14
invalidInt = String.toInt “abc” — Nothing
intString = String.fromInt 123 –“123”
floatString = String.fromFloat 123.5 –“123.5”
repeated = String.repeat 3 “ha” — “hahaha”
“`
9. Tuple
While tuples are part of Basics
, the Tuple
module provides functions for working specifically with pairs (tuples of two elements).
- Key Functions (from
Tuple
module):Tuple.first
: Get the first element of a pair.Tuple.second
: Get the second element of a pair.Tuple.mapFirst
: Apply a function to the first element.Tuple.mapSecond
: Apply a function to the second element.
“`elm
— Tuple examples
myPair = (1, “hello”)
firstElement = Tuple.first myPair — 1
secondElement = Tuple.second myPair — “hello”
newPair1 = Tuple.mapFirst (\x -> x * 2) myPair — (2, “hello”)
newPair2 = Tuple.mapSecond (\s -> s ++ “!”) myPair — (1, “hello!”)
“`
10. Debug
The Debug module is used for logging information during development.
Debug.log
: Log a value to the console, along with an optional label. This function returns the value that it logs, so it can be inserted into existing code without changing behavior (during development).Debug.todo
: Mark a section of code as not-yet-implemented. This will cause a runtime error if reached. Helpful for sketching out program structure.Debug.crash
: Immediately halt the program with an error message. Useful for handling unexpected states during development.
“`elm
— Debug examples
addAndLog : Int -> Int -> Int
addAndLog x y =
let
sum = x + y
in
Debug.log “The sum is” sum — Prints “The sum is:
— Debug.todo “Implement this later”
— result = Debug.crash “Unexpected state!”
“`
Important Notes:
- Immutability: All data structures in Elm Core are immutable. Functions like
List.append
orDict.insert
don’t modify the original data; they return new data structures with the changes. - Type Safety: Elm’s strong static typing, combined with the
Maybe
andResult
types, eliminates null pointer exceptions and many common runtime errors. - Function Composition: Use the pipe operator (
|>
) extensively to chain functions together, creating clean and readable code. Embrace functional programming principles. - Error Handling: Use
Maybe
andResult
to handle potential errors gracefully. AvoidDebug.crash
in production code. - No Side Effects: Functions in Elm’s core libraries are pure. That is they only compute results based on the provided values.
This guide covers the core of Elm’s Core libraries. By mastering these concepts and modules, you’ll have a solid foundation for building robust and reliable Elm applications. Remember to consult the official Elm documentation for each module for the most up-to-date and detailed information. Happy coding!