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. 简析android消息模型

    android总结系列 一.消息系统构成要素和基本原理 l  消息队列 l  发送消息 l  消息读取 l  消息分发 l  消息循环线程 消息系统必须要依赖一个消息循环线程来轮询自己的消息队列,如果 ...

  2. Android项目实战(八):列表右侧边栏拼音展示效果

    之前忙着做项目,好久之前的技术都没有时间总结,而发现自己的博客好多写的技术都比自己掌握的时候晚了很多.不管怎么样,写技术博客一定是一个想成为优秀程序猿或者已经是优秀程序猿必须做的.好吧,下面进行学习阶 ...

  3. iOS开发 UIWebView+JavaScript 交互总结

    算是个人项目经验的,印象比较深的Web+JS交互的使用 iOS原生应用与Web页面元素交互方式有很多,JavaScriptCore.拦截协议.第三方框架WebViewJavaScriptBridge. ...

  4. IOS 杂笔-13(appearance的巧妙使用)

    在我们查看原生api时,我们不难发现,有些api的后面有着->UI_APPEARANCE_SELECTOR 那么我可以很高兴的说我们可以通过appearance对象来统一设置.十分巧妙. 例如: ...

  5. Swift开发第五篇——四个知识点(Struct Mutable方法&Tuple&autoclosure&Optional Chain)

    本篇分三部分: 一.Struct Mutable方法 二.多元组(Tuple) 的使用 三.autoclosure 的使用 四.Optional Chain 的使用 一.Struct Mutable方 ...

  6. NSString的八条实用技巧

    NSString的八条实用技巧 有一篇文章写了:iOS开发之NSString的几条实用技巧 , 今天这篇,我们讲讲NSString的八条实用技巧.大家可以收藏起来,方便开发随时可以复制粘贴. 0.首字 ...

  7. 一些在IOS中关于JS、H5开发的网站

    1.JSPatch 2.

  8. node.js之看懂package.json依赖库版本控制

    金天:学习一个新东西,就要持有拥抱的心态,如果固守在自己先前的概念体系,就会有举步维艰的感觉.node.js依赖库的版本控制 一般node.js项目会依赖大量第三方module, 那么如何控制modu ...

  9. uname

    uname uname用于打印操作系统和硬件架构相关的信息,对于可能在多个系统或架构上运行的Shell脚本程序很有用, 缺省选项相当于 -s 或--system $uname [-amnrsvpio] ...

  10. Backbone模型

    现在进入最关键的组件 - 模型.模型用来存储应用的所有数据,以及直接和数据操作相关的逻辑.Backbone中的模型类是Backbone.Model,它包含了数据存储,数据验证,以及数据发生变动时触发相 ...