Elixir Pattern Library
Setup
Mix.install([
{:jason, "~> 1.4"},
{:kino, "~> 0.11.0"},
{:explorer, "~> 0.7.0"},
{:mox, "~> 1.0", only: :dev},
{:telemetry, "~> 1.0"}
])
defmodule PatternLibrary do
def common_patterns do
%{
"genserver" => %{
name: "GenServer Pattern",
description: "Common GenServer patterns and behaviors",
patterns: [
%{
name: "Basic GenServer",
code: """
defmodule MyServer do
use GenServer
# Client API
def start_link(opts \\\\ []) do
GenServer.start_link(__MODULE__, :ok, opts)
end
def get_state(pid) do
GenServer.call(pid, :get_state)
end
# Server Callbacks
@impl true
def init(:ok) do
{:ok, %{}}
end
@impl true
def handle_call(:get_state, _from, state) do
{:reply, state, state}
end
end
""",
use_cases: ["State management", "Background processing", "Rate limiting"]
},
%{
name: "Supervised GenServer",
code: """
defmodule MyApp.Application do
use Application
def start(_type, _args) do
children = [
{MyServer, name: MyServer}
]
opts = [strategy: :one_for_one, name: MyApp.Supervisor]
Supervisor.start_link(children, opts)
end
end
"""
}
]
},
"pub_sub" => %{
name: "Pub/Sub Patterns",
description: "Event handling and pub/sub patterns",
patterns: [
%{
name: "Phoenix PubSub",
code: """
# In your application.ex
children = [
{Phoenix.PubSub, name: MyApp.PubSub}
]
# Publishing
Phoenix.PubSub.broadcast(MyApp.PubSub, "room:123", {:new_msg, msg})
# Subscribing
Phoenix.PubSub.subscribe(MyApp.PubSub, "room:123")
# Handling events
def handle_info({:new_msg, msg}, socket) do
{:noreply, assign(socket, :messages, [msg | socket.assigns.messages])}
end
"""
}
]
},
"telemetry" => %{
name: "Telemetry Patterns",
description: "Common telemetry and instrumentation patterns",
patterns: [
%{
name: "Basic Telemetry",
code: """
defmodule MyApp.Telemetry do
def setup do
events = [
[:my_app, :repo, :query],
[:my_app, :api, :request]
]
:telemetry.attach_many(
"my-app-metrics",
events,
&handle_event/4,
nil
)
end
def handle_event([:my_app, :repo, :query], measurements, metadata, _config) do
# Handle the event
duration = measurements.duration
# Log or store metrics
end
end
"""
}
]
}
}
end
def advanced_patterns do
%{
"behaviours" => %{
name: "Custom Behaviours",
description: "Advanced behaviour patterns and implementations",
patterns: [
%{
name: "Custom Behaviour Definition",
code: """
defmodule MyBehaviour do
@callback init(opts :: keyword()) :: {:ok, state :: term()} | {:error, reason :: term()}
@callback handle_event(event :: term(), state :: term()) ::
{:ok, new_state :: term()} |
{:error, reason :: term(), new_state :: term()}
@optional_callbacks [terminate: 2]
defmacro __using__(_opts) do
quote do
@behaviour MyBehaviour
def terminate(_reason, _state), do: :ok
defoverridable [terminate: 2]
end
end
end
""",
use_cases: ["Plugin systems", "Strategy patterns", "Protocol implementations"]
}
]
},
"macros" => %{
name: "Advanced Macro Patterns",
description: "Complex macro patterns and AST manipulation",
patterns: [
%{
name: "AST Transformation",
code: """
defmodule MyMacros do
defmacro trace(do: block) do
quote do
try do
:telemetry.span([:my_app, :trace], %{}, fn ->
result = unquote(block)
{{:ok, result}, %{}}
end)
rescue
e ->
:telemetry.span([:my_app, :trace, :error], %{error: e}, fn ->
reraise e, __STACKTRACE__
end)
end
end
end
end
"""
}
]
},
"concurrency" => %{
name: "Advanced Concurrency Patterns",
description: "Complex concurrency and fault tolerance patterns",
patterns: [
%{
name: "Dynamic Supervisor with Registry",
code: """
defmodule MyApp.DynamicWorkers do
use DynamicSupervisor
def start_link(init_arg) do
DynamicSupervisor.start_link(__MODULE__, init_arg, name: __MODULE__)
end
@impl true
def init(_init_arg) do
DynamicSupervisor.init(strategy: :one_for_one)
end
def start_worker(worker_id) do
spec = {MyApp.Worker, id: worker_id}
DynamicSupervisor.start_child(__MODULE__, spec)
end
def terminate_worker(worker_id) do
case Registry.lookup(MyApp.WorkerRegistry, worker_id) do
[{pid, _}] -> DynamicSupervisor.terminate_child(__MODULE__, pid)
[] -> {:error, :not_found}
end
end
end
"""
}
]
}
}
end
end
Pattern Explorer
inputs = [
pattern_type: Kino.Input.select("Pattern Type", [
"Common Patterns": "common",
"Advanced Patterns": "advanced"
]),
category: Kino.Input.select("Category", [
"GenServer": "genserver",
"Pub/Sub": "pub_sub",
"Telemetry": "telemetry",
"Behaviours": "behaviours",
"Macros": "macros",
"Concurrency": "concurrency"
])
]
form = Kino.Control.form(inputs, submit: "Show Pattern")
frame = Kino.Frame.new()
Kino.listen(form, fn %{data: %{pattern_type: type, category: category}} ->
patterns = if type == "common", do: PatternLibrary.common_patterns(), else: PatternLibrary.advanced_patterns()
pattern = patterns[category]
content = Kino.Layout.grid([
Kino.Markdown.new("""
## #{pattern.name}
#{pattern.description}
### Available Patterns:
#{Enum.map_join(pattern.patterns, "\n\n", fn p -> """
#### #{p.name}
```elixir
#{p.code}
```
#{if Map.has_key?(p, :use_cases), do: "**Use Cases:** #{Enum.join(p.use_cases, ", ")}", else: ""}
""" end)}
""")
])
Kino.Frame.render(frame, content)
end)
frame
## Pattern Generator
generator_inputs = [
base_pattern: Kino.Input.select("Base Pattern", [
"GenServer": "genserver",
"Supervisor": "supervisor",
"DynamicSupervisor": "dynamic_supervisor",
"Registry": "registry",
"Behaviour": "behaviour"
]),
options: Kino.Input.text("Options (comma-separated)"),
module_name: Kino.Input.text("Module Name")
]
generator_form = Kino.Control.form(generator_inputs, submit: "Generate Code")
generator_frame = Kino.Frame.new()
defmodule PatternGenerator do
def generate(pattern, module_name, options) do
opts = String.split(options, ",", trim: true)
|> Enum.map(&String.trim/1)
case pattern do
"genserver" -> generate_genserver(module_name, opts)
"supervisor" -> generate_supervisor(module_name, opts)
"behaviour" -> generate_behaviour(module_name, opts)
_ -> "Pattern not implemented yet"
end
end
defp generate_genserver(module_name, opts) do
"""
defmodule #{module_name} do
use GenServer
require Logger
#{if "state" in opts do
"defstruct [:name, :value, :metadata]"
end}
# Client API
def start_link(opts \\\\ []) do
GenServer.start_link(__MODULE__, :ok, opts)
end
#{if "async" in opts do
"""
def async_call(pid, msg) do
GenServer.cast(pid, {:async, msg})
end
"""
end}
# Server Callbacks
@impl true
def init(:ok) do
#{if "telemetry" in opts do
":telemetry.execute([:#{module_name}, :init], %{}, %{})"
end}
{:ok, %{}}
end
#{if "async" in opts do
"""
@impl true
def handle_cast({:async, msg}, state) do
Logger.info("Handling async message: #{inspect(msg)}")
{:noreply, state}
end
"""
end}
end
"""
end
# Add more generators as needed...
end
Kino.listen(generator_form, fn %{data: %{base_pattern: pattern, options: options, module_name: module_name}} ->
code = PatternGenerator.generate(pattern, module_name, options)
content = Kino.Layout.grid([
Kino.Markdown.new("""
### Generated Code
```elixir
#{code}
```
""")
])
Kino.Frame.render(generator_frame, content)
end)
generator_frame
This notebook provides:
1. Common Patterns Library: - GenServer patterns - Pub/Sub patterns - Telemetry patterns - Basic supervision patterns
2. Advanced Patterns Library: - Custom behaviours - Complex macros - Advanced concurrency patterns - Fault tolerance patterns
3. Pattern Explorer: - Browse different pattern categories - See example implementations - View use cases and descriptions
4. Pattern Generator: - Generate code from base patterns - Customize with options - Get production-ready templates
Try: 1. Explore different pattern categories 2. Generate custom implementations 3. Mix and match patterns for your needs
Would you like me to add any specific patterns or categories?