文档章节

elixir官方教程 元编程(三) 领域特定语言

ljzn
 ljzn
发布于 2016/08/14 10:37
字数 1555
阅读 42
收藏 0
点赞 0
评论 0

#领域特定语言

  1. 前言
  2. 构建我们的测试案例
  3. test
  4. 用属性存储信息

#前言

领域特定语言(DSL)允许开发者修改他们的应用以适应特定的领域.制作DSL可以不需要用到宏:你在你的模块中定义的每个数据结构和每个函数都是你的领域特定语言的一部分.

比如,假设你想要实现一个提供了数据验证领域特定语言的验证模块.你可以使用数据结构,函数或宏来实现它.让我们来看看这些不同的DSL:

# 1. data structures
import Validator
validate user, name: [length: 1..100],
               email: [matches: ~r/@/]

# 2. functions
import Validator
user
|> validate_length(:name, 1..100)
|> validate_matches(:email, ~r/@/)

# 3. macros + modules
defmodule MyValidator do
  use Validator
  validate_length :name, 1..100
  validate_matches :email, ~r/@/
end

MyValidator.validate(user)

上述方法中,第一个是最有弹性的.如果我们的领域规则可以使用数据结构来编码,它们组成和实现起来会非常简单,因为Elixir的标准库中有很多用于操作不同数据类型的函数.

第二个方法使用函数调用,它能更好地适应更复杂的API(例如,需要传递很多选项)而且归功于Elixir中的管道操作符,它更易读.

第三个方法,使用宏,无疑是最复杂的.需要更多行的代码来实现,测试它是很难且很昂贵的(与测试简单的函数相比),而且它也限制了用户使用库的方法,因为所有验证需要在一个模块中定义.

为了更好地理解,假设你想要只在某种情况下验证某个特定属性.我们可以简单地使用第一个方法操作相应的数据结构来实现,或者使用第二个方法在调用函数前使用条件语句(if/else).然而不可能使用宏来完成,除非该DSL已经增强过了.

也就是说:

data > functions > macros

仍然有一些时候,由宏和模块来构建领域特定语言是很管用的.因为我们已经在入门教程中探索过了数据结构和函数定义,本章将会探索如何使用宏和模块属性来处理更复杂的DSL.

#构建我们自己的测试案例

本章的目标是构建一个名为TestCase的模块,它允许我们这样编写:

defmodule MyTest do
  use TestCase

  test "arithmetic operations" do
    4 = 2 + 2
  end

  test "list operations" do
    [1, 2, 3] = [1, 2] ++ [3]
  end
end

MyTest.run

在上面的例子中,使用TestCase,我们可以用test宏来编写测试,它定义了一个名为run的函数来自动为我们运行所有测试.我们的原型将简单地依赖匹配操作符(=)作为一个断言机制.

#test

让我们创建一个模块,它在被使用时简单地定义并进口了test宏:

defmodule TestCase do
  # Callback invoked by `use`.
  #
  # For now it simply returns a quoted expression that
  # imports the module itself into the user code.
  @doc false
  defmacro __using__(_opts) do
    quote do
      import TestCase
    end
  end

  @doc """
  Defines a test case with the given description.

  ## Examples

      test "arithmetic operations" do
        4 = 2 + 2
      end

  """
  defmacro test(description, do: block) do
    function_name = String.to_atom("test " <> description)
    quote do
      def unquote(function_name)(), do: unquote(block)
    end
  end
end

假设我们在一个名为tests.exs的文件中定义了TestCase,我们可以通过运行iex tests.exs打开它,并定义我们的第一个测试:

iex> defmodule MyTest do
...>   use TestCase
...>
...>   test "hello" do
...>     "hello" = "world"
...>   end
...> end

现在我们还没有一个运行测试的机制,但是我们知道有一个名为test hello的函数已经在幕后被定义了.当调用它时,他应该会失败:

iex> MyTest."test hello"()
** (MatchError) no match of right hand side value: "world"

#使用属性存储信息

为了完整实现我们的TestCase,我们需要能够访问所有已经定义的测试案例.我们可以通过在运行时使用__MODULE__.__info__(:functions)来检索测试,它会返回一个包含给定模块中所有函数的列表.然而,考虑到我们可能需要存储更多关于每个测试的信息,这就要求要有一个更灵活地方法.

当在前几章中讨论模块属性时,我们提到了它们是如何被用作临时存储的.那就是我们在本节中将应用的特性.

__using__/1的实现中,我们将会把一个名为@tests的模块属性初始化成一个空列表,然后存储每个已定义测试的名字到该属性中,这样测试就可以从run函数调用.

这是TestCase模块更新后的代码:

defmodule TestCase do
  @doc false
  defmacro __using__(_opts) do
    quote do
      import TestCase

      # Initialize @tests to an empty list
      @tests []

      # Invoke TestCase.__before_compile__/1 before the module is compiled
      @before_compile TestCase
    end
  end

  @doc """
  Defines a test case with the given description.

  ## Examples

      test "arithmetic operations" do
        4 = 2 + 2
      end

  """
  defmacro test(description, do: block) do
    function_name = String.to_atom("test " <> description)
    quote do
      # Prepend the newly defined test to the list of tests
      @tests [unquote(function_name) | @tests]
      def unquote(function_name)(), do: unquote(block)
    end
  end

  # This will be invoked right before the target module is compiled
  # giving us the perfect opportunity to inject the `run/0` function
  @doc false
  defmacro __before_compile__(env) do
    quote do
      def run do
        Enum.each @tests, fn name ->
          IO.puts "Running #{name}"
          apply(__MODULE__, name, [])
        end
      end
    end
  end
end

通过启动一个新的IEx会话,我们现在可以定义并运行我们的测试:

iex> defmodule MyTest do
...>   use TestCase
...>
...>   test "hello" do
...>     "hello" = "world"
...>   end
...> end
iex> MyTest.run
Running test hello
** (MatchError) no match of right hand side value: "world"

尽管我们跳过了一些细节,但这就是在Elixir中创建领域特定模块的主要思想.宏使得我们能够返回在调用者中执行了的引用表达式,我们可以用它来变形代码并通过模块属性来在目标模块中存储相关信息.最后,@before_compile这样的回调能让我们将代码注入到已定义完成的模块中.

除了@before_compile,还有其它有用的模块属性,例如@on_definition@after_compile,你可以在Module模块的文档中获取有关它们的信息.你也可以在Macro模块和Macro.Env的文档中找到关于宏和编译环境的有用信息.

© 著作权归作者所有

共有 人打赏支持
ljzn
粉丝 29
博文 69
码字总数 96245
作品 0
南平
程序员
elixir官方入门教程 学习资料

下一步该去哪 构建你的第一个Elixir项目 元编程 社区与其它资源 Erlang基础 想要学习更多?继续阅读! 构建你的第一个Elixir项目 为了开始你的第一个项目,Elixir装载了一个叫做Mix的构建工具....

ljzn ⋅ 2016/08/06 ⋅ 0

elixir官方教程 元编程(一) 引用与去引用

引用与去引用 引用(Quoting) 去引用(Unquoting) 释放(Escaping) 一个Elixir程序可以用它自己的数据结构来表现.本章,我们将会学习这些结构体的特点和如何组成它们.本章我们要学习的概念是为宏...

ljzn ⋅ 2016/08/13 ⋅ 0

总有你要的编程书单(GitHub )

目录 IDE IntelliJ IDEA 简体中文专题教程 MySQL 21分钟MySQL入门教程 MySQL索引背后的数据结构及算法原理 NoSQL Disque 使用教程 Neo4j .rb 中文資源 Redis 命令参考 Redis 设计与实现 The ...

汇智网 ⋅ 2017/11/22 ⋅ 0

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

宏 前言 我们的第一个宏 宏的隔离 环境 私有宏 负责任地编写宏 前言 尽管Elixir已竭力为宏提供一个安全的环境,用宏编写干净代码的责任仍然落在了开发者身上.宏比传统的Elixir函数更难编写,而...

ljzn ⋅ 2016/08/14 ⋅ 3

Elixir学习笔记(模型匹配、控制语句)

模型匹配 模式匹配是 Elixir 很强大的特性,它允许我们匹配简单值、数据结构、甚至函数。 匹配运算符 在Elixir中,运算符实际上叫做 匹配运算符。通过这个匹配操作符,我们可以赋值和匹配值。...

程序员小哥哥 ⋅ 04/12 ⋅ 0

elixir官方入门教程 介绍

介绍 安装 交互模式 运行脚本 提出疑问 欢迎! 在本教程中我们将教给你Elixir的基础,语法,如何定义模块,如何操作常用数据结构的特性等等.本章将确保Elixir安装好了,并且你能够成功运行Elixir的...

ljzn ⋅ 2016/08/06 ⋅ 0

Elixir 学习资源

Elixir 官网 getting started官方入门学习资源 官方文档 hex 包管理系统 elixir sips 比较不错视频课程 Elixir China 中文论坛 官方wiki Elixir by Example Awesome Elixir Elixir Quiz 通过...

lidashuang ⋅ 2017/11/29 ⋅ 0

elixir官方入门教程 别名,要求与进口

别名,要求与进口 别名 要求 进口 使用 理解别名 模块嵌套 群体别名/进口/要求/使用 为了方便软件复用,Elixir提供了三个命令(,和)外加一个宏,简介如下: 现在我们将详细探索它们.记住前三条之所...

ljzn ⋅ 2016/08/04 ⋅ 2

放弃Python转向Go语言:我们找到了以下9大理由

选自Stream 作者:Thierry Schellenbach 机器之心编译 参与:黄小天、李亚洲 转用一门新语言通常是一项大决策,尤其是当你的团队成员中只有一个使用过它时。今年 Stream 团队的主要编程语言从...

机器之心 ⋅ 2017/10/18 ⋅ 0

Elixir: 编程语言的未来

这篇文章谈一谈最近火爆的 Elixir,同时说一下对编程语言选择的看法。同时作为 Erlang 发烧友,Elixir 不可不提。即使有了那么多编程语言 Elixir 也值得接触。 现在开始接触 Elixir 对编程语...

oschina ⋅ 2015/08/02 ⋅ 18

没有更多内容

加载失败,请刷新页面

加载更多

下一页

vue-cli是什么?

vue-cli是什么? vue-cli 是vue.js的脚手架,用于自动生成vue.js+webpack的项目模板,分为vue init webpack-simple 项目名 和vue init webpack 项目名 两种。 当然首先你的安装vue,webpack...

韦姣敏 ⋅ 34分钟前 ⋅ 0

12c rman中输入sql命令

12c之前版本,要在rman中执行sql语句,必须使用sql "alter system switch logfile"; 而在12c版本中,可以支持大量的sql语句了: 比如: C:\Users\zhengquan>rman target / 恢复管理器: Release 1...

tututu_jiang ⋅ 40分钟前 ⋅ 0

java 线程池

概述 减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务 可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为因为消耗过多的内存,而把服务器累趴下(...

轨迹_ ⋅ 45分钟前 ⋅ 0

Nginx的https配置记录以及http强制跳转到https的方法梳理

Nginx的https配置记录以及http强制跳转到https的方法梳理 一、Nginx安装(略) 安装的时候需要注意加上 --with-httpsslmodule,因为httpsslmodule不属于Nginx的基本模块。 Nginx安装方法: ...

Yomut ⋅ 57分钟前 ⋅ 0

SpringCloud Feign 传递复杂参数对象需要注意的地方

1.传递复杂参数对象需要用Post,另外需要注意,Feign不支持使用GetMapping 和PostMapping @RequestMapping(value="user/save",method=RequestMethod.POST) 2.在传递的过程中,复杂对象使用...

@林文龙 ⋅ 59分钟前 ⋅ 0

如何显示 word 左侧目录大纲

打开word说明文档,如下图,我们发现左侧根本就没有目录,给我们带来很大的阅读障碍 2 在word文档的头部菜单栏中,切换到”视图“选项卡 3 然后勾选“导航窗格”选项 4 我们会惊奇的发现左侧...

二营长意大利炮 ⋅ 今天 ⋅ 0

智能合约编程语言Solidity之线上开发工具

工具地址:https://ethereum.github.io/browser-solidity/ 实例实验: 1.创建hello.sol文件 2.调试输出结果

硅谷课堂 ⋅ 今天 ⋅ 0

ffmpeg 视频格式转换

转 Mp4 格式 #> ffmpeg -i input.avi -c:v libx264 output.mp4#> ffmpeg -i input.avi -c:v libx264 -strict -2 output.mp4#> ffmpeg -i input.avi -c:v libx264 -strict -2 -s 1......

Contac ⋅ 今天 ⋅ 0

VCS仿真生成vpd文件(verilog)

VCS仿真生成vpd文件(verilog): https://www.cnblogs.com/OneFri/p/5987673.html SYNOPSYS VCS常用命令使用详解 https://blog.csdn.net/hemmingway/article/details/49382551 DVE是synopsys公......

whoisliang ⋅ 今天 ⋅ 0

Spring Boot启动配置原理

几个重要的事件回调机制 配置在META-INF/spring.factories ApplicationContextInitializer SpringApplicationRunListener 只需要放在ioc容器中 ApplicationRunner CommandLineRunner 启动流程......

小致dad ⋅ 今天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部