当前位置: 首页 > 编程日记 > 正文

[elixir! #0007] [译] 理解Elixir中的宏——part.5 重塑AST by Saša Jurić

上一章我们提出了一个基本版的deftraceable宏,能让我们编写可跟踪的函数。宏的最终版本有一些剩余的问题,今天我们将解决其中的一个——参数模式匹配。

今天的练习表明我们必须仔细考虑宏可能接收到的输入。

问题

正如我上一次暗示的那样,当前版本的deftraceable不适用于模式匹配的参数。让我们来演示一下这个问题:

iex(1)> defmodule Tracer do ... endiex(2)> defmodule Test doimport Tracerdeftraceable div(_, 0), do: :errorend
** (CompileError) iex:5: unbound variable _

发生了什么?deftraceable宏盲目地将输入的参数当做是纯变量或常量。因此,当你调用deftraceable div (a, b), do: …生成的代码会包含:

passed_args = [a, b] |> Enum.map(&inspect/1) |> Enum.join(",")

这将按预期工作,但如果一个参数是匿名变量(_),那么我们将生成以下代码:

passed_args = [_, 0] |> Enum.map(&inspect/1) |> Enum.join(",")

这显然是不正确的,而且我们因此得到了未绑定变量的错误。

那么如何解决呢?我们不应该就输入参数做任何假设。相反,我们应该将每个参数转换为由宏生成的专用变量。如果我们的宏被调用,那么:

deftraceable fun(pattern1, pattern2, ...)

我们应该生成函数头:

def fun(pattern1 = arg1, pattern2 = arg2, ...)

这允许我们将参数值接收到我们的内部临时变量中,并打印这些变量的内容。

解决方法

让我们开始实现。首先,我将向你展示解决方案的顶层草图:

defmacro deftraceable(head, body) do{fun_name, args_ast} = name_and_args(head)# Decorates input args by adding "= argX" to each argument.# Also returns a list of argument names (arg1, arg2, ...){arg_names, decorated_args} = decorate_args(args_ast)head = ??   # Replace original args with decorated onesquote dodef unquote(head) do... # unchanged# Use temp variables to make a trace messagepassed_args = unquote(arg_names) |> Enum.map(&inspect/1) |> Enum.join(",")... # unchangedendend
end

首先,我们从头中提取名称和参数(我们在前一篇文章中已经解决了)。然后,我们必须在args_ast中注入= argX,并取回修改过的args(我们会将其放入decorated_args)。

我们还需要生成的变量的纯名称(或者更确切地说,它们的AST),因为我们将使用这些变量来收集参数值。变量arg_names本质上包含quote do [arg_1, arg_2, …] end,可以很容易地注入到语法树中。

现在让我们实现其余的。首先,让我们看看如何装饰参数:

defp decorate_args(args_ast) dofor {arg_ast, index} <- Enum.with_index(args_ast) do# Dynamically generate quoted identifierarg_name = Macro.var(:"arg#{index}", __MODULE__)# Generate AST for patternX = argXfull_arg = quote dounquote(arg_ast) = unquote(arg_name)end{arg_name, full_arg}end|> List.unzip|> List.to_tuple
end

大多数操作发生在for语句中。本质上,我们经过了每个变量输入的AST片段,然后使用Macro.var/2函数计算临时名称(引用的argX),它能将一个原子变换成一个名称与其相同的引用的变量。Macro.var/2的第二个参数确保变量是卫生的。尽管我们将arg1,arg2,…变量注入到调用者上下文中,但调用者不会看到这些变量。事实上,deftraceable的用户可以自由地使用这些名称作为一些局部变量,不会干扰我们的宏引入的临时变量。

最后,在语境结束时,我们返回一个由temp的名称和引用的完整模式——(例如_ = arg10 = arg2)所组成的元组。在最后使用unzipto_tuple确保了decorate_args{arg_names, decorated_args}的形式返回结果。

有了decorated_argshelper,我们可以传递输入参数,获得修饰好的值,包含临时变量的名称。现在我们需要将这些修饰好的参数插入函数的头部,替换掉原始的参数。特别地,我们必须执行以下步骤:

  1. 递归遍历输入函数头的AST。

  2. 查找指定函数名和参数的位置。

  3. 将原始(输入)参数替换为修饰好的参数的AST

如果我们使用Macro.postwalk/2函数,这个任务就可以合理地简化:

defmacro deftraceable(head, body) do{fun_name, args_ast} = name_and_args(head){arg_names, decorated_args} = decorate_args(args_ast)# 1. Walk recursively through the ASThead = Macro.postwalk(head,# This lambda is called for each element in the input AST and# has a chance of returning alternative ASTfn# 2. Pattern match the place where function name and arguments are# specified({fun_ast, context, old_args}) when (fun_ast == fun_name and old_args == args_ast) -># 3. Replace input arguments with the AST of decorated arguments{fun_ast, context, decorated_args}# Some other element in the head AST (probably a guard)#   -> we just leave it unchanged(other) -> otherend)... # unchanged
end

Macro.postwalk/2递归地遍历AST,并且在所有节点的后代被访问之后,调用为每个节点提供的lambda。lambda接收元素的AST,这样我们有机会返回一些除了那个节点之外的东西。

我们在这个lambda里做的基本上是一个模式匹配,我们在寻找{fun_name, context, args}。如第三章中所述,这是表达式some_fun(arg1, arg2, …)的引用表示。一旦我们遇到匹配此模式的节点,我们只需要用新的(修饰的)输入参数替换掉旧的。在所有其它情况下,我们简单地返回输入的AST,使得树的其余部分不变。

这有点复杂,但它解决了我们的问题。以下是追踪宏的最终版本:

defmodule Tracer dodefmacro deftraceable(head, body) do{fun_name, args_ast} = name_and_args(head){arg_names, decorated_args} = decorate_args(args_ast)head = Macro.postwalk(head,fn({fun_ast, context, old_args}) when (fun_ast == fun_name and old_args == args_ast) ->{fun_ast, context, decorated_args}(other) -> otherend)quote dodef unquote(head) dofile = __ENV__.fileline = __ENV__.linemodule = __ENV__.modulefunction_name = unquote(fun_name)passed_args = unquote(arg_names) |> Enum.map(&inspect/1) |> Enum.join(",")result = unquote(body[:do])loc = "#{file}(line #{line})"call = "#{module}.#{function_name}(#{passed_args}) = #{inspect result}"IO.puts "#{loc} #{call}"resultendendenddefp name_and_args({:when, _, [short_head | _]}) doname_and_args(short_head)enddefp name_and_args(short_head) doMacro.decompose_call(short_head)enddefp decorate_args([]), do: {[],[]}defp decorate_args(args_ast) dofor {arg_ast, index} <- Enum.with_index(args_ast) do# dynamically generate quoted identifierarg_name = Macro.var(:"arg#{index}", __MODULE__)# generate AST for patternX = argXfull_arg = quote dounquote(arg_ast) = unquote(arg_name)end{arg_name, full_arg}end|> List.unzip|> List.to_tupleend
end

来试验一下:

iex(1)> defmodule Tracer do ... endiex(2)> defmodule Test doimport Tracerdeftraceable div(_, 0), do: :errordeftraceable div(a, b), do: a/bendiex(3)> Test.div(5, 2)
iex(line 6) Elixir.Test.div(5,2) = 2.5iex(4)> Test.div(5, 0)
iex(line 5) Elixir.Test.div(5,0) = :error

正如你看到的,进入AST,把它分开,然后注入一些自定义的代码,这是可能的,而且不是非常复杂。缺点就是,生成宏的代码变得越来越复杂,并且更难分析。

本章到此结束。下一章我将讨论现场生成代码。

Copyright 2014, Saša Jurić. This article is licensed under a Creative Commons Attribution-NonCommercial 4.0 International License.
The article was first published on the old version of the Erlangelist site.
The source of the article can be found here.

相关文章:

vue-cli3环境变量与分环境打包

第一步 : 了解环境变量概念 我们可以根目录中的下列文件来指定环境变量: .env # 在所有的环境中被载入 .env.local # 在所有的环境中被载入&#xff0c;但会被 git 忽略 .env.[mode] # 只在指定的模式中被载入 .env.[mode].local # 只在指定…

SLAM闭合回环————视觉词典BOW小结

在目前实际的视觉SLAM中&#xff0c;闭环检测多采用DBOW2模型https://github.com/dorian3d/DBoW2&#xff0c;而bag of words 又运用了数据挖掘的K-means聚类算法&#xff0c;笔者只通过bag of words 模型用在图像处理中进行形象讲解&#xff0c;并没有涉及太多对SLAM的闭环检测…

【Codeforces】53D Physical Education (有点像冒泡)

http://codeforces.com/problemset/problem/53/D 从上面所给的序列变成下面所给的序列 交换的时候只能交换相邻的两个数字 输出每一步的交换方法&#xff0c;输出的是该元素在序列中的位置&#xff08;第一个数的位置是1&#xff09; 不要求输出步数最少的那一种方法 当同…

js脚本冷知识

js中有个很恶心的写法。比如这个var adsf(function(){})();这样的写法&#xff0c;主要是原生的&#xff0c;能在dom元素加载完之后实现自动加载这个脚本文件到里面去。可以验证这个&#xff08;function(){.......&#xff09;&#xff08;&#xff09;;在.......里面可以直接…

Python中is同一性运算符和==相等运算符区别

2019独角兽企业重金招聘Python工程师标准>>> 在区分is和这两种运算符区别之前&#xff0c;需要知道Python中对象包含的三个基本要素&#xff0c;分别是&#xff1a;id(身份标识)、type(数据类型)和value(值)。 比较对象的value(值) 是python标准操作符中的比较操作符…

C++实现十大排序算法(冒泡,选择,插入,归并,快速,堆,希尔,桶,计数,基数)排序算法时间复杂度、空间复杂度、稳定性比较(面试经验总结)

排序算法分类 内部排序算法又分为基于比较的排序算法和不基于比较的排序算法&#xff0c;其分类如下&#xff1a; 比较排序&#xff1a; 直接插入排序 希尔排序 &#xff08;插入&#xff09; 冒泡排序 快速排序 (交换) 直接选择排序 堆排序&#xff08;选择&#…

32位处理器是什么意思

问题描述&#xff1a;朋友那个32位处理器&#xff0c;2的32次方算出来的单位是不是应该是4294967296位&#xff08;bit&#xff09;吧&#xff0c;怎么就成字节了呢&#xff1f;单位错了&#xff0c;那个32位是指32位地址总线&#xff0c;而不是32位数据。地址的数量单位是个&a…

【Codeforces】913C Party Lemonade (贪...)。

http://codeforces.com/contest/913/problem/C 这个题和以前见过的有点不一样&#xff0c;可以重复选择&#xff0c;这个有点emmm 首先将a数组优化&#xff0c;举个例子&#xff0c;如果1L20元&#xff0c;2L50元&#xff0c;那么将a[1]赋值为40&#xff0c;而不是50。 之后…

GDB 调试 Mysql 实战(二)GDB 调试打印

背景 在 https://mengkang.net/1328.html 实验中&#xff0c;我们通过optimizer_trace发现group by会使用intermediate_tmp_table&#xff0c;而且里面的的row_length是20&#xff0c;抱着"打破砂锅问到底"的求学精神&#xff0c;所以想通过 gdb 调试源码的方式看这个…

移动端AR的适用分析(二)

移动端AR的适用分析&#xff08;二&#xff09;1. 单目SLAM难点 2. 视觉SLAM难点 3. 可能的解决思路 单目slam的障碍来自于理论和实践两个方面。理论障碍可以看做是固有的&#xff0c;无法通过硬件选型或软件算法来解决的&#xff0c;例如单目初始化和尺度问题。实践问题包括计…

新的理念、 新的解决方案、 新的Azure Stack技术预览

Jeffrey Snover 我们很高兴地宣布︰ Azure Stack Technical Preview 2&#xff08;TP2&#xff09;已发布&#xff01;我们朝着向您的数据中心提供Azure服务能力的目标又更近一步。自发布第一个技术预览版&#xff08;TP1&#xff09;以来&#xff0c;我们访问了很多用户&…

【HDU】1084 What Is Your Grade? (结构体 sort)

http://acm.hdu.edu.cn/showproblem.php?pid1084 题目的关键&#xff1a; 1、Note, only 1 student will get the score 95 when 3 students have solved 4 problems. If you can solve 4 problems, you can also get a high score 95 or 90 (you can get the former(前者)…

FastDFS之Linux下搭建

1.软件环境 CentOS6.5 FastDFS v5.05 libfastcommon- - master.zip&#xff08;是从 FastDFS 和 FastDHT 中提取出来的公共 C 函数库&#xff09; fastdfs- - nginx- - module_v1.16.tar.gz nginx- - 1.6.2.tar.gz fastdfs_client_java._v1.25.tar.gz 2.FastDFS集群规划 描述 …

Linux进程与线程的区别 详细总结(面试经验总结)

首先&#xff0c;简要了解一下进程和线程。对于操作系统而言&#xff0c;进程是核心之核心&#xff0c;整个现代操作系统的根本&#xff0c;就是以进程为单位在执行任务。系统的管理架构也是基于进程层面的。在按下电源键之后&#xff0c;计算机就开始了复杂的启动过程&#xf…

【HDU/POJ/ZOJ】Calling Extraterrestrial Intelligence Again (素数打表模板)

http://poj.org/problem?id1411 POJ http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode1689 ZOJ http://acm.hdu.edu.cn/showproblem.php?pid1239 HDU 都是同一个题&#xff0c;但是可能你在HDU上AC&#xff0c;在POJ和ZOJ上是TLE&#xff08;所以还有待…

[AVR]使用AVR单片机驱动舵机

最近参加了三系举办的小车比赛(好像叫什么"驭远杯")。领导要求我驱动3-4个舵机。研究了几日&#xff0c;总算折腾出一个方案..、 1.舵机驱动的基本原理 &#xff08;可以参考http://blog.sina.com.cn/s/blog_8240cbef01018hu1.html&#xff09; "控制信号由接收…

高阶函数的使用

问题 字节跳动面试时问题&#xff1a;原函数例如fetchData是一个异步函数&#xff0c;尝试从服务器端获取一些信息并返回一个Promise。写一个新的函数可以自动重试一定次数&#xff0c;并且在使用上和原函数没有区别。 思路 这个问题其实不是很难&#xff0c;不过可能是太菜了紧…

内存溢出和内存泄漏的定义,产生原因以及解决方法(面试经验总结)

一、定义&#xff08;概念与区别&#xff09; 内存溢出 out of memory&#xff0c;是指程序在申请内存时&#xff0c;没有足够的内存空间供其使用&#xff0c;出现out of memory&#xff1b;比如申请 了一个integer,但给它存了long才能存下的数&#xff0c;那就是内存溢出。 …

【Codeforces】716B Complete the Word (26个字母)

http://codeforces.com/contest/716/problem/B 给你一个字符串该字符串中是否存在长度为26且这26个字母没有重复 一个满足上述条件但是部分区域是问号的话&#xff0c;需要用剩下的字母覆盖掉问号&#xff0c;其余部分的问号可以随便赋值 没有的话输出-1 暴力即可。 #incl…

MySQL ERROR 1878 解决办法

MySQL ERROR 1878报错解决办法错误重现Part1:大表修改字段mysql> ALTER TABLE erp-> ADD COLUMN eas_status tinyint(3) unsigned NOT NULL DEFAULT 0 AFTER totalprice;ERROR 1878 (HY000): Temporary file write failure.mysql> \q这里可以看到&#xff0c;添加字…

共享程序集和强命名程序集(3):强命名程序集的一些作用

强命名程序集能防篡改 用私钥对程序集进行签名&#xff0c;并将公钥和签名嵌入程序集&#xff0c;CLR就可以炎症程序集未被修改或破坏。程序集安装到GAC时&#xff0c;系统对包含清单的那个文件的内容进行哈希处理&#xff0c;将Hash值与PE文件中嵌入的RSA数字签名进行比较。如…

堆和栈的区别(面试经验总结)

C中&#xff0c;内存分为5个区&#xff1a;堆、栈、自由存储区、全局/静态存储区和常量存储区。 栈&#xff1a;是由编译器在需要时自动分配&#xff0c;不需要时自动清除的变量存储区。通常存放局部变量、函数参数等。 堆&#xff1a;是由new分配的内存块&#xff0c;由程序员…

百度Q3财报里的“大生意”

在今日发布的Q3财报中&#xff0c;百度花了不少篇幅来介绍人工智能业务的进展&#xff0c;作为百度的技术核心&#xff0c;近段时间几乎所有百度业务都在与人工智能做深入结合&#xff0c;这预示着移动互联网信息化技术发展已经全面开启人工智能时代&#xff0c;而百度势必要在…

【Codeforces/HDU】76A Plus and xor / 2095 find your present (2)(异或)。

http://codeforces.com/contest/76/problem/D A X Y B X xor Y 异或&#xff08;不进位加法&#xff09;&#xff1a;两个二进制数&#xff0c;对应的位置上&#xff0c;相同为0&#xff0c;不同为1 性质&#xff1a;a^a0&#xff0c;a^0a&#xff0c;满足交换律 所以…

前端项目如何管理

前端项目如何管理 前端项目的管理分为两个维度&#xff1a;项目内的管理与多项目之间的管理。 1. 项目内的管理 在一个项目内&#xff0c;当有多个开发者一起协作开发时&#xff0c;或者功能越来越多、项目越来越庞大时&#xff0c;保证项目井然有序的进行是相当重要的。 一般会…

CMake学习(一)

什么是 CMake 你或许听过好几种 Make 工具&#xff0c;例如 GNU Make &#xff0c;QT 的 qmake &#xff0c;微软的 MS nmake&#xff0c;BSD Make&#xff08;pmake&#xff09;&#xff0c;Makepp&#xff0c;等等。这些 Make 工具遵循着不同的规范和标准&#xff0c;所执行…

【Codeforces】1104C Grid game (变异的俄罗斯方块)

http://codeforces.com/problemset/problem/1104/C 4 X 4 的方格 放置 1*2的矩形&#xff08;用1表示&#xff09;和2*1的矩形&#xff08;用0表示&#xff09; 只要有一行或者一列都填满了&#xff0c;就会自动消除&#xff0c;就可以放心的矩形了&#xff0c;只要不重叠就可…

如何创建.gitignore文件,忽略git不必要提交的文件

1、在需要创建 .gitignore 文件的文件夹, 右键选择Git Bash 进入命令行&#xff0c;进入项目所在目录。 2、输入 touch .gitignore &#xff0c;生成“.gitignore”文件。 3、在”.gitignore” 文件里输入你要忽略的文件夹及其文件就可以了。&#xff08;注意格式&#xff09; …

软件安全访谈:ZipSlip、NodeJS安全性和BBS攻击

正如Nodejs Security WG成员和Snyk开发者布道师Liran Tal所写的那样&#xff0c;自BBS早期以来&#xff0c;这种漏洞利用的矢量攻击已经为人所知。InfoQ采访了Tal&#xff0c;了解了更多有关软件安全性&#xff08;尤其是Nodejs安全性&#xff09;的相关信息。今年早些时候&…

客户端与服务器的数据交互

毕设需要接粗到一些关于app和前端后端的东西&#xff0c;学习记录一下。 首先不要管安卓端还是苹果端&#xff0c;现在一般都是响应式的app&#xff0c;放到安卓或者苹果或者pc或者平板都是没有问题的。一般采用的是http接口通讯&#xff0c;或者socket连接。具体你要去查资料…