lib/reverse_match.ex

defmodule ReverseMatch do
  @moduledoc """
  Suport reverse match to help write more clear code.

  But we do have `=`, why need a reverse one?
  Let's see some snips:

  ```elixir
  defp serve(socket) do
    socket
    |> read_line()
    |> write_line(socket)

    serve(socket)
  end
  ```
  This is snipe from the [Task and gen_tcp](https://elixir-lang.org/getting-started/mix-otp/task-and-gen-tcp.html).
  Why the last line do not use `|>`? Because `write_line/2` do not
  return socket, and `serve/1` want it.
  The pipele is broken.

  In the pipeline, if the upstream function return value is not the
  downstream function's first required, the pipeline will be broken.
  With the help of `tr`, we can transform the value from the upstream to
  what the next function required, like this:
  ```
  import ReverseMatch
  defp server(socket) do
    socket
    |> read_line()
    |> write_line(socket)
    |> tr(_, do: socket)
    |> server
  end
  ```
  """

  @doc """
  Transform the `value` to `new_form`.

  Just as `tr(value, new_form)` same as `new_form = value`.
  ## Examples tr(value,new_from)
  ```
  iex>import ReverseMatch
  iex>ab = [a: 1, b: 2]
  iex>tr(ab, [a: a, b: _])
  iex>a
  1
  ```

  After binding variables in `new_form`, we can do more wrok in the `do_block`,
  and the the variables in the `new_from` at the same scope as this `do_block`.

  ## Examples tr(value,new_form, do: more)
  ```
  iex>import ReverseMatch
  iex>Agent.start_link(fn ->10 end) |>
  iex> tr({:ok,pid}, do: pid) |>
  iex> Agent.get(&(&1))
  10
  iex>Agent.stop(pid)
  :ok
  ```
  ## Returns
  If there is `do_more` block, the value of the `do_more` will return,
  else the `vlaue` return.
  """
  defmacro tr(value, new_form, do_more \\ nil) do
    match_ast =
      quote do
        unquote(new_form) = unquote(value)
      end

    if do_more != nil do
      quote do
        unquote(match_ast)
        unquote(do_more[:do])
      end
    else
      match_ast
    end
  end

  @doc """
  Return a zero argument function, it always return the `value`.

  ## Examples
  ```
  iex>import ReverseMatch
  iex>{:ok,pid} = Agent.start_link(const(10))
  iex>Agent.get(pid,&(&1))
  10
  ```
  """
  def const(value), do: fn -> value end

  @doc """
  Just short the name of `Function.identity/1`
  ## Examples
  ```
  iex>import ReverseMatch
  iex>id(1)
  1
  ```
  """
  defdelegate(id(value), to: Function, as: :identity)
end