Powered by AppSignal & Oban Pro

Getting Started with Elixir

01-getting-started.livemd

Getting Started with Elixir

Mix.install([])

Introduction

Welcome to your first Elixir Livebook! This interactive notebook will teach you Elixir basics with hands-on exercises.

Basic Data Types

Numbers

# Integers
42
# Floats
3.14
# Arithmetic
10 + 5
# Try it: Calculate how many seconds in a day
hours = 24
minutes = 60
seconds = 60

hours * minutes * seconds

Strings

"Hello, Platform Engineer!"
# String concatenation
"Deploy" <> " to " <> "Production"
# String interpolation
name = "Kubernetes"
"Deploying to #{name}"
# Try it: Create a log message with timestamp
timestamp = "2024-11-01"
level = "INFO"
message = "Deployment successful"

"[#{timestamp}] #{level}: #{message}"

Lists

servers = ["web-1", "web-2", "web-3"]
# Add to front (fast)
["web-0" | servers]
# Concatenate lists
servers ++ ["web-4", "web-5"]

Maps

server = %{
  name: "web-1",
  ip: "10.0.0.1",
  port: 8080,
  status: :running
}
# Access values
server.name
# Update (creates new map)
%{server | status: :stopped}
# Try it: Create a deployment configuration map
deployment = %{
  service: "api",
  version: "v2.1.0",
  replicas: 3,
  environment: "production"
}

Pattern Matching

# Match and destructure
{:ok, result} = {:ok, "Success"}
result
# Match list head and tail
[head | tail] = [1, 2, 3, 4, 5]
IO.inspect(head, label: "Head")
IO.inspect(tail, label: "Tail")
# Match map fields
%{name: server_name, port: port} = %{name: "api", port: 8080, ip: "10.0.0.1"}
IO.puts("Server: #{server_name}, Port: #{port}")

Functions

Anonymous Functions

double = fn x -> x * 2 end
double.(21)
# With capture operator
double = &amp;(&amp;1 * 2)
double.(21)
# Try it: Create a function that converts MB to GB
mb_to_gb = fn mb -> mb / 1024 end
mb_to_gb.(5120)

Named Functions (in Modules)

defmodule ServerUtils do
  def format_url(host, port) do
    "http://#{host}:#{port}"
  end
  
  def format_uptime(seconds) do
    hours = div(seconds, 3600)
    minutes = div(rem(seconds, 3600), 60)
    "#{hours}h #{minutes}m"
  end
  
  def check_port(port) do
    cond do
      port < 1 -> {:error, "Invalid port"}
      port < 1024 -> {:warning, "Privileged port"}
      port > 65535 -> {:error, "Port too high"}
      true -> {:ok, port}
    end
  end
end
ServerUtils.format_url("localhost", 8080)
ServerUtils.format_uptime(7265)
ServerUtils.check_port(8080)

Enum Module

# Map - transform each element
Enum.map([1, 2, 3, 4, 5], &amp;(&amp;1 * 2))
# Filter - keep matching elements
Enum.filter([1, 2, 3, 4, 5], &amp;(rem(&amp;1, 2) == 0))
# Reduce - accumulate a result
Enum.reduce([1, 2, 3, 4, 5], 0, &amp;(&amp;1 + &amp;2))
# Try it: Given a list of server statuses, count healthy ones
servers = [
  %{name: "web-1", status: :healthy},
  %{name: "web-2", status: :degraded},
  %{name: "web-3", status: :healthy},
  %{name: "api-1", status: :unhealthy},
  %{name: "api-2", status: :healthy}
]

servers
|> Enum.filter(&amp;(&amp;1.status == :healthy))
|> Enum.count()

Pipe Operator

# Transform data through a pipeline
"  deploy to kubernetes  "
|> String.trim()
|> String.upcase()
|> String.split(" ")
|> Enum.join("-")
# Try it: Process a log line
log_line = "2024-11-01 ERROR: Connection timeout in pod nginx-123"

log_line
|> String.split(" ", parts: 3)
|> case do
  [date, level, message] -> %{date: date, level: level, message: message}
  _ -> :invalid_format
end

Pattern Matching with case

defmodule HealthChecker do
  def check_status(status_code) do
    case status_code do
      200 -> :healthy
      code when code >= 500 -> :server_error
      code when code >= 400 -> :client_error
      _ -> :unknown
    end
  end
end
HealthChecker.check_status(200)
HealthChecker.check_status(500)
# Try it: Categorize deployment status
defmodule DeploymentStatus do
  def categorize({:success, version}) do
    "Successfully deployed #{version}"
  end
  
  def categorize({:in_progress, percentage}) when percentage >= 50 do
    "Deployment #{percentage}% complete - halfway there!"
  end
  
  def categorize({:in_progress, percentage}) do
    "Deployment #{percentage}% complete"
  end
  
  def categorize({:failed, reason}) do
    "Deployment failed: #{reason}"
  end
end

DeploymentStatus.categorize({:success, "v2.1.0"})

Practical Platform Engineering Example

defmodule LogAnalyzer do
  def parse_log(log_line) do
    case String.split(log_line, " ", parts: 3) do
      [timestamp, level, message] ->
        %{
          timestamp: timestamp,
          level: String.trim(level, ":"),
          message: message
        }
      _ ->
        nil
    end
  end
  
  def analyze_logs(log_lines) do
    log_lines
    |> Enum.map(&amp;parse_log/1)
    |> Enum.reject(&amp;is_nil/1)
    |> Enum.group_by(&amp; &amp;1.level)
    |> Enum.map(fn {level, logs} -> {level, length(logs)} end)
    |> Map.new()
  end
  
  def find_errors(log_lines) do
    log_lines
    |> Enum.map(&amp;parse_log/1)
    |> Enum.reject(&amp;is_nil/1)
    |> Enum.filter(&amp;(&amp;1.level == "ERROR"))
    |> Enum.map(&amp; &amp;1.message)
  end
end
logs = [
  "2024-11-01 INFO: Service started",
  "2024-11-01 ERROR: Connection failed",
  "2024-11-01 INFO: Retry attempt 1",
  "2024-11-01 ERROR: Timeout",
  "2024-11-01 INFO: Service recovered"
]

LogAnalyzer.analyze_logs(logs)
LogAnalyzer.find_errors(logs)

Exercises

Exercise 1: Server Health Report

Create a function that generates a health report from server data:

defmodule Exercise1 do
  def health_report(servers) do
    # TODO: Return a map with counts of healthy, degraded, and unhealthy servers
    # servers is a list of maps with :name and :status keys
  end
end

# Test data
servers = [
  %{name: "web-1", status: :healthy},
  %{name: "web-2", status: :healthy},
  %{name: "api-1", status: :degraded},
  %{name: "api-2", status: :unhealthy},
  %{name: "db-1", status: :healthy}
]

# Expected output: %{healthy: 3, degraded: 1, unhealthy: 1}
Exercise1.health_report(servers)

Exercise 2: Port Range Validator

defmodule Exercise2 do
  def validate_port_range(ports) do
    # TODO: Return only valid ports (1024-65535)
    # Hint: Use Enum.filter
  end
end

# Test
ports = [22, 80, 443, 8080, 70000, 5432, 100]
# Expected: [8080, 5432]
Exercise2.validate_port_range(ports)

Exercise 3: Log Message Formatter

defmodule Exercise3 do
  def format_log(level, service, message) do
    # TODO: Return formatted log like: "[ERROR] api: Connection failed"
    # Hint: Use string interpolation
  end
end

# Test
Exercise3.format_log("ERROR", "api", "Connection failed")

Solutions

defmodule Solutions do
  # Exercise 1
  def health_report(servers) do
    servers
    |> Enum.group_by(&amp; &amp;1.status)
    |> Enum.map(fn {status, list} -> {status, length(list)} end)
    |> Map.new()
  end

  # Exercise 2
  def validate_port_range(ports) do
    Enum.filter(ports, &amp;(&amp;1 >= 1024 and &amp;1 <= 65535))
  end

  # Exercise 3
  def format_log(level, service, message) do
    "[#{level}] #{service}: #{message}"
  end
end

IO.puts("Exercise 1:")
IO.inspect(Solutions.health_report(servers))

IO.puts("\nExercise 2:")
IO.inspect(Solutions.validate_port_range(ports))

IO.puts("\nExercise 3:")
IO.inspect(Solutions.format_log("ERROR", "api", "Connection failed"))

Next Steps

Congratulations! You’ve learned:

  • ✓ Basic data types (numbers, strings, lists, maps)
  • ✓ Pattern matching
  • ✓ Functions (anonymous and named)
  • ✓ Enum operations
  • ✓ Pipe operator
  • ✓ Case expressions

Continue Learning

  1. Read the full Beginner Documentation
  2. Try more Interactive Notebooks
  3. Build the Health Check Aggregator Project

Resources

Happy coding! 🎉