Various latent rules in the definition of [Elixir007] on_definition gauge function

Keywords: Erlang Attribute

1. demand

Writing a memcache-based cache module requires a specific prefix before the key, so the original store function of user cache should be written as

# user.ex
def
store(user_id, value) do key
= Cache.key_encode(user_id, :user)
...
end

Since the prefixed operation (key_encode/1) is all that must be done before caching, we can consider using metaprogramming to define a behavior called before_store/2 to do this, and then hook before_store before put ting, which makes the code very difficult to understand.

I think it's better to check that the prefixed encode function has been executed at the beginning of the store/2 compilation to make the code easier to understand.

So our underlying rule is that the first line of each function in the module must be Cache.key_encode/2.

2. Use @on_definition to check that the first line of each function in the module must call Cache.key_encode/2

We're going to use it next. @on_definition Check in the compiler whether the specified module conforms to this customized latent rule.

mix new on_definition_play
cd on_definition_play
# lib/user.ex
defmodule User do
  @on_definition {Cache.Enforcement, :on_def}

  def store_user(user_id, user) do
    key = Cache.key_encode(user_id, :user)
    Cache.put(key, user)
  end
  # This is not done. key_encode For example, it should be compiled but
  def store_comment(user_id, comment) do
    Cache.put(user_id, comment)
  end
end

Look at the definition above. on_definition Attribute, and then we'll implement this on_def/6

defmodule Cache do
  # Here's just an example of memcache_client. You can use other backend s.
  def put(key, value) do
    Memcache.Client.put(key, value)
  end
  def get(key)  do
    Memcache.Client.get(key)
  end

  def key_encode(key, prefix) do
    "#{prefix}:#{inspect key}"
  end

  defmodule Enforcement do
    def on_def(env, _kind, _name, args, _guards, body) do
      check_start_with_key_encode(env, args, body)
    end

    defp check_start_with_key_encode(_env, [{_, meta, _} | _args], body) do
      line = Keyword.get(meta, :line)
# Take the first line out of the body and check its format expr
= get_first_line(body) IO.inspect expr case expr do :print_to_see_this_struct-> # We don't know what it is now, so let's use it first. IO.inspect/1 Type it out and see, and then format it. :ok _ -> raise Cache.LacksEncodeError, message: "Function line#{line} must begin with a Cache.key_encode/2" end end
# Define the schema def func used in the function, do: defp
get_first_line({:__block__, _, expr_list}) do List.first(expr_list)
end defp get_first_line(expr) do expr end end defmodule LacksEncodeError do defexception [:message] end end

We don't know what the first line will look like in AST, so let's give IO.inspect the correct format first. Then match it up:)

So based on inspect's results, we can finally write check_start_with_key_encode/3 as follows:

defp check_start_with_key_encode(_env, [{_, meta, _} | _args], body) do
      line = Keyword.get(meta, :line)
      expr = get_first_line(body)
      case expr do
       {:=, _,
         [{_, _, _},
          {{:., _,
          [{:__aliases__, _, [:Cache]},#That's it!
           :key_encode]}, _,#That's it!
            _}]} ->
          :ok
       _ ->
        raise Cache.LacksEncodeError, message: "Function line#{line} must begin with a Cache.key_encode/2"
      end
    end

If you run mix compile here, you will get

> mix compile
== Compilation error on file lib/user.ex ==
** (Cache.LacksEncodeError) Function line9 must begin with a Cache.key_encode/2
    lib/cache.ex:31: Cache.Enforcement.check_start_with_key_encode/3
    (stdlib) erl_eval.erl:669: :erl_eval.do_apply/6

Be accomplished!

3. conclusion:

@ on_definition calls on_def/6, so we can customize any underlying rules you need for each function during compilation (but don't abuse them either.)

4. Resources

Module docs There are other compile callback functions and options that deserve a good look

 

Posted by seanpaulcail on Wed, 20 Mar 2019 20:00:28 -0700