iT邦幫忙

2025 iThome 鐵人賽

DAY 23
0

讓我們建立一個檔案來寫帶有巨集 (macro) 的模組

defmodule MyMacro do
  defmacro macro_rem(a, b) do
    quote do
      rem(unquote(a), unquote(b))
    end
  end
end

存到檔案 (my_macro.ex) 後,使用 iex my_macro.ex 打開並載入 iex
(如果是放在 mix 專案內,使用 iex -S mix)

在使用巨集前,我們必須要 require 該模組

require MyMacro

MyMacro.test
#=> 3

defmacro/2

使用 defmacro 定義函數時,回傳值必須要是 AST 表達式
上面我們使用了
quote do: rem(unquote(a), unquote(b))
經過 quote 與 unquote 帶入的變數(假如是 3, 2) 變成
{:rem, [context: Elixir, imports: [{2, Kernel}]], [3, 2]}
並在 macro 使用時從表達式執行

但是目前這樣其實還不如直接呼叫 rem 就可以了

但因為先解開成為 AST

這代表我們可以從 AST 結構上去改變執行方式

有些事只有巨集做的到

我們使用 IO.inspect/2 觀察一下這個巨集執行前的 AST

defmacro play(do: block) do
  ast = quote do: unquote(block)
  IO.inspect(ast)
end
require MyMacro

MyMacro.play do
  name = "Jack"
  name <> "White"
end

這個 AST 一樣是三個元件組成的 tuple
第三個裡面是 list 裝著在這個 block 裡面的每一行各自的 AST

{:__block__, [line: 1],
 [
   {:=, [line: 3], [{:name, [line: 3], nil}, "Jack"]},
   {:<>, [line: 4], [{:name, [line: 4], nil}, "White"]}
 ]}

有順序就代表我們可以把它倒過來執行

defmacro rewind(do: block) do
  {name, meta, args} = quote do: unquote(block)
  {name, meta, Enum.reverse(args)}
end

在這個 rewind 函式內,我們用 pattern matching 將第三個代表 block 的 list 取出
使用 Enum.reverse/1 更改順序後再組裝回去

我們就得到了一個在 do end 之間從最後一行開始執行到前面的神奇函式

require MyMacro

MyMacro.rewind do
  "#{fullname} 覺得這個是不是來亂的"
  fullname = name <> " White"
  name = "Jack"
  IO.puts "接著是這行"
  IO.puts "這行先執行"
end

上一篇
Meta-programming 2 - 使用 unquote 帶入變數
下一篇
Meta-programming 4 - 自動設置函數
系列文
通勤看手機就可讀懂的 Elixir 語言入門教學24
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言