Skip to content

Instantly share code, notes, and snippets.

@c-u-l8er
Created July 20, 2025 19:51
Show Gist options
  • Save c-u-l8er/2a83d2f8163d02c64829254766f8887c to your computer and use it in GitHub Desktop.
Save c-u-l8er/2a83d2f8163d02c64829254766f8887c to your computer and use it in GitHub Desktop.
Actor model system with navigable DSL for EnhancedADT modules. Provides hierarchical navigation instead of link-based references.
defmodule EnhancedADT.Actors do
@moduledoc """
Actor model system with navigable DSL for EnhancedADT modules.
Provides hierarchical navigation instead of link-based references.
"""
defmacro __using__(_opts) do
quote do
import EnhancedADT.Actors.DSL
import EnhancedADT.Actors.Navigation
import EnhancedADT.Actors.Exports
import EnhancedADT.Actors.Runtime
use EnhancedADT
end
end
end
defmodule EnhancedADT.Actors.DSL do
@moduledoc """
Navigation-based DSL for defining actor modules and their interactions
"""
@doc """
Define an actor module with navigable structure
## Example
```elixir
defactor Calculator do
state :: %{value: number(), history: list()}
exports do
add/1 -> :math/arithmetic
subtract/1 -> :math/arithmetic
get_history/0 -> :data/access
end
navigation do
:math -> MathOperations
:data -> DataOperations
:ui -> UIOperations
end
handles Add(amount) do
new_value = state.value + amount
history_entry = {:add, amount, new_value}
state
|> put_in([:value], new_value)
|> update_in([:history], &[history_entry | &1])
end
end
```
"""
defmacro defactor(name, do: body) do
{state_def, exports_def, navigation_def, handlers} = extract_actor_parts(body)
quote do
defmodule unquote(name) do
use GenServer
use EnhancedADT
# State definition
unquote(generate_state_type(state_def))
# Message types (automatically derived from handlers)
unquote(generate_message_types(handlers))
# Navigation structure
@navigation unquote(extract_navigation(navigation_def))
@exports unquote(extract_exports(exports_def))
# Actor metadata
def __actor_info__ do
%{
name: unquote(name),
navigation: @navigation,
exports: @exports,
message_types: __message_types__()
}
end
# Navigation functions
unquote(generate_navigation_functions())
# Export functions
unquote(generate_export_functions(exports_def))
# GenServer callbacks
def start_link(initial_state \\ nil) do
GenServer.start_link(__MODULE__, initial_state, name: __MODULE__)
end
def init(initial_state) do
state = initial_state || default_state()
{:ok, state}
end
# Message handlers
unquote_splicing(generate_message_handlers(handlers))
# Navigation-based message sending
unquote(generate_nav_send_functions())
# Default state
unquote(generate_default_state(state_def))
end
end
end
@doc """
Define exports with navigation paths
"""
defmacro exports(do: export_list) do
quote do
@exports unquote(extract_export_mappings(export_list))
end
end
@doc """
Define navigation structure
"""
defmacro navigation(do: nav_list) do
quote do
@navigation unquote(extract_navigation_mappings(nav_list))
end
end
@doc """
Define message handlers with pattern matching
"""
defmacro handles(pattern, do: body) do
quote do
def handle_call(unquote(pattern), _from, state) do
new_state = (fn state -> unquote(body) end).(state)
{:reply, :ok, new_state}
end
def handle_cast(unquote(pattern), state) do
new_state = (fn state -> unquote(body) end).(state)
{:noreply, new_state}
end
end
end
# Helper functions for extracting actor parts
defp extract_actor_parts({:__block__, _, blocks}) do
Enum.reduce(blocks, {nil, nil, nil, []}, fn block, {state, exports, nav, handlers} ->
case block do
{:::, _, [{:state, _, _}, state_type]} ->
{state_type, exports, nav, handlers}
{:exports, _, [[do: export_block]]} ->
{state, export_block, nav, handlers}
{:navigation, _, [[do: nav_block]]} ->
{state, exports, nav_block, handlers}
{:handles, _, _} = handler ->
{state, exports, nav, [handler | handlers]}
_ ->
{state, exports, nav, [block | handlers]}
end
end)
end
defp extract_actor_parts(single_block), do: extract_actor_parts({:__block__, [], [single_block]})
defp generate_state_type(state_def) when state_def != nil do
quote do
@type state :: unquote(state_def)
end
end
defp generate_state_type(_), do: quote(do: @type state :: any())
defp generate_message_types(handlers) do
message_variants = Enum.map(handlers, fn
{:handles, _, [pattern, _]} -> extract_message_pattern(pattern)
_ -> nil
end)
|> Enum.filter(&(&1 != nil))
if message_variants != [] do
quote do
defsum Message do
unquote_splicing(message_variants)
end
def __message_types__, do: unquote(message_variants)
end
else
quote do
def __message_types__, do: []
end
end
end
defp extract_message_pattern({name, _, args}) when is_list(args) do
quote do: unquote(name)(unquote_splicing(args))
end
defp extract_message_pattern({name, _, _}) do
quote do: unquote(name)
end
defp extract_message_pattern(name) when is_atom(name) do
quote do: unquote(name)
end
defp generate_navigation_functions() do
quote do
def navigate(path) when is_list(path) do
Enum.reduce(path, __MODULE__, fn segment, current_module ->
nav_map = current_module.__actor_info__().navigation
Map.get(nav_map, segment, current_module)
end)
end
def navigate(segment) when is_atom(segment) do
navigate([segment])
end
def nav(path), do: navigate(path)
end
end
defp generate_export_functions(exports_def) when exports_def != nil do
exports = extract_export_mappings(exports_def)
Enum.map(exports, fn {func_spec, nav_path} ->
{func_name, arity} = parse_func_spec(func_spec)
quote do
def unquote(func_name)(unquote_splicing(generate_args(arity))) do
target_module = navigate(unquote(nav_path))
message = {unquote(func_name), unquote_splicing(generate_args(arity))}
GenServer.call(target_module, message)
end
end
end)
end
defp generate_export_functions(_), do: []
defp generate_args(0), do: []
defp generate_args(arity) do
Enum.map(1..arity, fn i -> Macro.var(:"arg#{i}", nil) end)
end
defp parse_func_spec({:/, _, [name, arity]}) when is_atom(name) and is_integer(arity) do
{name, arity}
end
defp extract_export_mappings({:__block__, _, mappings}) do
Enum.map(mappings, &extract_single_export/1)
end
defp extract_export_mappings(single_mapping) do
[extract_single_export(single_mapping)]
end
defp extract_single_export({:->, _, [func_spec, nav_path]}) do
path_list = case nav_path do
{:/, _, [base, segment]} -> [base, segment]
single when is_atom(single) -> [single]
_ -> nav_path
end
{func_spec, path_list}
end
defp extract_navigation(nav_def) when nav_def != nil do
extract_navigation_mappings(nav_def)
end
defp extract_navigation(_), do: %{}
defp extract_navigation_mappings({:__block__, _, mappings}) do
mappings
|> Enum.map(&extract_single_navigation/1)
|> Enum.into(%{})
end
defp extract_navigation_mappings(single_mapping) do
[extract_single_navigation(single_mapping)] |> Enum.into(%{})
end
defp extract_single_navigation({:->, _, [key, module]}) do
{key, module}
end
defp extract_exports(exports_def) when exports_def != nil do
extract_export_mappings(exports_def) |> Enum.into(%{})
end
defp extract_exports(_), do: %{}
defp generate_message_handlers(handlers) do
Enum.map(handlers, fn
{:handles, _, [pattern, [do: body]]} ->
quote do
def handle_call(unquote(pattern), _from, state) do
result = (fn state -> unquote(body) end).(state)
case result do
{reply, new_state} -> {:reply, reply, new_state}
new_state -> {:reply, :ok, new_state}
end
end
def handle_cast(unquote(pattern), state) do
new_state = (fn state -> unquote(body) end).(state)
{:noreply, new_state}
end
end
_ -> nil
end)
|> Enum.filter(&(&1 != nil))
end
defp generate_nav_send_functions() do
quote do
def send_to(path, message, opts \\ []) do
target = navigate(path)
mode = Keyword.get(opts, :mode, :call)
case mode do
:call -> GenServer.call(target, message)
:cast -> GenServer.cast(target, message)
:info -> send(target, message)
end
end
def call(path, message), do: send_to(path, message, mode: :call)
def cast(path, message), do: send_to(path, message, mode: :cast)
def notify(path, message), do: send_to(path, message, mode: :info)
end
end
defp generate_default_state(state_def) when state_def != nil do
quote do
def default_state() do
# Generate appropriate default based on state type
case unquote(state_def) do
{:%, _, [struct_name, _]} -> struct(struct_name)
_ -> %{}
end
end
end
end
defp generate_default_state(_) do
quote do
def default_state(), do: %{}
end
end
end
defmodule EnhancedADT.Actors.Navigation do
@moduledoc """
Navigation utilities for actor hierarchies
"""
@doc """
Navigate through actor hierarchy using path notation
## Examples
```elixir
# Single level navigation
nav(:math) |> call(Add(5))
# Multi-level navigation
nav([:ui, :forms, :validation]) |> call(Validate(data))
# Pipe-friendly navigation
Calculator
|> nav(:math)
|> nav(:arithmetic)
|> call(Add(10))
```
"""
defmacro nav(module, path) do
quote do
unquote(module).navigate(unquote(path))
end
end
@doc """
Broadcast message to all actors in a navigation subtree
"""
defmacro broadcast(path, message, opts \\ []) do
quote do
EnhancedADT.Actors.Navigation.broadcast_impl(
unquote(path),
unquote(message),
unquote(opts)
)
end
end
def broadcast_impl(path, message, opts) do
# Implementation for broadcasting to actor subtrees
# This would traverse the navigation tree and send to all matching actors
:ok
end
@doc """
Query actor hierarchy for capabilities
"""
defmacro query(path, capability) do
quote do
EnhancedADT.Actors.Navigation.query_capability(
unquote(path),
unquote(capability)
)
end
end
def query_capability(path, capability) do
# Implementation for querying actor capabilities
# Returns list of actors that support the given capability
[]
end
end
defmodule EnhancedADT.Actors.Exports do
@moduledoc """
Export management for actor modules
"""
@doc """
Export functions with automatic navigation binding
"""
defmacro export_to(target_path, functions) do
quote do
@export_bindings Map.put(@export_bindings || %{}, unquote(target_path), unquote(functions))
end
end
@doc """
Import functions from other actors with navigation
"""
defmacro import_from(source_path, functions, opts \\ []) do
alias_prefix = Keyword.get(opts, :as)
function_imports = Enum.map(functions, fn func ->
{func_name, arity} = parse_function_spec(func)
imported_name = if alias_prefix do
:"#{alias_prefix}_#{func_name}"
else
func_name
end
quote do
def unquote(imported_name)(unquote_splicing(generate_args(arity))) do
target = navigate(unquote(source_path))
GenServer.call(target, {unquote(func_name), unquote_splicing(generate_args(arity))})
end
end
end)
quote do
unquote_splicing(function_imports)
end
end
defp parse_function_spec({:/, _, [name, arity]}), do: {name, arity}
defp parse_function_spec(name) when is_atom(name), do: {name, 0}
defp generate_args(0), do: []
defp generate_args(arity) do
Enum.map(1..arity, fn i -> Macro.var(:"arg#{i}", nil) end)
end
end
defmodule EnhancedADT.Actors.Runtime do
@moduledoc """
Runtime utilities for actor system
"""
@doc """
Start an actor hierarchy with navigation structure
"""
defmacro start_hierarchy(root_actor, do: structure) do
quote do
supervisor_spec = EnhancedADT.Actors.Runtime.build_supervisor_spec(
unquote(root_actor),
unquote(structure)
)
Supervisor.start_link(supervisor_spec, strategy: :one_for_one)
end
end
def build_supervisor_spec(root_actor, structure) do
# Build supervisor specification from navigation structure
# This would analyze the navigation tree and create appropriate child specs
[
{root_actor, []}
]
end
@doc """
Hot-swap actor implementations while preserving state
"""
def hot_swap(actor_module, new_implementation) do
# Implementation for hot-swapping actor code
:ok
end
@doc """
Actor lifecycle management
"""
def lifecycle(actor, event) do
case event do
:start -> GenServer.start_link(actor, [])
:stop -> GenServer.stop(actor)
:restart ->
GenServer.stop(actor)
GenServer.start_link(actor, [])
:pause -> GenServer.call(actor, :pause)
:resume -> GenServer.call(actor, :resume)
end
end
end
# Example usage with EnhancedADT integration
defmodule EnhancedADT.Actors.Examples do
use EnhancedADT.Actors
# Calculator actor with navigation structure
defactor Calculator do
state :: %{value: number(), history: list(), status: atom()}
exports do
add/1 -> :math/arithmetic
subtract/1 -> :math/arithmetic
multiply/1 -> :math/arithmetic
divide/1 -> :math/arithmetic
get_result/0 -> :data/access
get_history/0 -> :data/access
clear/0 -> :control/reset
end
navigation do
:math -> MathModule
:data -> DataModule
:control -> ControlModule
:ui -> UIModule
end
# Message types defined using EnhancedADT
defsum Operation do
Add(number())
Subtract(number())
Multiply(number())
Divide(number())
Clear
GetResult
GetHistory
end
handles Add(amount) do
new_value = state.value + amount
entry = {:add, amount, new_value, DateTime.utc_now()}
state
|> Map.put(:value, new_value)
|> Map.update(:history, [entry], &[entry | &1])
end
handles Subtract(amount) do
new_value = state.value - amount
entry = {:subtract, amount, new_value, DateTime.utc_now()}
state
|> Map.put(:value, new_value)
|> Map.update(:history, [entry], &[entry | &1])
end
handles GetResult(_from, state) do
{:reply, state.value, state}
end
handles GetHistory(_from, state) do
{:reply, state.history, state}
end
handles Clear do
%{value: 0, history: [], status: :ready}
end
end
# Math operations module
defactor MathModule do
navigation do
:arithmetic -> ArithmeticModule
:advanced -> AdvancedMathModule
end
import_from [:calculator], [add/1, subtract/1], as: :calc
end
# Usage examples
def example_usage do
# Start the calculator
{:ok, _pid} = Calculator.start_link(%{value: 0, history: [], status: :ready})
# Navigate and call operations
Calculator
|> nav(:math)
|> nav(:arithmetic)
|> call(Add(5))
# Pipe-friendly operations
Calculator
|> call(Add(10))
|> call(Subtract(3))
|> call(GetResult())
# Broadcast to multiple calculation modules
broadcast([:math], Clear())
# Query for specific capabilities
math_actors = query([:math], :arithmetic)
# Use fold operations on calculation history
history = Calculator.call(GetHistory())
fold history, state: 0 do
{:add, amount, _, _} -> state + amount
{:subtract, amount, _, _} -> state - amount
_ -> state
end
end
def start_calculator_hierarchy do
start_hierarchy Calculator do
Calculator -> [
{:math, MathModule} -> [
{:arithmetic, ArithmeticModule},
{:advanced, AdvancedMathModule}
],
{:data, DataModule},
{:ui, UIModule}
]
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment