如何重构两个相似的函数以减少 Elixir 中的代码重复?

How can I refactor two similar functions to reduce the code duplication in Elixir?

我有以下两个功能:

  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 vs &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

关于如何使这项工作有任何想法吗?

谢谢!

List.first/1 instead of 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

这是一个与您尝试的版本非常相似的版本。它编译(但我没有运行它与真实数据)。

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 逻辑更加地道。