使用理解的模板化功能

时间:2018-04-28 20:01:23

标签: elixir metaprogramming

假设我们有以下实现:

defmodule SomeModule do
  @celcius_coefficient 800
  @rheinan_coefficient 900
  @fahrenheit_coefficient 1000

  @available_units [:celcius, :rheinan, :fahrenheit]

  def foo(some_struct) do
    some_var = get_var(some_struct, :var_name)

    if some_var == :kelvin do
      initialize(some_struct)
    else
      bar(some_struct, some_var)
    end
  end

  defp bar(struct, :celcius) do
    mult(struct, @celcius_coefficient)
  end
  defp bar(struct, :rheinan) do
    mult(struct, @rheinan_coefficient)
  end
  defp bar(struct, :fahrenheit) do
    mult(struct, @fahrenheit_coefficient)
  end
end

有一些以*_coefficient开头的模块属性,其中*是一个表示内容的关键字。我们正在实施bar/2以匹配所有可用子句。实现很麻烦,当我们需要添加一个新单元时,我们需要实现一个新函数来匹配新单元,这个单元将由同一个表达式进行评估。

An implementation in Comeonin library使用理解来为所有子句生成函数。我建议采用以下方法:

defmodule SomeModule do
  @celcius_coefficient 800
  @rheinan_coefficient 900
  @fahrenheit_coefficient 1000

  @available_units [:celcius, :rheinan, :fahrenheit]

  def foo(some_struct) do
    some_var = get_var(some_struct, :var_name)

    if some_var == :kelvin do
      initialize(some_struct)
    else
      bar(some_struct, some_var)
    end
  end

  for unit <- @available_units do
    defp bar(struct, unquote(unit)) do
      coefficient = unquote(Module.get_attribute(__MODULE__, :"#{Atom.to_string(unquote(unit))}_coefficient"))

      mult(struct, coefficient)
    end
  end
end

在编译期间应使用Module.get_attribute/2检索模块属性,因为它在运行时不可用。 我们还需要取消引用unit参数,以便为函数提供正确的原子。

它抛出一个编译错误,说:unquote called outside quote。 试图在某些地方放置quote条款,没有任何帮助。

我想我对Elixir中的元编程有一些误解和混淆。

你有什么建议吗?

我还处于使用defpdefmacrop来定义bar/2的两难境地。做这种事情的正确结构是什么?

谢谢。

1 个答案:

答案 0 :(得分:1)

您不需要在内部unit值周围取消引用,因为它已经没有引用:

coefficient = unquote(
  Module.get_attribute(__MODULE__, :"#{Atom.to_string(unit)}_coefficient")
)

程序:

defmodule SomeModule do
  @celcius_coefficient 800
  @rheinan_coefficient 900
  @fahrenheit_coefficient 1000

  @available_units [:celcius, :rheinan, :fahrenheit]

  for unit <- @available_units do
    def bar(struct, unquote(unit)) do
      coefficient = unquote(Module.get_attribute(__MODULE__, :"#{Atom.to_string(unit)}_coefficient"))
      {struct, coefficient}
    end
  end
end

IO.inspect SomeModule.bar(1, :celcius)
IO.inspect SomeModule.bar(2, :fahrenheit)

输出:

{1, 800}
{2, 1000}

(我将该功能公开用于测试,如果您不想在此模块之外使用它,可以再次将其设为私有)

  

我使用defp或defmacrop定义bar / 2也处于两难境地。做这种事情的正确结构是什么?

仅在需要将此功能用作宏时才定义宏。在这种情况下,您不需要,因此defp(或def)没问题。