[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 ...
随机推荐
- Effective Java 阅读笔记——方法
38:检查参数的有效性 每当编写方法或者构造器的时候,应该考虑它的参数有哪些限制,在方法的开头处对参数进行检查,并且把这些限制写入文档. 注意: 对于公有方法,应该使用@throws标签在文档中说明违 ...
- 【转】IOS中的release和nil
nil和release的作用: nil就是把一个对象的指针置为空,只是切断了指针与内存中对象的联系:而release才是真正通知内存释放这个对象. 所以nil并没有释放内存,只有release才回真正 ...
- 朝花夕拾-android 获取当前手机的内存卡状态和网络连接状态
序言: 人的一生是一个选择的过程. 如果脚下只有一条路,只要一往无前即可,不用担心走错.即使是错也别无它法.然而人是不安分的,况且安于独木桥的行走,其目的地由于没有蜿蜒曲折去遮挡行路人的视线,一往无前 ...
- 利用JS实现手机访问PC网址自动跳转到wap网站
方法一:使用百度siteapp中的js进行判断 <script src="http://siteapp.baidu.com/static/webappservice/uaredirec ...
- 【Android】 Android实现录音、播音、录制视频功能
智能手机操作系统IOS与Android平分天下(PS:WP与其他的直接无视了),而Android的免费招来了一大堆厂商分分向Android示好,故Android可能会有“较好”的前景. Android ...
- QT的QWebView显示网页不全
最近使用QWebView控件遇到一个问题,就是无论窗口多大,网页都显示那么大,而且,显示不完全,有滚动条 试过使用showMaximized()方法, 还是一样,网上一直说是布局问题,也没说清楚是虾米 ...
- HTML实体对照表
HTML开发特殊字符是没办法原样输出的,必须用到实体,为了以后查看方便,收藏一下实体对照表是必要的,另外,使用<xmp></xmp>标签可以原样输出,当然,也包括特殊字符啦! ...
- 2012年 蓝桥杯预赛 java 本科 题目
2012年 蓝桥杯预赛 java 本科 考生须知: l 考试时间为4小时. l 参赛选手切勿修改机器自动生成的[考生文件夹]的名称或删除任何自动生成的文件或目录,否则会干扰考试系统正确采集您的解答 ...
- hdu 4607 Park Visit 求树的直径
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=4607 题目大意:给你n个点,n-1条边,将图连成一棵生成树,问你从任意点为起点,走k(k<=n) ...
- Swift 语法笔记01
Swift 好多新奇的地方啊...妈的 var display: int { get() set() } Tuple: let x: (d:Double, e:String, f:Int) = (3. ...