lib/interval/intervalable.ex

defprotocol Interval.Intervalable do
  @moduledoc """
  Implementing this protocol allows `Interval` to infer the implementation
  to use for a value, which allows you to pass the values to `Interval.new/1`
  without specifying a `module` option:

      iex> Interval.new(left: 1, right: 2)

  Instead of

      iex> Interval.new(module: Interval.Integer, left: 1, right: 2)
      # or equivalent
      iex> Interval.Integer.new(left: 1, right: 2)

  This functionality is purely for ergonomic reasons,
  and is not required to be able to define custom intervals, or to use
  the builtin ones.

  Explicitly calling `Interval.Integer.new/1` could be preferred in certain
  situations because you know exactly what implementation you are getting.
  """

  @doc "Return the Interval.Behaviour implementation to use for this value type"
  def infer_implementation(value)
end

if Application.get_env(:interval, Interval.Integer, true) do
  defimpl Interval.Intervalable, for: Integer do
    def infer_implementation(value) when is_integer(value), do: Interval.Integer
  end
end

if Application.get_env(:interval, Interval.Float, true) do
  defimpl Interval.Intervalable, for: Float do
    def infer_implementation(value) when is_float(value), do: Interval.Float
  end
end

if Application.get_env(:interval, Interval.Date, true) do
  defimpl Interval.Intervalable, for: Date do
    def infer_implementation(value) when is_struct(value, Date), do: Interval.Date
  end
end

if Application.get_env(:interval, Interval.DateTime, true) do
  defimpl Interval.Intervalable, for: DateTime do
    def infer_implementation(value) when is_struct(value, DateTime), do: Interval.DateTime
  end
end

if Application.get_env(:interval, Interval.Decimal, true) do
  defimpl Interval.Intervalable, for: Decimal do
    def infer_implementation(value) when is_struct(value, Decimal), do: Interval.Decimal
  end
end