如何在Ecto中存储Elixir功能?

时间:2015-12-04 02:30:30

标签: elixir ecto

我希望有一个数据库表,其中有一个' action'领域。我想在现场存储Elixir功能。这可能吗?

该功能始终只接受一个参数。

2 个答案:

答案 0 :(得分:2)

这是我过去使用的Ecto类型,与上面评论中链接的@AbM非常类似:

defmodule ErlangETF do
  def type, do: :binary

  def cast(binary = << 131, _ :: binary >>) do
    try do
      {:ok, :erlang.binary_to_term(binary)}
    catch
      _ ->
        {:ok, binary}
    end
  end
  def cast(any), do: {:ok, any}

  def load(any), do: cast(any)

  def dump(any), do: {:ok, :erlang.term_to_binary(any)}
end

模型的架构定义可能如下所示:

defmodule Example do
  use Ecto.Model

  schema "examples" do
    field :function, ErlangETF
  end
end

如果你想进一步限制允许的术语类型(例如,只允许arity为1的函数),这样的事情可能有效:

defmodule ErlangFunctionArity1ETF do
  def type, do: :binary

  def cast(binary = << 131, _ :: binary >>) do
    try do
      case :erlang.binary_to_term(binary) do
        function when is_function(function, 1) ->
          {:ok, function}
        _ ->
          {:ok, binary}
      end
    catch
      _ ->
        {:ok, binary}
    end
  end
  def cast(any), do: {:ok, any}

  def load(any), do: cast(any)

  def dump(function) when is_function(function, 1),
    do: {:ok, :erlang.term_to_binary(function)}
  def dump(_),
    do: {:ok, nil}
end

您可以使用外部术语格式存储两种格式的函数:

  1. EXPORT_EXT
  2. NEW_FUN_EXT
  3. 以下是EXPORT_EXT的示例以及生成的字节大小:

    iex> (&Enum.sum/1) |> ErlangFunctionArity1ETF.dump |> elem(1) |> byte_size
    24
    

    以下是NEW_FUN_EXT的示例以及生成的字节大小:

    iex> (fn c -> Enum.sum(c) end) |> ErlangFunctionArity1ETF.dump |> elem(1) |> byte_size
    228
    

    我提到字节大小的差异,以防存储大小完全是个问题。

    EXPORT_EXT基本上只存储2个原子和1个整数(:Enum:sum1),这就是序列化函数的大小如此之小的原因。匿名函数被序列化为NEW_FUN_EXT,它存储了所有函数代码以及有关其创建的信息。

答案 1 :(得分:0)

这是一种将函数序列化为字符串并反序列化的快速方法。

iex(1)> defmodule Test do
...(1)> def test(num), do: {:ok, num}
...(1)> end
{:module, Test,
 <<70, 79, 82, 49, 0, 0, 4, 224, 66, 69, 65, 77, 69, 120, 68, 99, 0, 0, 0, 147,
   131, 104, 2, 100, 0, 14, 101, 108, 105, 120, 105, 114, 95, 100, 111, 99, 115,
   95, 118, 49, 108, 0, 0, 0, 4, 104, 2, ...>>, {:test, 1}}
iex(2)> save = "#{Test} #{:test}"
"Elixir.Test test"
iex(3)> [mod, fun] = String.split(save, " ") |> Enum.map(&String.to_existing_atom/1)
[Test, :test]
iex(4)> apply mod, fun, [10]
{:ok, 10}