Elm Core Guide: Everything You Need to Know

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 type a.
  • Nothing: Represents the absence of a value.
  • Key Functions:
    • Maybe.map: Apply a function to the value inside a Just, leaving Nothing unchanged.
    • Maybe.andThen: Chain operations that might return Maybe values. Essential for handling sequences of potentially failing operations.
    • Maybe.withDefault: Provide a default value to use if the Maybe is Nothing.
    • 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 type value.
  • Err error: Represents a failure with an error value of type error.
  • Key Functions:
    • Result.map: Apply a function to the value inside an Ok, leaving Err unchanged.
    • Result.mapError: Apply a function to the error inside an Err, leaving Ok unchanged.
    • Result.andThen: Chain operations that might return Result 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 type a)
  • 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 a Maybe a).
    • List.tail: Get the list without the first element (returns a Maybe (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 a Maybe 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 type comparable and values of type v)
  • 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 a Maybe 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 (returns Maybe).
    • 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: ” to the console

— 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 or Dict.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 and Result 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 and Result to handle potential errors gracefully. Avoid Debug.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!

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top