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

时间:2015-10-31 16:44:56

标签: elixir

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

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

是否有一种设置运行时配置的首选方式,就像我在这里做的一样?也许我过度复杂了?

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

4 个答案:

答案 0 :(得分:5)

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

Module类似于Class

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

但是,这是一个常见的编程问题,在Elixir中有一种惯用的解决方法: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

答案 1 :(得分:3)

答案取决于你的约束。你需要使用.yaml吗?我们不鼓励使用YAML,除非你真的让非Elixir程序员需要触摸它们。如果他们都被程序员触及,那么你可以使用Elixir配置:

# config/config.exs
config :my_app,
  url: "...",
  this: "...",
  that: "..."

这将允许您使用Application.get_env(:my_app, :url)Application.put_env(:my_app, :foo, :bar)等功能访问和更改配置。将来,如果您想构建版本(将整个应用程序与VM一起发布到一个目录中),提供升级等,使用Elixir配置将证明是最佳工作流程。

答案 2 :(得分:1)

您可以使用exs文件吗?如有必要,可能会在那里加载yaml文件?这篇博文似乎非常相似:http://www.schmitty.me/taking-advantage-of-mix-config/

答案 3 :(得分:0)

我最后在我的主模块中使用yamerl来读取.yaml文件,然后我使用Application.put_env/2将值放入可用于我所有模块的位置。

[ config | _ ] = :yamerl_constr.file("config.yaml") Application.put_env(:osq_simulator, :base_url, :proplists.get_value('base_url', config))

虽然基于我得到的其他一些反馈,但看起来Chris Meyer的回答可能就是"对"做我想做的事情。