elixir官方教程 元编程(二) 宏

2024-02-27 14:30
文章标签 教程 编程 官方 elixir

本文主要是介绍elixir官方教程 元编程(二) 宏,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

为什么80%的码农都做不了架构师?>>>   hot3.png

#宏

  1. 前言
  2. 我们的第一个宏
  3. 宏的隔离
  4. 环境
  5. 私有宏
  6. 负责任地编写宏

#前言

尽管Elixir已竭力为宏提供一个安全的环境,用宏编写干净代码的责任仍然落在了开发者身上.宏比传统的Elixir函数更难编写,而且在不必要的场合使用宏是不好的.所以请负责任地编写宏.

Elixir已经提供了许多数据结构和函数,能够让你以简单可读的风格编写日常代码.宏应当是最后的选择.记住,明显胜过含蓄.清晰的代码胜过简洁的代码.

#我们的第一个宏

ELixir中使用defmacro/2来定义宏.

本章,我们将使用文件来代替在IEx中运行样本代码.这是因为代码样本将跨越许多行,将它们全部输入IEx会适得其反.你应当将代码样本保存进macro.exs文件,并使用elixir macros.exsiex macro.exs来运行.

为了更好地理解宏是如何运作的,让我们创建一个新的模块,在其中实现unless,它的作用与if相反.分别以函数和宏的形式:

defmodule Unless dodef fun_unless(clause, expression) doif(!clause, do: expression)enddefmacro macro_unless(clause, expression) doquote doif(!unquote(clause), do: unquote(expression))endend
end

函数接收了参数,并传送给if.然而正如我们在前一章所学过的,宏会接收引用表达式,将它们注入引用,最后返回另一个引用表达式.

让我们用iex运行上面的模块:

$ iex macros.exs

调戏一下那些定义:

iex> require Unless
iex> Unless.macro_unless true, IO.puts "this should never be printed"
nil
iex> Unless.fun_unless true, IO.puts "this should never be printed"
"this should never be printed"
nil

注意,在宏的实现中,句子没有被打印,然而在函数的实现中,句子被打印了.这是因为函数的参数会在调用函数之前被执行.而宏不会执行它们的参数.它们以引用表达式的形式接收参数,之后又将其变形为其它引用表达式.本例中,我们实际上是将unless宏重写成了一个if.

换句话说,当被这样调用时:

Unless.macro_unless true, IO.puts "this should never be printed"

我们的macro_unless宏接收到了:

macro_unless(true, {{:., [], [{:aliases, [], [:IO]}, :puts]}, [], ["this should never be printed"]})

然后返回了一个引用表达式:

{:if, [],[{:!, [], [true]},[do: {{:., [],[{:__aliases__,[], [:IO]},:puts]}, [], ["this should never be printed"]}]]}

我们可以使用Macro.expand_once/2来验证它:

iex> expr = quote do: Unless.macro_unless(true, IO.puts "this should never be printed")
iex> res  = Macro.expand_once(expr, __ENV__)
iex> IO.puts Macro.to_string(res)
if(!true) doIO.puts("this should never be printed")
end
:ok

Macro.expand_once/2接收了引用表达式,并根据当前环境扩展了它.本例中,它扩展/调用了Unless.macro_unless/2宏,并返回了结果.之后我们将返回的引用表达式转换成一个字符串并打印出来(我们将在本章稍后的位置讨论__ENV__).

这就是宏.它们接收引用表达式并将其变形为别的东西.事实上,Elixir中的unless/2是作为宏来实现的:

defmacro unless(clause, options) doquote doif(!unquote(clause), do: unquote(options))end
end

本教程中用到的许多纯Elixir实现的结构都是宏,例如unless/2,defmacro/2,def/2,defprotocol/2等等.这意味着,开发者可以用构建语言的结构来将语言扩展到它们工作的领域.

我们可以定义任何函数和宏,甚至覆盖Elixir中的原本定义.唯一的例外是Elixir特殊形式,它们不是由Elixir实现的,因此不能被覆盖,特殊形式的完整列表可以在Kernel.SpecialForms中找到.

#宏的隔离(Macros hygiene)

Elixir的宏有着低决定权.这保证了引用中的变量定义不会与宏被扩展到的语境中的变量定义相冲突.例如:

defmodule Hygiene dodefmacro no_interference doquote do: a = 1end
enddefmodule HygieneTest dodef go dorequire Hygienea = 13Hygiene.no_interferenceaend
endHygieneTest.go
# => 13

上述例子中,即使宏注入了a = 1,却没有影响到变量a在函数go中的定义.如果宏想要明确地影响语境,可以使用var!:

defmodule Hygiene dodefmacro interference doquote do: var!(a) = 1end
enddefmodule HygieneTest dodef go dorequire Hygienea = 13Hygiene.interferenceaend
endHygieneTest.go
# => 1

因为Elixir使用变量的语境来注解它,所以能够实现变量隔离.例如,一个模块的第三行定义的变量x可以被表示成:

{:x, [line: 3], nil}

然而一个引用变量是这样表示的:

defmodule Sample dodef quoted doquote do: xend
endSample.quoted #=> {:x, [line: 3], Sample}

注意引用变量的第三个元素是原子Sample,而不是nil,它标记了变量是来自Sample模块的.因此,Elixir认为这两个变量来自不同语境,会分别处理它们.

Elixir也为进口(imports)和别名(aliases)提供了相似的机制.这保证了宏的行为会与它源模块中的定义相同,而不是与宏所扩展到的目标模块相冲突.使用类似var!/2alias!/2之类的宏可以突破隔离,但是它们必须小心使用,因为这直接改变了用户环境.

有时,变量名会被动态地创建.Macro.var/2可用于定义新变量:

defmodule Sample dodefmacro initialize_to_char_count(variables) doEnum.map variables, fn(name) ->var = Macro.var(name, nil)length = name |> Atom.to_string |> String.lengthquote dounquote(var) = unquote(length)endendenddef run doinitialize_to_char_count [:red, :green, :yellow][red, green, yellow]end
end> Sample.run #=> [3, 5, 6]

注意Macro.var/2的第二个变量.在下一节中我们将知道它是所使用的语境,而且能定义隔离.

#环境

本章早些时候,我们调用Macro.expand_once/2时,使用了特殊形式__ENV__.

__ENV__返回了一个Macro.Env结构的实例,它包含了编译环境的有用信息,包括当前模块,文件和行,所有定义在当前作用域中的变量,还有imports,requires等等.

iex> __ENV__.module
nil
iex> __ENV__.file
"iex"
iex> __ENV__.requires
[IEx.Helpers, Kernel, Kernel.Typespec]
iex> require Integer
nil
iex> __ENV__.requires
[IEx.Helpers, Integer, Kernel, Kernel.Typespec]

Macro模块中的许多函数都期望一个环境.你可以在Macro模块中找到关于这些函数,以及在Macro.Env的文档中找到关于编译环境的更多信息.

#私有宏

Elixir也支持私有宏,使用defmacrop来定义.和私有函数一样,这些宏只能在它的定义模块中使用,而且只在编译时.

很重要的一点是,宏在使用之前定义.没有在调用一个宏之前定义它,将会在运行时抛出一个错误,因为宏不会被扩展,而且将会被转化成函数调用:

iex> defmodule Sample do
...>  def four, do: two + two
...>  defmacrop two, do: 2
...> end
** (CompileError) iex:2: function two/0 undefined

#负责任地编写宏

宏是很强大的结构,Elixir提供了许多机制来确保它们被负责任地使用.

- 宏是隔离的: 定义在宏内的变量默认是不会影响用户代码的.而且,宏语境中的函数调用和别名是不会泄露到用户语境中的.

- 宏具有词典性质: 不可能全局地注入代码或宏.为了使用宏,你需要明确地requireimport定义了宏的模块.

- 宏是明确的: 宏不可能在没有明确被导入的情况下运行.例如,一些语言允许开发者在内部完全重写函数,通常是通过语义转换或一些反射机制.在Elixir中,编译时,宏必须在调用者中被明确导入.

- 宏的语言是清晰的: 许多语言为quoteunquote提供了语法捷径.在Elixir中,我们更愿意它们被明确地拼写出来,以便清楚地划出宏定义与它的引用表达式间的界限.

即使有这些保障,开发者仍在负责任地编写宏这件事中扮演重要角色.如果你确信你需要使用宏,记住宏不是你的API.你的宏定义要保持简短,包括它们的引用内容.例如,与其像这样编写宏:

defmodule MyModule dodefmacro my_macro(a, b, c) doquote dodo_this(unquote(a))...do_that(unquote(b))...and_that(unquote(c))endend
end

不如这样:

defmodule MyModule dodefmacro my_macro(a, b, c) doquote do# Keep what you need to do here to a minimum# and move everything else to a functiondo_this_that_and_that(unquote(a), unquote(b), unquote(c))endenddef do_this_that_and_that(a, b, c) dodo_this(a)...do_that(b)...and_that(c)end
end

这使得你的代码更清晰,也更容易测试和维护,因为你可以直接调用和测试do_this_that_and_that/3.这也有助于你为那些不愿意依赖宏的开发者来设计一个实际的API.

现在,我们结束了对宏的介绍.下一章我们将简短得讨论DSL,展示如何混合宏和模块属性,来注释和扩展模块与函数.

转载于:https://my.oschina.net/ljzn/blog/732310

这篇关于elixir官方教程 元编程(二) 宏的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!


原文地址:https://blog.csdn.net/weixin_34159110/article/details/91605518
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.chinasem.cn/article/752708

相关文章

Go语言数据库编程GORM 的基本使用详解

《Go语言数据库编程GORM的基本使用详解》GORM是Go语言流行的ORM框架,封装database/sql,支持自动迁移、关联、事务等,提供CRUD、条件查询、钩子函数、日志等功能,简化数据库操作... 目录一、安装与初始化1. 安装 GORM 及数据库驱动2. 建立数据库连接二、定义模型结构体三、自动迁

深度解析Spring AOP @Aspect 原理、实战与最佳实践教程

《深度解析SpringAOP@Aspect原理、实战与最佳实践教程》文章系统讲解了SpringAOP核心概念、实现方式及原理,涵盖横切关注点分离、代理机制(JDK/CGLIB)、切入点类型、性能... 目录1. @ASPect 核心概念1.1 AOP 编程范式1.2 @Aspect 关键特性2. 完整代码实

Java Web实现类似Excel表格锁定功能实战教程

《JavaWeb实现类似Excel表格锁定功能实战教程》本文将详细介绍通过创建特定div元素并利用CSS布局和JavaScript事件监听来实现类似Excel的锁定行和列效果的方法,感兴趣的朋友跟随... 目录1. 模拟Excel表格锁定功能2. 创建3个div元素实现表格锁定2.1 div元素布局设计2.

SpringBoot连接Redis集群教程

《SpringBoot连接Redis集群教程》:本文主要介绍SpringBoot连接Redis集群教程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1. 依赖2. 修改配置文件3. 创建RedisClusterConfig4. 测试总结1. 依赖 <de

Nexus安装和启动的实现教程

《Nexus安装和启动的实现教程》:本文主要介绍Nexus安装和启动的实现教程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、Nexus下载二、Nexus安装和启动三、关闭Nexus总结一、Nexus下载官方下载链接:DownloadWindows系统根

CnPlugin是PL/SQL Developer工具插件使用教程

《CnPlugin是PL/SQLDeveloper工具插件使用教程》:本文主要介绍CnPlugin是PL/SQLDeveloper工具插件使用教程,具有很好的参考价值,希望对大家有所帮助,如有错... 目录PL/SQL Developer工具插件使用安装拷贝文件配置总结PL/SQL Developer工具插

Java中的登录技术保姆级详细教程

《Java中的登录技术保姆级详细教程》:本文主要介绍Java中登录技术保姆级详细教程的相关资料,在Java中我们可以使用各种技术和框架来实现这些功能,文中通过代码介绍的非常详细,需要的朋友可以参考... 目录1.登录思路2.登录标记1.会话技术2.会话跟踪1.Cookie技术2.Session技术3.令牌技

Python使用Code2flow将代码转化为流程图的操作教程

《Python使用Code2flow将代码转化为流程图的操作教程》Code2flow是一款开源工具,能够将代码自动转换为流程图,该工具对于代码审查、调试和理解大型代码库非常有用,在这篇博客中,我们将深... 目录引言1nVflRA、为什么选择 Code2flow?2、安装 Code2flow3、基本功能演示

Java Spring 中的监听器Listener详解与实战教程

《JavaSpring中的监听器Listener详解与实战教程》Spring提供了多种监听器机制,可以用于监听应用生命周期、会话生命周期和请求处理过程中的事件,:本文主要介绍JavaSprin... 目录一、监听器的作用1.1 应用生命周期管理1.2 会话管理1.3 请求处理监控二、创建监听器2.1 Ser

MySQL 安装配置超完整教程

《MySQL安装配置超完整教程》MySQL是一款广泛使用的开源关系型数据库管理系统(RDBMS),由瑞典MySQLAB公司开发,目前属于Oracle公司旗下产品,:本文主要介绍MySQL安装配置... 目录一、mysql 简介二、下载 MySQL三、安装 MySQL四、配置环境变量五、配置 MySQL5.1