Как я могу провести рефакторинг двух похожих функций, чтобы уменьшить дублирование кода в Elixir? - PullRequest
0 голосов
/ 23 марта 2020

У меня есть следующие две функции:

  defp select_previous_scheduled_price(scheduled_prices, date) do
    if length(scheduled_prices) > 1 do
      before_prices = Enum.filter(scheduled_prices, &starts_before(&1, date))

      if !Enum.empty?(before_prices) do
        hd(before_prices)
      else
        nil
      end
    else
      nil
    end
  end

  defp select_next_scheduled_price(scheduled_prices, date) do
    if length(scheduled_prices) >= 1 do
      after_prices = Enum.filter(scheduled_prices, &starts_after(&1, date))

      if !Enum.empty?(after_prices) do
        hd(after_prices)
      else
        nil
      end
    else
      nil
    end
  end

Есть два различия: 1. Операторы во второй строке (т. Е. > против >=); и 2. Функция, вызванная для фильтрации в третьей строке3 (т. е. &starts_before/2 против &starts_after/2)

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

Другими словами, я хотел бы сделать решение таким (только на самом деле функциональным, чего не будет):

  defp select_previous_scheduled_price(scheduled_prices, date) do
    select_scheduled_price(scheduled_prices, date, >, &starts_before/2)
  end

  defp select_next_scheduled_price(scheduled_prices, date) do
    select_scheduled_price(scheduled_prices, date, >=, &starts_after/2)
  end

  defp select_scheduled_price(scheduled_prices, date, meets_length_criteria, filter_criteria) do
    if meets_length_criteria(scheduled_prices, 1) do
      qualified_prices = Enum.filter(scheduled_prices, &filter_criteria(&1, date))

      if !Enum.empty?(qualified_prices) do
        hd(qualified_prices)
      else
        nil
      end
    else
      nil
    end
  end

Есть идеи, как сделать эту работу?

Спасибо!

Ответы [ 2 ]

1 голос
/ 23 марта 2020

List.first/1 вместо hd/1 исключит необходимость вложенного if в первую очередь. Затем я бы разбил код на более мелкие функции для уточнения цели.

defp if_prices(:before, prices, date),
  do: {length(prices) > 1, &starts_before(&1, date)}
defp if_prices(:after, prices, date),
  do: {length(prices) >= 1, &starts_after(&1, date)}

defp select_previous_scheduled_price(scheduled_prices, date),
  do: select_scheduled_price(:before, scheduled_prices, date)
defp select_next_scheduled_price(scheduled_prices, date),
  do: select_scheduled_price(:after, scheduled_prices, date)

defp select_scheduled_price(direction, prices, date) do
  case if_prices(direction, prices, date) do
    {false, _} -> nil
    {_, fun} -> 
      prices
      |> Enum.filter(fun)
      |> List.first()
  end
end
0 голосов
/ 23 марта 2020

Вот версия, очень похожая на вашу попытку. Он компилируется (но я не запускал его с реальными данными).

defp select_previous_scheduled_price(scheduled_prices, date) do
  select_scheduled_price(scheduled_prices, date, &>/2, &starts_before/2)
end

defp select_next_scheduled_price(scheduled_prices, date) do
  select_scheduled_price(scheduled_prices, date, &>=/2, &starts_after/2)
end

defp select_scheduled_price(scheduled_prices, date, meets_length_criteria, filter_criteria) do
  if meets_length_criteria.(scheduled_prices, 1) do
    scheduled_prices
    |> Enum.filter(&filter_criteria.(&1, date))
    |> List.first()
  else
    nil
  end
end

Значительные изменения:

  1. Для передачи бинарных операторов вы можете использовать & и /2 : >= становится &>=/2.
  2. Для вызова этих функций вам нужно использовать ..
  3. Я сделал if logi c more idiomati c.
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...