In a nutshell, I have a script that reads a .yaml file to get some configuration information at runtime such as what URL to contact, what shared secret to use, whether to use debug mode, etc.
The module that uses that config has a start function which later calls a loop and also calls a logdebug function that writes diagnostics but only if debug mode is set. What is irritating me, though is that I have to pass my configuration around to each of these functions every time I call them. It would be much easier if I could call the start function and have it set some variables that are available to all the other functions in the module. Can that be done? I can't seem to find anything about how to do that.
Is there a preferred way of setting runtime configuration like what I'm doing here? Maybe I'm overcomplicating things?
EDIT: Little more detail, I'm distributing this as an executable created with Escript.Build and I don't want to make the end users edit a file and then rebuild the file. That's why I want the end user (who might not be super technical) to just be able to edit a .yaml file.
Disclaimer: I'm interpreting your 'runtime config' more as arguments; if that's not the case, this answer may not be very useful.
A Module is similar to a Class.
Unfortunately, not similar enough for this common O-O approach to work; Elixir/Erlang modules have no "life" in them, and are just flat logic. What you are effectively trying to do is store state in the Module itself; in a functional language, state must be kept in variables, because the Module is shared across all callers from all processes- another process might need to store different state!
However, this is a common programming problem, and there is an idiomatic way to solve it in Elixir: a GenServer.
If you aren't familiar with OTP, you owe it to yourself to learn it: it'll change the way you think about programming, it'll help you write better (read: more reliable) software, and it'll make you happy. Really.
I would store the config in the GenServer's state; If you create an internal struct to represent it, you can pass it around easily and set defaults; all the things we want in a pleasing API.
A sample implementation:
defmodule WebRequestor do
use GenServer
### API ###
# these functions execute in the CALLER's process
def start_link() do
GenServer.start_link(__MODULE__, [], [name: __MODULE__])
end
def start do
# could use .call if you need synch, but then you'd want to look at
# delayed reply genserver calls, which is an advanced usage
GenServer.cast(__MODULE__, :start)
end
#could add other methods for enabling debug, setting secrets, etc.
def update_url(new_url) do
GenServer.call(__MODULE__, {:update_url, new_url})
end
defmodule State do
@doc false
defstruct [
url: "http://api.my.default",
secret: "mybadpassword",
debug: false,
status: :ready, # or whatever else you might need
]
end
### GenServer Callbacks ###
# These functions execute in the SERVER's process
def init([]) do
config = read_my_config_file
{:ok, config}
end
def handle_cast(:start, %{status: :ready} = state) do
if config.debug, do: IO.puts "Starting"
make_request(state.url)
{:noreply, %{state|status :running}}
end
def handle_cast(:state, state) do
#already running, so don't start again.
{:noreply, state}
end
def handle_call({:update_url, new_url}, _from, state) do
{:reply, :ok, %{state|url: new_url}}
end
### Internal Calls ###
defp make_request(config) do
# whatever you do here...
end
defp read_my_config_file do
# config reading...
%State{}
end
end
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With