Your code…
# Initializes the ETS table and starts the sensor simulation
def init(:ok) do
# Use `:named_table` to allow access from other processes
:ets.new(:sensor_data, [:set, :public, :named_table])
schedule_sensor_read()
schedule_cleanup()
{:ok, %{}}
end
In FP functions returns something… It’s nice to write pure functions fn input → output
end
You are writing methods, probably with side effects, we don’t know the input, we don’t know the output
schedule_sensor_read()
schedule_cleanup()
This would be my init
@impl GenServer
def init(:ok) do
# Use `:named_table` to allow access from other processes
:ets.new(:sensor_data, [:set, :protected, :named_table])
# HERE TRAP EXIT, so You don't die when Your tasks dies
# You will instead receive a DOWN info message
Process.flag(:trap_exit, true)
{:ok, %{}, {:continue, :load_data}}
end
@impl GenServer
def handle_continue(:load_data, state) do
# at least the init is very short!
# I would not write this code... but data = schedule_...
schedule_sensor_read()
schedule_cleanup()
{:noreply, state}
end
Just above init, I would write the API, You just wrote the callbacks
Something like this
# THIS IS API
def get_ets_data do
# Here is the fun part with public, or protected ets...
# You can read it without having to call the server
# In case your table is private, You need to call, or cast the server
end
def write_ets_data(data) do
# Here You call the server if protected
# or You write directly if public
end
Your code…
defp schedule_sensor_read do
Process.send_after(self(), :read_sensor, 1)
end
Do You know it returns a ref?
defp schedule_sensor_read do
ref = Process.send_after(self(), :read_sensor, 1)
end
This ref could be stored in the server state
Because some day, You might want to cancel it
Your code…
# Periodically analyze temperature data
defp analyze_temperature do
analyze_and_report()
:timer.sleep(10_000) # Use :timer.sleep for more reliable behavior
analyze_temperature()
end
My code… with some modifications. I would keep the ref in the state
If the state is a struct, with ref defined, I would then return the modified state with
%{state | ref: ref}
Be careful with this syntax… It works only if state is a struct with ref as field
But You get the idea… the function takes an input, and output a modified version of the state
defp tick(state) do
# Do something here...
ref = Process.send_after(self(), :tick, 10_000)
%{state | ref: ref}
end
Your code… it’s too low level to use spawn, or spawn link
Prefer the task module
# Public function to start the analysis process
def start_analysis do
# Use `spawn` instead of `spawn_link` to avoid linking the process
spawn(fn -> analyze_temperature() end)
end
Also… it should be at least explicative of what it returns, even if You don’t use pid
_pid = spawn(fn → analyze_temperature() end)
Your code reflects something You would write in other languages
OTP is a delightful piece of software, but can be tricky to write, and Functional Programming can be tricky too. In particular if You are an experienced OOP programmer