【问题标题】:How to create "abstract" module in Elixir or "virtual" functions?如何在 Elixir 或“虚拟”函数中创建“抽象”模块?
【发布时间】:2016-06-24 01:26:39
【问题描述】:

我知道我在这里有点 Ruby 风格甚至 Java 风格,但这是我经常遇到的情况。

我想定义一些通用模块(可以使用元编程),它将使用将在“子”模块(可能是use这个通用模块)中指定的一些变量/函数。

我想是这样的:

defmodule Parent do
  defmacro __using__(_) do
    quote do
      def function do
        __MODULE__.other_function
      end
    end
  end
end

以后:

defmodule Child do
  use Parent

  def other_function do
    # some real work
  end
end

我经常需要“抽象”一些功能,最好的办法是定义一些可在“父”模块中访问的模块变量@var,但我知道它无论如何都不会那样工作。

有没有办法调用将在“包含”模块中定义的函数?

【问题讨论】:

  • 仔细考虑为什么要将 Elixir 转变为 OOP。这就是使用函数而不是对象的重点——随心所欲地使用它们。只需创建具有通用功能的模块并在需要的任何地方使用它,但不要进行继承。

标签: elixir


【解决方案1】:

我认为你在正确的轨道上。如果您添加一个@callback 来为“抽象”函数定义spec,那就更好了。我将用一个工作示例进行解释:

defmodule Fibonacci do
  @callback fib(number :: integer) :: integer

  defmacro __using__(_) do
      quote do
        @behaviour Fibonacci

        def fibonacci(n) when not is_integer(n), do: {:error, "Not integer"}
        def fibonacci(n) when n < 0, do: {:error, "Negative number"}
        def fibonacci(n), do: {:ok, __MODULE__.fib(n)}

        def fibonacci!(n) do
          case fibonacci(n) do
            {:error, reason} -> raise reason
            {:ok, n} -> n
          end
        end
      end
  end
end

每个使用Fibonacci 的模块都需要实现回调函数fib/1。此外,useFibonacci 模块的每个模块都将具有处理错误的函数 fibonacci/1 和引发错误的 fibonacci!/1

那么,让我们用直接递归来实现fib/1

defmodule Direct do
  use Fibonacci

  def fib(0), do: 0
  def fib(1), do: 1
  def fib(n), do: fib(n - 1) + fib(n - 2)
end

对于尾递归:

defmodule Tail do
  use Fibonacci

  def fib(0), do: 0
  def fib(1), do: 1
  def fib(n), do: fib(1, 1, n - 2)

  defp fib(_, value, 0), do: value
  defp fib(n_2, n_1, n), do: fib(n_1, n_2 + n_1, n - 1)
end

所以在iex 中我们可以调用不同的实现:

iex(1)> Direct.fibonacci(10)
{:ok, 55}
iex(2)> Tail.fibonacci!(10)
55
iex(3)> Tail.fibonacci!(-10)
** (RuntimeError) Negative number

另外,如果你忘记在你的模块中定义 fib/1 函数,那么编译器会警告你:

defmodule Warning do
  use Fibonacci
end

* warning: undefined behaviour function fib/1 (for behaviour Fibonacci)

如果您想使用此代码,只需点击此链接 http://elixirplayground.com?gist=606fe8c283443f03c7af08f85c888fe3

我希望这能回答你的问题。

【讨论】:

    【解决方案2】:

    可能有不止一种方法可以做到这一点,具体取决于您的具体用例。最简单的做法是只使用高阶函数,完全不使用元编程。

    defmodule Parent do
      def function(function_passed_as_variable) do
        function_passed_as_variable.()
      end
    end
    
    defmodule Child do
      def function do
        Parent.function(&other_function/1)
      end
      def other_function do
        #some real function
      end
    end
    

    我保留了名称 ParentChild 来引用您的代码,但在这种情况下它不适合。

    要实现多态性(一个函数根据数据做不同的事情),您可以使用模式匹配:

    def function({:first_type, data}), do: data*2
    def function({:second_type, data}), do: data+2
    def function(data), do: data
    

    还可以为不同的结构和其他数据类型定义协议。

    选择正确的模式可能并不容易,因此请随时评论更具体的示例。

    【讨论】:

    • 感谢 Tomek 的回复。我还在考虑将函数名称传递给use 中的opts 以动态构建“父”函数,该函数将从“子”模块调用此给定函数。
    • 我觉得可以直接传函数就不需要了。
    猜你喜欢
    • 2015-12-25
    • 1970-01-01
    • 2021-11-17
    • 1970-01-01
    • 2014-06-04
    • 2011-03-21
    • 2017-04-07
    • 1970-01-01
    • 2019-04-20
    相关资源
    最近更新 更多