Можно ли воссоздать математические функции Эрланга как макросы эликсира? - PullRequest
6 голосов
/ 06 марта 2019

Я нахожусь в процессе создания макроса, который будет вычислять расстояние между двумя наборами значений lat-long.

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

В данный момент это работает подобно обычной функции.Причина, по которой я хотел бы, чтобы он стал макросом, заключается в том, чтобы я мог использовать его в предложении guard.например,

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

Однако для макроса, который будет использоваться в предложении защиты, он должен " следовать правилам ".Это означает, что многие функции и операторы не могут использоваться.

Мой начальный макрос выглядел так ...

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.

Если бы я мог написать свою собственную версию этой функции как макрос, это решило бы проблему.

Кто-нибудь знает, возможно ли это?Если так, как я могу пойти по этому поводу?

1 Ответ

3 голосов
/ 06 марта 2019

Нет, это невозможно реализовать как защитный тест.

Или, возможно, если вы допустите потерю точности: это приближение функции синуса можетбыть реализован с использованием только операций, разрешенных в охране.

Но, скорее всего, точность вашей программы является более высоким приоритетом, чем сохранение нескольких строк кода.В этом случае я бы, вероятно, сделал бы свой вызов функции 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
...