[Elixir007] on_definition规范函数定义时的各种潜规则
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规范函数定义时的各种潜规则的更多相关文章
- js调用函数时传入的参数个数与函数定义时的参数个数不符时的操作
在js中函数没有重载的概念,如果声明了多个重名的函数,不管函数的形参个数是否一样,只有最有一个有效,其他的函数声明都是无效的.比如说声明了两个函数fn(),第一次声明时没有形参,第二次声明时形参有两个 ...
- resful规范: 进行数据交换时的代码潜规则
目前主流的三种web服务交互方案: REST (Representational State Transfer) 表征性状态转移 SOAP (Simple Object Access Protocol ...
- 9 - Python函数定义-位置参数-返回值
目录 1 函数介绍 1.1 为什么要使用函数 1.2 Python中的函数 2 函数的基本使用 3 函数的参数 3.1 参数的默认值 3.2 可变参数 3.2.1 可变位置传参 3.2.2 可变关键字 ...
- Python 2.7 学习笔记 基本语法和函数定义
本文介绍下python的基本语法 一.变量定义 不需要说明类型,也不需要像js等脚本语言使用var等标识符.直接声明即可,如: num=1 说明:上面语句声明了一个变量num,并在声明时初始化值为 1 ...
- JavaScript函数定义 ,参数调用
一.JavaScript函数函数: 函数就是一种封装,由事件驱动的或者当它被调用时执行的可重复使用的代码块.定义函数:function 函数名(){函数体;}数不会自动执行,需要被调用才可以执行函数名 ...
- python3 参数*args 、 **args 在函数定义和调用中的应用
一.函数调用时 说明:*args 表示解包(解包 列表.元组.字符串类型) #定义函数cn_musql def cn_musql(host,port,user,pwd,db): print(host) ...
- Python函数定义和使用
函数是一段可以重复多次调用的代码,通过输入的参数值,返回需要的结果.通过使用函数,可以提高代码的重复利用率.本文主要介绍Python函数的定义.调用和函数参数设置方法. 函数的定义 Python函数定 ...
- ISO/IEC 9899:2011 条款6.9.1——函数定义
6.9.1 函数定义 语法 1.function-definition: declaration-specifiers declarator declaration-listopt ...
- Python18之函数定义及调用,注释
一.函数定义 def 函数名(形参1,形参2...): 函数体 return 返回值 (可以返回任何东西,一个值,一个变量,或是另一个函数的返回值,如果函数没有返回值,可以省略retu ...
随机推荐
- FIJ Jobs – 2013/8/12
Department Vacancies Total Skill Set Experience Language Systems Systems Coordinator 1 Communication ...
- 独立博客开张!有关读书、GTD和IT方面的内容将发布在新网站上
2015年自己建个独立博客http://www.shenlongbin.com,以后与读书.GTD和IT技术有关的主题都放在个人博客中,2015年计划基本制定,请移步到这里. 感谢博客园提供了如此优秀 ...
- Sharepoint学习笔记—习题系列--70-573习题解析 -(Q118-Q120)
Question 118You are creating a Business Connectivity Services (BCS) entity.You need to ensure that a ...
- android 短信助手demo
关于意图Intent: 显式意图:必须指定要激活的组件的完整包名和类名(应用程序之间耦合在一起) 一般激活自己应用的组件的时候采用显式意图 隐式意图:只需要指定动作和数据就可以(好处是应用程序之间没有 ...
- MAC OS Finder 中快速定位指定路径
在看一些 tip 文章的时候,时不时会有需要进到某某目录替换文件之类的步骤.如果碰上这个目录层次够多,一层一层的点击既麻烦又容易出错,有什么快捷的办法呢? 快捷键:Shift + Command + ...
- 代码校验工具 SublimeLinter 的安装与使用
SublimeLinter 是 Sublime 的插件,它的作用是检查代码语法是否有错误,并提示.习惯了 IDE 下写代码的人一定需要一款在 Sublime 上类似的语法检查工具.下面我们开始. 安装 ...
- Android Design Support Library——Snackbar
Snackbar是一个轻量级控件,它可以很方便的提供消息的提示和动作反馈,类似于Toast.Snackbar包括一段文字信息与一个可选的操作按钮,超时自动隐藏,也可以通过滑动来删除.效果如下所示: S ...
- MONGODB(三)——Java操作Mongo
相比于java调用MySqlApI来操作数据库,调用Mongo要简洁容易的多.通过一个简单的样例,很容易地就可以上手 一.导入Jar包 添加Monog支持Java的jar包,这里使用的是2.9.3 & ...
- 第一篇 UEditor入门部署和体验
UEditor 是由百度「FEX前端研发团队」开发的所见即所得富文本web编辑器,具有轻量,可定制,注重用户体验等特点,开源基于MIT协议,允许自由使用和修改代码. UEditor富文本编辑器,轻量, ...
- 详解xml文件描述,读取方法以及将对象存放到xml文档中,并按照指定的特征寻找的方案
主要的几个功能: 1.完成多条Emp信息的XML描述2.读取XML文档解析Emp信息3.将Emp(存放在List中)对象转换为XML文档4.在XML文档中查找指定特征的Emp信息 dom4j,jaxe ...