【问题标题】:Elixir: setting variables with module-wide scopeElixir:在模块范围内设置变量
【发布时间】:2015-10-31 16:44:55
【问题描述】:

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

使用该配置的模块有一个 start 函数,该函数稍后会调用一个循环并调用一个 logdebug 函数来写入诊断信息,但前提是设置了调试模式。令我恼火的是,每次调用它们时,我都必须将配置传递给这些函数中的每一个。如果我可以调用 start 函数并让它设置一些可用于模块中所有其他函数的变量,那会容易得多。可以这样做吗?我似乎找不到任何关于如何做到这一点的信息。

有没有像我在这里所做的那样设置运行时配置的首选方法?也许我把事情复杂化了?

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

【问题讨论】:

  • 我不确定这是否能解决你的问题 Kevin 但你可能想看看conform

标签: elixir


【解决方案1】:

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

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

【讨论】:

  • 哇,这是一个非常详细的答案,我怀疑里面有很多好东西。我将不得不花一些时间来真正考虑一下这个代码示例。谢谢!
  • 很好的答案!在这种情况下,由于他只想将状态保存在某个地方,他可以使用 Agent 并避免 GenServer 中的许多样板。
【解决方案2】:

答案取决于您的限制条件。你需要使用 .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 配置将证明是最佳工作流程。

【讨论】:

  • 我使用了 :yamerl 和 Application.put_env 的组合。原因是:yaml 文件适用于可能不知道如何重建 Escript.Build 正在创建的文件的最终用户。你认为我的用例是一个使用 yaml 有意义的地方,还是你仍然认为我应该寻找一种在运行时配置应用程序的不同方式?
  • 为什么让最终用户修改 yaml 或任何其他配置?给他们一个漂亮的 UI 来进行任何需要进行的配置更改。最终用户不必了解您的底层实现机制。事实上,如果您因为某些其他原因不得不放弃 YAML,那么您的最终用户就会变得不必要地困难。
【解决方案3】:

你能用 exs 文件代替吗?如有必要,可能在那里加载 yaml 文件?这是一篇看起来非常相似的博文:http://www.schmitty.me/taking-advantage-of-mix-config/

【讨论】:

    【解决方案4】:

    我最终在我的主模块中使用 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 的回答是做我想做的事情的“正确”方式。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-05-12
      • 2020-11-09
      • 2019-02-26
      • 2015-03-22
      相关资源
      最近更新 更多