Rob1
Simple 1-file ETS demonstration you can try
Have you ever run ETS, Elixir’s famous in-memory caching mechanism?
If not, I have prepared this short code sample you can clone and run as a single file to demonstrate it in use, just start it from the command line with elixir cloudcomputing.exs
I am just learning ETS myself, so any feedback about my file is welcome for those who have run it.
Most Liked
kokolegorille
I can see some possible enhancement…
- use handle_continue for probable long load in the init
- use a direct ets call to retrieve data, without calling the genserver
currently You use the table as if it was private… but You set it as public.
I prefer protected
- use spawn link to link both process (main and task)
it’s a good place to use trap exit, and manage task death in the genserver
Just some quick things I would do
I also prefer Process.send_after instead of :timer.sleep
If I can I would avoid using :timer
schneebyte
Well for match specs i might use a wrapper/library, but otherwise i try to just use the :erlang stuff directly, no unnecessary abstraction.
~3k LOC just so i can write ETS instead of :ets.
And its missing :ets.lookup_element/4.
I would replace all those
:ets.tab2list(:sensor_data) |> Enum.filter
with :ets.match/match_delete/select/select_delete
kokolegorille
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
Last Post!
kokolegorille
You should look at how Ecto deals with external data. You could have a SensorApi providing data. This data is casted and validated to valid Sensor data
Once casted and validated, You can have a pure Core
I don’t know what You retrieve, but You could have pure functions doing Celsius ↔ Fahrenheit conversion
Of course it’s overkill for simple project, but I would not mind using multiple modules
Sensors
Workers
Core
Api
Popular in Discussions
Other popular topics
Chat & Discussions>Discussions
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #websockets
- #supervisor
- #elixirconf-us
- #advent-of-code
- #distillery
- #processes
- #forms
- #api
- #metaprogramming
- #hex
- #security









