【问题标题】:Is it possible to recreate erlang's :math functions as elixir macros?是否可以将 erlang 的 :math 函数重新创建为 elixir 宏?
【发布时间】:2019-07-28 05:25:05
【问题描述】:

我正在创建一个宏来计算两组经纬度值之间的距离。

iex()> calc_distance(posA, posB)
2  # distance is in km

目前,它的工作方式类似于常规功能。我希望它成为宏的原因是我可以在保护子句中使用它。 例如

fn(posA, posB) when calc_distance(posA, posB) < 10 -> "close enough" end

但是,要在保护子句中使用宏,它必须是“follow the rules”。这意味着很多函数和运算符都不允许使用。

我最初的宏看起来是这样的......

defmacro calc_distance(ll1, ll2) do
  quote do
    lat1 = elem(unquote(ll1), 0)
    long1 = elem(unquote(ll1), 1)
    lat2 = elem(unquote(ll2), 0)
    long2 = elem(unquote(ll2), 1)

    v = :math.pi / 180
    r = 6372.8

    dlat  = :math.sin((lat2 - lat1) * v / 2)
    dlong = :math.sin((long2 - long1) * v / 2)
    a = dlat * dlat + dlong * dlong * :math.cos(lat1 * v) * :math.cos(lat2 * v)
    res = r * 2 * :math.asin(:math.sqrt(a))
    res
  end
end

我已经开始通过删除宏中定义的所有变量来使其“保护条款友好”。

defmacro calc_distance(ll1, ll2) do
  quote do
    :math.sin((elem(unquote(ll2), 1) - elem(unquote(ll1), 1)) * (3.141592653589793 / 180) / 2)
    |> square()
    |> Kernel.*(:math.cos(elem(unquote(ll1), 0) * (3.141592653589793 / 180)))
    |> Kernel.*(:math.cos(elem(unquote(ll2), 0) * (3.141592653589793 / 180)))
    |> Kernel.+(square(:math.sin((elem(unquote(ll2), 0) - elem(unquote(ll1), 0)) * (3.141592653589793 / 180) / 2)))
    |> :math.sqrt()
    |> :math.asin()
    |> Kernel.*(2)
    |> Kernel.*(6372.8)
  end
end

这仍然可以用作宏,但是当我尝试将其用作保护子句时仍然会出错,因为正在使用 :math 函数。

如果我可以将此函数的自己版本编写为宏,这将解决问题。

有人知道这是否可能吗?如果是这样,我该怎么办?

【问题讨论】:

    标签: erlang elixir metaprogramming


    【解决方案1】:

    不,不可能将此作为保护测试来实现。

    或者好吧,如果你考虑到准确性的损失,这是可能的:this approximation of the sine function 可以仅使用警卫中允许的操作来实现。

    但最有可能的是,在您的程序中,准确性比保存几行代码更重要。在这种情况下,我可能会调用我的函数 call_distance 并将结果作为参数传递给另一个函数,该函数可以对结果使用保护测试:

    def my_function(ll1, ll2) do
        my_function(ll1, ll2, calc_distance(ll1, ll2))
    end
    
    defp my_function(ll1, ll2, distance) when distance < 10 do
        "close enough"
    end
    defp my_function(ll1, ll2, distance) do
        "too far"
    end
    

    【讨论】:

    • 非常感谢您的回复。这是非常有帮助的。但是,我希望能够将此函数定义为宏的原因是因为我试图找出一种在 ets 表上运行复杂查询的方法。我希望将 lat_long 值列表存储在 ets 表中,并仅选择 x 距离内的值。据我所知,似乎唯一的方法是使用保护条款。您知道是否可以通过这种方式查询 ets 表。 ets表甚至是解决这个问题的好方法吗?再次感谢
    • 对,对于查询 ets 表,您可以将 match specification 传递给 ets:select,这将受到与警卫测试类似的限制 - 也许使用正弦近似值会很有用在那里做什么?或者也许使用ets:foldl 来收集匹配的条目(使用math 函数)就足够快了。这里有一个性能/准确性的权衡,我想同时尝试和测量是选择合适方法的方法。
    猜你喜欢
    • 2016-05-05
    • 2011-01-07
    • 1970-01-01
    • 2022-01-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-07-07
    • 1970-01-01
    相关资源
    最近更新 更多