Elixir:设置模块范围内的变量

Elixir:设置模块范围内的变量,elixir,Elixir,简而言之,我有一个脚本,它读取一个.yaml文件以在运行时获取一些配置信息,例如要联系的URL、要使用的共享秘密、是否使用调试模式等 使用该配置的模块有一个启动函数,该函数稍后会调用循环,也会调用一个logdebug函数,该函数只在设置了调试模式时写入诊断。但让我恼火的是,每次调用这些函数时,我都必须将配置传递给它们。如果我可以调用start函数,并让它设置一些可用于模块中所有其他函数的变量,那么会容易得多。那能做到吗?我似乎找不到任何关于如何做到这一点的方法 有没有像我在这里所做的那样设置运行

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

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

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


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

您可以使用exs文件吗?如果需要,是否可能在那里加载yaml文件?这里有一篇类似的博文:

答案取决于你的限制。你需要使用yaml吗?我们不鼓励使用YAML,除非您确实有非长生不老药程序员需要接触这些。如果程序员都在触摸它们,那么您可以使用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配置将证明是最佳的工作流。

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

模块
类似于

不幸的是,这种常见的O-O方法还不够相似;Elixir/Erlang模块中没有“生命”,只是平面逻辑。实际上,您要做的是将状态存储在模块本身中;在函数式语言中,状态必须保存在变量中,因为模块在所有进程的所有调用方之间共享-另一个进程可能需要存储不同的状态

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

如果你不熟悉,你有责任学习它:它会改变你对编程的思考方式,它会帮助你编写更好(阅读:更可靠)的软件,它会让你快乐。真的

我会将配置存储在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

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


[config |(]=:yamerl_constr.file(“config.yaml”)
Application.put_env(:osq_simulator,:base_url,:proplist.get_value('base_url',config))


尽管根据我得到的一些其他反馈,克里斯·迈耶的回答似乎是我试图做的事情的“正确”方式。

我不确定这是否能解决你的问题,凯文,但你可能想看看魔兽世界,这是一个非常详细的答案,我怀疑里面有很多好东西。我得花点时间认真思考一下这个代码示例。非常感谢。回答得好!在这种情况下,由于他只是想在某个地方保持状态,所以他可以使用代理并避免GenServer中的许多样板文件。原因是:yaml文件适用于可能不知道如何重建Escript.Build正在创建的文件的最终用户。你认为我的用例是一个使用yaml有意义的地方,还是你仍然认为我应该在运行时寻找另一种配置应用程序的方式?为什么最终用户会修改yaml或任何其他配置?为他们提供一个良好的UI,以便进行任何需要进行的配置更改。最终用户不必知道任何有关底层实现机制的信息。事实上,如果你因为其他原因而不得不离开YAML,你会让你的最终用户变得更加困难。