Skip to content

Instantly share code, notes, and snippets.

@spacebat
Last active December 24, 2024 05:20
Show Gist options
  • Save spacebat/a3056267e757b97cfd63d9cfcb51e624 to your computer and use it in GitHub Desktop.
Save spacebat/a3056267e757b97cfd63d9cfcb51e624 to your computer and use it in GitHub Desktop.
GenSimpl demo
# GenSimpl will wrap the public functions of a state management module for a GenServer
# This state management module can be tested simply with a focus on the the internal logic of pure functions
defmodule MyStack do
defstruct stack: []
def new(initial_elements \\ []), do: struct!(__MODULE__, stack: initial_elements)
def pop(%{stack: []}), do: :error
def pop(%{stack: [head | tail]} = state), do: {head, %{state | stack: tail}}
def push(%{stack: stack} = state, value), do: {:ok, %{state | stack: [value | stack]}}
def get_state(%{stack: stack} = state), do: {stack, state}
end
# This GenServer has pop/1, push/2 and get_state/1 available in its API, as implemented above and demoed below.
# It can be tested separately with a focus on its behaviour as a server.
defmodule MyStackServer do
use GenServer
use GenSimpl, from: MyStack, except: [:new]
def start(stack \\ []), do: GenServer.start(__MODULE__, stack)
@impl GenServer
def init(init_arg), do: {:ok, MyStack.new(init_arg)}
end
# iex> {:ok, stack} = MyStackServer.start([1,3])
# {:ok, #PID<0.1678.0>}
# iex> MyStackServer.get_state(stack)
# [1, 3]
# iex> MyStackServer.pop(stack)
# 1
# iex> MyStackServer.pop(stack)
# 3
# iex> MyStackServer.get_state(stack)
# []
# iex> MyStackServer.push(stack, 17)
# :ok
# iex> MyStackServer.get_state(stack)
# [17]
# If I add debug?: true to the end of the use GenSimpl line, then compilation of the MyStackServer module prints
# the following, with the actual code generated at the end labeled AST:
# Compiling 1 file (.ex)
# only: %{
# module_info: %{0 => true, 1 => true},
# new: %{0 => true, 1 => true},
# get_state: %{1 => true},
# push: %{2 => true},
# __info__: %{1 => true},
# __struct__: %{0 => true, 1 => true},
# pop: %{1 => true}
# }
# only: %{module_info: %{*: true}, new: %{*: true}}
# except: %{module_info: %{*: true}, new: %{*: true}}
# except: %{module_info: %{*: true}, new: %{*: true}}
# functions_post_only: [
# __info__: 1,
# __struct__: 0,
# __struct__: 1,
# get_state: 1,
# new: 0,
# new: 1,
# pop: 1,
# push: 2,
# module_info: 0,
# module_info: 1
# ]
# functions_post_underscore: [get_state: 1, new: 0, new: 1, pop: 1, push: 2, module_info: 0, module_info: 1]
# functions_post_except: [get_state: 1, pop: 1, push: 2]
# AST:
# [
# def get_state(server \\ __MODULE__) do
# GenServer.call(server, {&MyStack.get_state/1, []})
# end,
# def pop(server \\ __MODULE__) do
# GenServer.call(server, {&MyStack.pop/1, []})
# end,
# def push(server \\ __MODULE__, arg1) do
# GenServer.call(server, {&MyStack.push/2, [arg1]})
# end,
# def handle_call({impl_function, args}, _from, state) when is_function(impl_function) do
# GenSimpl.apply_call(impl_function, state, args)
# end
# ]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment