Rob1

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

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 :slight_smile:
  • 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

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

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 :slight_smile:

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

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

Where Next?

Popular in Discussions Top

AstonJ
Please see the new poll here: Which code editor or IDE do you use? (Poll) (2022 Edition) It’s been a while since we first asked this, I...
208 31695 143
New
AstonJ
I’ve just started the Phoenix part of the utterly brilliant online course by @pragdave. On generating the Phoenix app he uses the --no-ec...
New
PragTob
Hey everyone, this has been brewing in my head some time and it came up again while reading Adopting Elixir. GenServers, supervisors et...
New
lorenzo
Hey everone! I created a prototype for my app using Nodejs for the api. But the framework I chose wasnt great (in general theresnt any g...
New
nburkley
AWS re:Invent is on at the moment with some interesting announcements. One new feature in particular is the Lambda Runtime API for AWS La...
New
tomekowal
Hey guys! I want to create a toy project that shows a chart of temperature over time and updates every 5 seconds. I feel LiveView is per...
New
marciol
Please, let me know if this kind of discussion already took place in another topic . Hi all, how do you consider if is better to build ...
New

Other popular topics Top

electic
Hi, I am new to Elixir. I am trying to use the DateTime component to insert a date into MySQL however the there seems to be no way to fo...
New
grych
Hi folks, Few months ago I have announced the proof-of-concept of the library to manipulate the browsers DOM objects directly from Elixi...
639 54006 488
New
New
jononomo
For some reason my phoenix channels are working for me in my local dev environment, but as soon as I deploy via Docker, I get a 403 error...
New
openscript
Hello! Sorry for this astonishing simple question, but I’m really stuck. I try to set up the intellij-elixir plugin, but I don’t know ho...
New
ashish173
I am using Ecto timestamps with postgres, I can see the timestamps() use the :naive_dateime but for my use case I wanted to store the ti...
New

We're in Beta

About us Mission Statement