Elixir:使用模块范围设置变量

Kev*_*son 3 elixir

简而言之,我有一个脚本,它读取.yaml文件以在运行时获取一些配置信息,例如要联系的URL,要使用的共享密钥,是否使用调试模式等.

使用该配置的模块有一个启动函数,该函数稍后调用一个循环,并且还调用一个logdebug函数来写入诊断信息,但只有在设置了调试模式的情况下.令我恼火的是,每当我打电话给我时,我必须将配置传递给每个功能.如果我可以调用start函数并设置一些可用于模块中所有其他函数的变量,那将会容易得多.可以这样做吗?我似乎无法找到有关如何做到这一点的任何事情.

有没有像我在这里做的那样设置运行时配置的首选方法?也许我过于复杂了?

编辑:更多细节,我将此作为一个可执行文件分发Escript.Build,我不想让最终用户编辑文件,然后重建文件.这就是为什么我希望最终用户(可能不是超级技术人员)能够编辑.yaml文件.

Chr*_*yer 5

免责声明:我将你的'运行时配置'更多地解释为参数; 如果不是这样的话,这个答案可能不是很有用.

A Module类似于a Class.

不幸的是,这种常见的面向对象工作方法不够相似; Elixir/Erlang模块没有"生命",只是扁平的逻辑.你有效地尝试做的是在模块本身存储状态; 在函数式语言中,状态必须保存在变量中,因为模块在所有进程的所有调用者之间共享 - 另一个进程可能需要存储不同的状态!

然而,这是一个常见的编程问题,并且有一种惯用的方法可以在Elixir中解决它:a GenServer.

如果您不熟悉OTP,那么您应该自己学习它:它会改变您对编程的看法,它会帮助您编写更好的(读取:更可靠)软件,它会让您快乐.真.

我会将配置存储在GenServer的状态中; 如果你创建一个内部结构来表示它,你可以轻松传递它并设置默认值; 我们想要的所有东西都在令人愉悦的API中.

示例实现:

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
Run Code Online (Sandbox Code Playgroud)