3

带你了解Elixir的宏

 2 years ago
source link: https://www.ttalk.im/2019/10/macro-in-elixir.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

介绍Lisp宏和Elixir宏的,以及通过示例代码,一步步为读者介绍Elixir的宏是如何在编译的过程中进行展开的

宏(Macro),是一种批量处理的称谓。一般说来,宏是一种规则或模式,或称语法替换 , 用于说明某一特定输入(通常是字符串)如何根据预定义的规则转换成对应的输出(通常也 是字符串)。这种替换在预编译时进行,称作宏展开。 说道宏,就不得不提一个经典语言和 它的宏。

Lisp的特点和它的宏

Lisp的特点:

  1. 数据就代码,代码就是数据
  2. LISP中所有的都是list, 当然也可以叫做S表达式。
  3. 如果把list中的第一元素视为函数,该list就可视作代码一样运行。术语叫做求值, evaluate。当然也可以不求值,此时list就是数据。因此这里引出一个重要概念 ,代码 也是数据,一切皆为数据,一切都是list。

Lisp的宏:

  1. 如果一个list传递给lisp函数,则先被求值为atom(一个特殊的list,不能再被求值) 后再传递进去 如果一个list传递给lisp宏,则不被求值,而将其完整的传递进去,至于 宏里面怎么干,随便宏的实现者怎么玩。像C的宏吧,不过C的宏只是文本替换,还是简 单了点。
  2. 宏可以返回的是一个list,而且被视作可以求值的list,也就是代码。
  3. 两阶段执行,第一阶段在编译期,称之为展开,第二阶段在运行期,称之为计算。宏在 展开时,并不对实参求值,只把宏定义中对形参的引用简单替换为实参。实参在计算阶 段时才求值。

Elixir是什么

Elixir 是一个基于Erlang虚拟机强大的类Ruby语法的编程语言。

Elixir的宏

Elixir也是支持宏的,并且Elixir的宏也是异常强大的,也做到了两阶段执行。 但是今天 要介绍的主要是关于Elixir中use和@before_compile的部分。

defmodule MyModule do
  use MyPlugBuilder

  plug :hello
  plug :world, good: :morning
end

defmodule MyPlugBuilder do

  defmacro __using__(_opts) do
    quote do
      import MyPlugBuilder, only: [plug: 1, plug: 2]
      Module.register_attribute(__MODULE__, :plugs, accumulate: :true)
      @before_compile MyPlugBuilder
    end
  end

  defmacro plug(plug, opts \\ []) do
    quote do
      @plugs {unquote(plug), unquote(opts)}
    end
  end

  defmacro __before_compile__(env) do
    plugs = Module.get_attribute(env.module, :plugs)
    quote do
      def plugs, do: unquote(plugs)
    end
  end
end

MyPlugBuilder的展开

defmodule MyModule do
  # ----
  # use MyPlugBuilder
  # ---- ↓
  require MyPlugBuilder
  MyPlugBuilder.__using__([])
  # ----

  plug :hello
  plug :world, good: :morning
end

因为use MyPlugBuilder这句话会展开成 require MyPlugBuilder MyPlugBuilder.using MyModule 会请求引入 MyPlugBuilder,接着会调用_using_宏,并且默认参数为[]

defmodule MyModule do
  require MyPlugBuilder

  # ----
  # MyPlugBuilder.__using__([])
  # ---- ↓
  import MyPlugBuilder, only: [plug: 1, plug: 2]
  Module.register_attribute(__MODULE__, :plugs, accumulate: :true)
  @before_compile MyPlugBuilder
  # ----

  plug :hello
  plug :world, good: :morning
end

using宏会立刻被执行,相当于立刻将MyPlugBuilder的函数引入进来,并且给MyModule 注册了一个叫做plugs的模块属性。同时告诉编译器,稍后编译MyPlugBuilder的时候,调 用_before_compile_。

defmodule MyModule do
  require MyPlugBuilder

  import MyPlugBuilder, only: [plug: 1, plug: 2]
  Module.register_attribute(__MODULE__, :plugs, accumulate: :true)
  @before_compile MyPlugBuilder

  # ----
  # plug :hello
  # plug :world, good: :morning
  # ---- ↓
  @plugs {:hello, []}
  @plugs {:world, [good: :morning]}
  # ----
end

此时还没有展开_before_compile_,而是先展开从MyPlugBuilder模块中import进来的 plug宏,完成相关定义内容

defmodule MyModule do
  require MyPlugBuilder

  import MyPlugBuilder, only: [plug: 1, plug: 2]
  Module.register_attribute(__MODULE__, :plugs, accumulate: :true)
  @before_compile MyPlugBuilder

  @plugs {:hello, []}
  @plugs {:world, [good: :morning]}

  MyPlugBuilder.__before_compile__(__ENV__) end

此时展开了MyPlugBuilder中_before_compile_宏,完成整个展开过程。

一个复杂点的例子

defmodule MyPlugBuilder do

  defmacro __using__(_opts) do
    quote do
      import MyPlugBuilder, only: [plug: 1, plug: 2, aplug: 1]
      Module.register_attribute(__MODULE__, :plugs, accumulate: :true)
      IO.puts __MODULE__
      IO.puts unquote(__MODULE__)
      @before_compile MyPlugBuilder
      unquote(defs())
    end
  end

  # `plug` 本体
  defmacro plug(plug, opts \\ []) do
    quote do
      IO.puts unquote(plug)
      @plugs {unquote(plug), unquote(opts)}
    end
  end

  defmacro aplug(plug) do
      xplug(plug, [])
  end

  defp defs() do
    IO.puts "aplug"
    quote unquote: false do
      IO.puts "eval"
      var!(pplug, MyPlugBuilder) = fn resource ->
        IO.puts resource
      end
    end
  end

  defp xplug(plug,opts \\ []) do
    quote do
      plug = unquote(plug)
      var!(pplug, MyPlugBuilder).(plug)
    end
  end

  defmacro __before_compile__(env) do
    plugs = Module.get_attribute(env.module, :plugs)
    IO.puts "__before_compile__"
    conn = compile(env)
    quote do
      def plugs, do: unquote(plugs)
      def plug_builder_call(unquote(conn)), do: IO.puts conn
    end
  end

  def compile(env) do
    conn = quote do: conn
    conn
  end

end

展开后代码上的差异

defmodule MyModule do
  # ----
  # use MyPlugBuilder
  # ---- ↓
  require MyPlugBuilder
  MyPlugBuilder.__using__([])
  # ----

  plug :hello
  plug :world, good: :morning
end


defmodule MyModule do
  require MyPlugBuilder

  # ----
  # MyPlugBuilder.__using__([])
  # ---- ↓
  MyPlugBuilder.defs()
  import MyPlugBuilder, only: [plug: 1, plug: 2, aplug: 1]
  Module.register_attribute(__MODULE__, :plugs, accumulate: :true)
  IO.puts __MODULE__
  IO.puts "MyPlugBuilder"
  @before_compile MyPlugBuilder
  # ----

  plug :hello
  plug :world, good: :morning
end

至此读者可能已经注意到了MyPlugBuilder的defs()函数先于两个IO.puts执行了。

可以在编译期间展开在执行阶段求值实参的宏,确实可以给我们带来很大的方便,但是也大 大带来了危险性。 宏乃屠龙之技,但是用的时候要慎之再慎。


Recommend

  • 66
    • 微信 mp.weixin.qq.com 6 years ago
    • Cache

    这一次带你彻底了解Cookie

    网络早期最大的问题之一是如何管理状态。简而言之,服务器无法知道两个请求是否来自同一个浏览器。当时最简单的方法是在请求时,在页面中插入一些参数,并在下一个请求中传回参数。这需要使用包含参数的隐藏的表单,或者作为URL参数的一部分传递。这两个解决方案都手...

  • 96

    网络早期最大的问题之一是如何管理状态。简而言之,服务器无法知道两个请求是否来自同一个浏览器。当时最简单的方法是在请求时,在页面中插入一些参数,并在下一个请求中传回参数。这需要使用包含参数的隐藏的表单,或者作为URL参数的一部分传递。这两个解决方案都手...

  • 57

    什么是加密货币?它们和比特币一样吗? 一言以蔽之:是的。 比特币是第一个加密货币,目前也是规模最大的加密货币,但是...

  • 85
    • developer.51cto.com 6 years ago
    • Cache

    带你了解我喜欢Vue的10个方面

    带你了解我喜欢Vue的10个方面

  • 51

    本文已在我的公众号hongyangAndroid原创发布。 一、概要 大家应该都清楚,大家上线app,需要上线各种平台,比如:小米,华为,百度等等等等,我们多数称之为渠道,如果发的渠道多,可能有上百个渠道。 针对每个渠道,我们希望可以获取各个渠道的一些独立的

  • 59
    • GAD腾讯游戏开发者平台 gad.qq.com 6 years ago
    • Cache

    一文带你全面了解游戏运营这个岗位

    在国内,游戏行业的从业者众多,而每家游戏公司几乎都会囊括市场、运营、技术、设计等职位,今天我们就来详细聊一下游戏运营,给想入行的朋友们一些建议,以及给初入行的朋友们一些遗漏知识面上的扩充。 

  • 64

    10个例子带你了解机器学习中的线性代数

  • 51

    一般我们熟悉 Python 中列表、元组及字典等数据结构,但集合可能用得稍微少一点。但集合独特的元素唯一性与 O(1) 时间复杂度的成员检测方法,令其在很多任务中有特别的优势。本文介绍了 Python 集合的常见方法与概念,包括集合元...

  • 52

    阅读: 10 6月25日,Wi-Fi联盟正式推出Wi-Fi连接用户身份验证技术的最新版本,即WPA3加密方式。与当今普...

  • 63

    前言在 Java中,反射机制(Reflection)非常重要,但对于很多开发者来说,这并不容易理解,甚至觉得有点神秘今天,我将献上一份 Java反射机制的介绍 & 实战攻略,希望你们会喜欢。 目录1. 简介定义:Java语言中 一种 动态(运行时)访问、检测 &am...

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK