Powered by AppSignal & Oban Pro
Would you like to see your link here? Contact us

Elixir Pattern Library

notebooks/elixir_patterns.livemd

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?