1.需求

写一个基于memcache的cache模块, 需要在key前面加上特定的前缀, 所以user cache的原始的store函数应该写成

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

由于加前缀的操作(key_encode/1)是所有存入cache前必须要做的事, 所以我们可以考虑通过metaprogramming来定义一个行为叫before_store/2来做这件事,然后在put前hook before_store,但这会让代码非常难以理解。

我觉得更好的方法是在编译store/2期间去检查它的开始有没有执行过这个加前缀的encode函数, 这才能让让代码更容易理解。

所以我们的潜规则是在模块中的每一个函数的第一行,必须是Cache.key_encode/2

2. 使用@on_definition检查模块的每个函数第一行必须调用Cache.key_encode/2

我们接下来要使用@on_definition 在编译器去检查指定模块是不是符合这个自定义的潜规则。

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
# 这个是没有做key_encode的例子,应该编译不过
def store_comment(user_id, comment) do
Cache.put(user_id, comment)
end
end

看上面的我们定义了on_definition属性,接下来我们就来实现这个on_def/6

defmodule Cache do
# 这里只是用到了memcache_client做例子,你可以使用其它backend
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)
# 从body里面取出第一行,然后再check它的格式
expr = get_first_line(body)
IO.inspect expr
case expr do
:print_to_see_this_struct-> # 我们现在也不知道这东西是个什么东西,所以先用IO.inspect/1打出来看看,然后再对格式
:ok
_ ->
raise Cache.LacksEncodeError, message: "Function line#{line} must begin with a Cache.key_encode/2"
end
end

# 定义函数里使用的简略模式 def func, 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

我们也不知道第一行编成AST后会是什么样子,所以我们先把正确的格式给IO.inspect看一看。然后再匹配上去 :)

所以根据inspect的结果我们可以最后把check_start_with_key_encode/3写成:

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]},#就是它!
:key_encode]}, _,#就是它!
_}]} ->
:ok
_ ->
raise Cache.LacksEncodeError, message: "Function line#{line} must begin with a Cache.key_encode/2"
end
end

这里再运行mix compile就会得到

> 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

大功告成!

3. 结论:

@on_definition时会调用on_def/6所以我们可以在编译期间对每一个函数自定义你所需要的任何潜规则(但是也不要滥用哦:) )

4. Resources

Module docs  这里面还有其它的compile callback函数和选项,值得好好看看


[Elixir007] on_definition规范函数定义时的各种潜规则的更多相关文章

  1. js调用函数时传入的参数个数与函数定义时的参数个数不符时的操作

    在js中函数没有重载的概念,如果声明了多个重名的函数,不管函数的形参个数是否一样,只有最有一个有效,其他的函数声明都是无效的.比如说声明了两个函数fn(),第一次声明时没有形参,第二次声明时形参有两个 ...

  2. resful规范: 进行数据交换时的代码潜规则

    目前主流的三种web服务交互方案: REST (Representational State Transfer) 表征性状态转移 SOAP (Simple Object Access Protocol ...

  3. 9 - Python函数定义-位置参数-返回值

    目录 1 函数介绍 1.1 为什么要使用函数 1.2 Python中的函数 2 函数的基本使用 3 函数的参数 3.1 参数的默认值 3.2 可变参数 3.2.1 可变位置传参 3.2.2 可变关键字 ...

  4. Python 2.7 学习笔记 基本语法和函数定义

    本文介绍下python的基本语法 一.变量定义 不需要说明类型,也不需要像js等脚本语言使用var等标识符.直接声明即可,如: num=1 说明:上面语句声明了一个变量num,并在声明时初始化值为 1 ...

  5. JavaScript函数定义 ,参数调用

    一.JavaScript函数函数: 函数就是一种封装,由事件驱动的或者当它被调用时执行的可重复使用的代码块.定义函数:function 函数名(){函数体;}数不会自动执行,需要被调用才可以执行函数名 ...

  6. python3 参数*args 、 **args 在函数定义和调用中的应用

    一.函数调用时 说明:*args 表示解包(解包 列表.元组.字符串类型) #定义函数cn_musql def cn_musql(host,port,user,pwd,db): print(host) ...

  7. Python函数定义和使用

    函数是一段可以重复多次调用的代码,通过输入的参数值,返回需要的结果.通过使用函数,可以提高代码的重复利用率.本文主要介绍Python函数的定义.调用和函数参数设置方法. 函数的定义 Python函数定 ...

  8. ISO/IEC 9899:2011 条款6.9.1——函数定义

    6.9.1 函数定义 语法 1.function-definition: declaration-specifiers    declarator    declaration-listopt     ...

  9. Python18之函数定义及调用,注释

    一.函数定义 def 函数名(形参1,形参2...): 函数体 return 返回值         (可以返回任何东西,一个值,一个变量,或是另一个函数的返回值,如果函数没有返回值,可以省略retu ...

随机推荐

  1. Spark GraphX学习资料

    <Spark GraphX 大规模图计算和图挖掘> http://book.51cto.com/art/201408/450049.htm http://www.csdn.net/arti ...

  2. linux网络流量实时监控工具之iptraf

    这个工具还是很强大 linux网络流量实时监控工具之iptraf [我的Linux,让Linux更易用]IPTraf是一个网络监控工具,功能比nload更强大,可以监控所有的流量,IP流量,按协议分的 ...

  3. android view holder 优化

    android 一般都用viewholder来优化contentView,采用sparseArray能够进一步优化 /** * 用法: ImageView bananaView = ViewHolde ...

  4. 创建第一个Android 5.0应用程序

    1.新建一个Andriod项目,并配置相关参数 2.接着下一步 3.接着下一步,配置相关参数 4.完成后出现下面界面 5.运行此项目 6.你可以在菜单中看到刚才新建的Helloworld app图标

  5. 触发layoutSubviews的条件

    1. init初始化不会触发layoutSubviews 2. addSubview会触发layoutSubviews 3. 设置view的Frame会触发layoutSubviews,当然前提是fr ...

  6. UITableVIew 滚动流畅性优化

    影响UITableViewUITableView滚动的流畅性原因: 1. 在代理方法中做了过多的计算占用了 UI 线程的时间 2.同上 3.Cell 中 view 的组织复杂,比如使用layer并不会 ...

  7. WPF x名称空间

    X是映射XML名称空间时给他取的名字.X名称空间里面的成员是专门给XAML编译器看的.用来引导XAML编译器把XAML代码编译成CLR代码的,他是取的XAML的首字母X. XAML有自己的编译器,会吧 ...

  8. UVa 101 - The Blocks Problem(积木问题,指令操作)

    题目来源:https://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&category=3&pa ...

  9. virtualbox 中ubantu虚拟机范文win7文件夹

    宿主机操作系统: windows7_x64 旗舰版 Oracle VM virtualBox版本:5.0.12 虚拟机操作系统: Ubantu15.12_x64 Desktop版本   让虚拟机可以访 ...

  10. Hive安装与配置(靠谱亲测)

     Hive是hadoop生态环境的组成之一.通过Hive,可以使得直接用SQL操作HDFS.最大的好处就是让熟悉SQL,但是不了解JAVA的数据分析师使用.其机制就是一个将SQL语言转化为MapRed ...