Master Valentines Day in Gleam: Complete Learning Path

a heart is shown on a computer screen

Master Valentines Day in Gleam: Complete Learning Path

The "Valentines Day" module is a core component of the kodikra.com curriculum, designed to teach you Gleam's powerful and safe type system. By completing this module, you will gain a foundational understanding of custom types, pattern matching, and how they combine to eliminate entire classes of runtime errors, making your code robust and predictable.


Have you ever written code that crashed because of an unexpected null value or a typo in a string? It's a frustratingly common problem in many languages. You build a complex system, only to have it fail in production because of a state you didn't account for. This is where Gleam’s philosophy of type safety becomes your greatest ally.

This comprehensive guide dives deep into the "Valentines Day" module from our exclusive Gleam learning path. We won't just show you the solution; we'll dissect the "why" behind Gleam's custom types and pattern matching. Prepare to transform your approach to problem-solving, writing code that is not only correct but is provably correct by the compiler itself.


What is the Valentines Day Module?

The "Valentines Day" module is a practical, hands-on lesson within the Gleam learning path on kodikra.com. It uses a simple, relatable theme—planning a Valentine's Day dinner—to introduce one of the most powerful features in the Gleam language: custom types (also known as enums or algebraic data types) and pattern matching via the case expression.

At its core, this module challenges you to model different states and choices as distinct types. Instead of using ambiguous strings like "approved" or "disapproved", you will define explicit types like Approved and Disapproved. This seemingly small change has profound implications for code safety and clarity, which the Gleam compiler leverages to help you write bug-free applications.

You will learn to implement a series of functions that operate on these custom types, forcing you to handle every possible scenario and leaving no room for runtime surprises. It's the perfect introduction to thinking in types, a fundamental skill for any serious Gleam developer.


Why is Mastering Custom Types Crucial in Gleam?

Understanding and utilizing custom types is not just a feature of Gleam; it is central to its entire design philosophy. Developers coming from dynamically typed languages like Python or JavaScript might initially find it verbose, but mastering this concept unlocks unparalleled benefits that lead to more maintainable and reliable software.

Compile-Time Guarantees

The most significant advantage is safety. When you define a custom type, you are creating a contract with the compiler. You list every possible variant a type can have. When you use a case expression to work with that type, the Gleam compiler forces you to handle every single variant. If you forget one, your code will not compile. This shifts error detection from runtime (when a user is clicking a button) to compile-time (when you are developing).

Elimination of Invalid States

Consider managing a request's state with strings: "loading", "success", "error". A typo like "sucess" would introduce a bug that might be hard to track down. With custom types, these states are defined once and reused, making typos impossible.

// The Gleam way: impossible to misspell a state
pub type RequestState(data, error) {
  Loading
  Success(data)
  Error(error)
}

Self-Documenting Code

Custom types make your function signatures incredibly expressive. A function signature tells a story about what it does, what it accepts, and what it can return, without needing to read a single line of implementation or documentation.

// This function signature is crystal clear:
// It takes a User and might return an Avatar, or it might return Nothing.
pub fn get_avatar(user: User) -> Result(Avatar, Nil)

The Power of the `case` Expression

The case expression is the mechanism for working with custom types. It's like a switch statement on steroids. It not only checks which variant you have but can also extract data associated with that variant in a safe and ergonomic way. This tight coupling between data definition (custom types) and data usage (case expressions) is the cornerstone of writing robust Gleam code.


How Does Gleam Implement These Concepts?

Let's break down the exact syntax and logic you'll encounter in the "Valentines Day" module. We'll explore the tools Gleam provides for building type-safe programs.

Defining Custom Types with the `type` Keyword

In Gleam, you create a custom type using the type keyword. You give the type a name (starting with a capital letter) and then list its possible variants, separated by a pipe |. Each variant is also capitalized.

In this module, you'll define two custom types: Approval and Cuisine.

// In Gleam, this defines a new type named `Approval`.
// An `Approval` can only ever be one of two things: `Approved` or `Disapproved`.
pub type Approval {
  Approved
  Disapproved
}

// Similarly, this defines a `Cuisine` type with three possible variants.
pub type Cuisine {
  Korean
  Turkish
  Brazilian
}

These definitions create new types that are distinct from strings, integers, or any other built-in type. You cannot accidentally assign an Approval value to a variable expecting a Cuisine value; the compiler will stop you.

Using `case` Expressions for Pattern Matching

Once you have a value of a custom type, the only way to determine which variant it is and act accordingly is with a case expression. The syntax is clean and readable: you provide a value to inspect, and then list patterns for each variant.

Here’s a function from the module that converts a Cuisine type into a greeting string:

import gleam/string

pub fn greeting(cuisine: Cuisine) -> String {
  case cuisine {
    Korean -> "Annyeonghaseyo!"
    Turkish -> "Merhaba!"
    Brazilian -> "Olá!"
  }
}

Notice the structure: case value { pattern -> result }. Gleam checks the value of cuisine. If it's Korean, the expression evaluates to "Annyeonghaseyo!". If it's Turkish, it returns "Merhaba!", and so on. The compiler guarantees that all variants of Cuisine are handled. If a new cuisine, say Italian, were added to the `Cuisine` type definition, this function would fail to compile until a case for Italian was also added.

Logic Flow with Pattern Matching

Here's a conceptual view of how a case expression directs the flow of your program.

    ● Start with a value (e.g., of type `Cuisine`)
    │
    ▼
  ┌─────────────────┐
  │ case value { ... } │
  └────────┬────────┘
           │
           ├─ Is it `Korean`? ⟶ Return "Annyeonghaseyo!"
           │
           ├─ Is it `Turkish`? ⟶ Return "Merhaba!"
           │
           └─ Is it `Brazilian`? ⟶ Return "Olá!"

Combining Logic in Functions

The module culminates in implementing the permission_for function, which combines multiple pieces of information to return an Approval status. This demonstrates how to build complex logic from simple, type-safe building blocks.

pub fn permission_for(cuisine: Cuisine, is_vegetarian: Bool) -> Approval {
  case cuisine, is_vegetarian {
    // If cuisine is Turkish AND is_vegetarian is true, it's approved.
    Turkish, True -> Approved
    // If cuisine is Korean AND is_vegetarian is true, it's approved.
    Korean, True -> Approved
    // The wildcard `_` matches any value.
    // So, for any other combination, it's disapproved.
    _, _ -> Disapproved
  }
}

This example introduces two new concepts:

  1. Matching on Tuples: You can pattern match on multiple values at once by grouping them into a tuple, like case cuisine, is_vegetarian.
  2. The Wildcard Pattern _: The underscore _ is a special pattern that matches any value without binding it to a name. It's useful as a catch-all case. However, it should be used with care, as explicitly matching every case is often safer.

Where Are These Patterns Used in The Real World?

The concepts learned in the "Valentines Day" module are not just academic exercises. They are fundamental patterns used to build robust, large-scale applications. Here are a few real-world scenarios where custom types and pattern matching are indispensable.

1. Handling API Responses

A network request can have many outcomes. It can be loading, it can succeed with data, or it can fail with an error. Modeling this with a custom type is a classic and powerful pattern.

pub type ApiResponse(data) {
  Loading
  Success(data)
  Failure(String)
}

pub fn render_page(response: ApiResponse(User)) {
  case response {
    Loading -> "
Loading user profile...
" Success(user) -> "

Welcome, " <> user.name <> "!

" Failure(error_message) -> "
" <> error_message <> "
" } }

This ensures you never try to access user.name when the request is still loading or has failed.

2. State Machine Implementation

Custom types are perfect for defining the states of a state machine, such as a user authentication flow, a document's lifecycle (e.g., Draft, InReview, Published, Archived), or a shopping cart's status.

3. Configuration Management

Imagine an application that can connect to different databases. You can model the configuration safely with a custom type.

pub type DbConfig {
  Postgres(connection_string: String)
  Sqlite(filepath: String)
  InMemory
}

pub fn connect(config: DbConfig) -> DbConnection {
  case config {
    Postgres(conn_str) -> postgres.connect(conn_str)
    Sqlite(path) -> sqlite.connect(path)
    InMemory -> in_memory.new_connection()
  }
}

Pros and Cons of Using Custom Types

For balanced understanding, it's important to know when and why to choose custom types over other structures.

Aspect Custom Types (Enums) Strings / Integers
Type Safety Excellent. The compiler validates all possible states and prevents typos. Poor. Prone to typos ("sucess" vs "success") and invalid values.
Readability High. Function signatures are self-documenting (e.g., fn -> Approval). ⚠️ Medium. A function returning a String is ambiguous without documentation.
Exhaustiveness Guaranteed. The case expression must handle all variants. None. An if/else chain can easily miss a possible string value.
Performance Excellent. Under the hood, variants are often compiled to efficient integer comparisons. ⚠️ Good, but... String comparisons are generally slower than integer comparisons.
Initial Verbosity ⚠️ Medium. Requires defining the type upfront before it can be used. Low. You can use a "magic string" immediately without any definition.

The Kodikra Learning Path: Valentines Day

This module is designed to be a focused, foundational step in your Gleam journey. By completing it, you will build the mental models necessary to tackle more complex problems with confidence. The progression is straightforward and reinforces the core concepts repeatedly.

Module Progression

The module consists of one core exercise that builds upon itself. You will implement several small functions that all work together, providing a cohesive learning experience.

    ● Start: Understand the Goal
    │
    ▼
  ┌───────────────────────────┐
  │ 1. Define `Approval` type │
  │    (Approved | Disapproved)  │
  └────────────┬──────────────┘
               │
               ▼
  ┌───────────────────────────┐
  │ 2. Define `Cuisine` type  │
  │    (Korean | Turkish | ...) │
  └────────────┬──────────────┘
               │
               ▼
  ┌───────────────────────────┐
  │ 3. Implement functions    │
  │    using `case` expressions │
  └────────────┬──────────────┘
               │
               ▼
    ● Module Complete

Hands-On Exercise

Dive into the practical implementation and solidify your understanding of Gleam's type system.

  • Learn Valentines Day step by step: In this foundational exercise from the kodikra.com curriculum, you will define the Approval and Cuisine custom types. You will then implement a series of functions—approval, cuisine, greeting, and permission_for—using pattern matching to handle different logical paths. This exercise is the perfect introduction to thinking in types.

Frequently Asked Questions (FAQ)

What exactly is a custom type in Gleam?

A custom type, also known as an enum or a sum type, is a way to define a type that can have one of several different but specific variants. For example, type Bool { True | False } is a custom type. It allows you to represent a fixed set of states in a type-safe way, preventing you from using any value that isn't one of the defined variants.

How is a Gleam `case` expression different from a `switch` statement in C or Java?

There are two key differences. First, a Gleam case expression is an expression, meaning it evaluates to a value, which can be returned from a function or assigned to a variable. Second, and more importantly, Gleam's case expressions must be exhaustive. The compiler checks to ensure you have a pattern for every possible variant of the type you are matching on, which eliminates a huge category of bugs common with `switch` statements (like a missing `break` or an unhandled enum member).

Why can't I just use strings like "approved" or "disapproved"?

Using strings (often called "magic strings") is brittle. A simple typo like "disaproved" would be treated as a valid but different string, causing your logic to fail silently. The compiler cannot help you find this error. With a custom type like Disapproved, a typo (e.g., Disaproved) results in a compile-time error, so the bug is caught before the program even runs.

What are Algebraic Data Types (ADTs)?

Algebraic Data Types are a concept from type theory. Custom types (sum types) are one half of ADTs. The other half are product types (like records/structs and tuples). Gleam's custom types are "sum types" because the total number of possible values is the sum of its variants. A record is a "product type" because its total possibilities are the product of its fields' possibilities. Together, they form a powerful system for modeling complex data structures.

Can custom type variants hold data?

Absolutely! This is what makes them so powerful. The examples in this module use variants without data (e.g., Approved), but you can associate data with each variant. A classic example is Gleam's built-in Result type: pub type Result(a, e) { Ok(a) | Error(e) }. Here, the Ok variant holds a value of type a, and the Error variant holds a value of type e. You can then extract this data during pattern matching.

Is Gleam a functional programming language?

Yes, Gleam is a functional, statically-typed language that runs on the Erlang virtual machine (BEAM) and can also compile to JavaScript. It emphasizes immutability, pure functions, and a strong type system to help developers write robust and maintainable code.

How does Gleam's type system prevent runtime errors?

Gleam's type system is designed to catch errors at compile-time. It prevents type mismatches (e.g., passing a String to a function expecting an Int), ensures exhaustive pattern matching with case expressions, and has no concept of null or undefined, using types like Result and Option to explicitly handle the absence of a value. This means if your Gleam code compiles, it's free from a large class of common runtime errors.


Conclusion: Write Code That Cannot Be Wrong

The "Valentines Day" module is your gateway to a new paradigm of programming. By embracing Gleam's type system, you are not just learning a new syntax; you are adopting a discipline of clarity and correctness. The skills you build here—defining custom types to model your domain and using pattern matching to handle logic exhaustively—are foundational to writing professional, production-grade Gleam applications.

You will move from writing code that you think is correct to writing code that the compiler can prove is correct. This shift is transformative, leading to greater confidence, fewer bugs, and more maintainable systems in the long run.

Disclaimer: All code snippets and concepts are based on Gleam v1.3.1 and later. While the core principles are stable, always refer to the official Gleam documentation for the latest syntax and features.

Ready to continue your journey? Explore the full Gleam Learning Roadmap on kodikra.com or return to the main Gleam language guide for more resources.


Published by Kodikra — Your trusted Gleam learning resource.