Logo
Section 1 / 3

编写我们自己的符号

学习在 Elixir 中编写自己的符号。

导入

编写我们自己的符号

学习在 Elixir 中编写自己的符号。

编写自定义符号

Elixir 包含许多让编程变得愉快的功能。本章将介绍其中一些特性。

我们已经知道,可以使用符号来创建字符串和正则表达式字面量:

1string = ~s{现在是时候了} 2regex = ~r{..h..}

你是否曾希望扩展这些符号,以添加自己的特定字面量类型?你可以做到。

当我们编写如 ~s{...} 这样的符号时,Elixir 会将其转换为对函数 sigil_s 的调用。该函数接收两个参数:

  • 第一个是分隔符之间的字符串。
  • 第二个是一个包含紧跟在闭合分隔符之后的小写字母的列表。这个参数用于获取传递给正则表达式字面量的选项,例如 ~r/cat/if。

下面是一个实现符号 ~l 的例子,它接收一个多行字符串,并返回一个包含每一行作为独立字符串的列表。我们知道 ~l... 会被转换为对 sigil_l 的调用,因此我们只需在 LineSigil 模块中编写一个简单的函数。

1defmodule LineSigil do 2 @doc """ 3 实现 `~l` 符号,它接收一个包含多行的字符串,并返回这些行组成的列表。 4 5 ## 使用示例 6 7 iex> import LineSigil 8 nil 9 iex> ~l""" 10 ...> one 11 ...> two 12 ...> three 13 ...> """ 14 ["one", "two", "three"] 15 """ 16 def sigil_l(lines, _opts) do 17 lines |> String.trim_trailing |> String.split("\n") 18 end 19end

我们可以在另一个模块中测试这个功能:

1defmodule Example do 2 import LineSigil 3 4 def lines do 5 ~l""" 6 line 1 7 line 2 8 and another line in #{__MODULE__} 9 """ 10 end 11end 12 13IO.inspect Example.lines

运行结果如下:

1["line 1", "line 2", "and another line in Elixir.Example"]

由于我们在示例模块中导入了 sigil_l 函数,因此 ~l 符号仅在该模块内有效。另外请注意,Elixir 在将字符串传递给我们的方法之前会进行插值处理。这是因为我们使用的是小写的 l。如果我们的符号是 ~L{...},并且函数重命名为 sigil_L,则不会执行插值。

预定义的符号函数包括:sigil_C、sigil_c、sigil_R、sigil_r、sigil_S、sigil_s、sigil_W 和 sigil_w。如果我们想覆盖其中某个函数,就需要显式导入 Kernel 模块,并使用 except 子句排除它。

在这个例子中,我们使用了 here-doc 语法(""")。这会将一个移除了前导空格的多行字符串传给我们的函数。符号选项无法与 here-doc 一起使用,因此我们将切换到常规字面量语法来尝试选项。

获取选项参数

让我们编写一个符号,用于定义颜色常量。如果我们输入 ~c{red},将得到 0xff0000,即 RGB 表示形式。我们还支持选项 h 来返回 HSB 值,因此 ~c{red}h 的结果是 {0,100,100}。

以下是代码实现:

1defmodule ColorSigil do 2 3 @color_map [ 4 rgb: [red: 0xff0000, green: 0x00ff00, blue: 0x0000ff, # ... 5 ], 6 hsb: [red: {0,100,100}, green: {120,100,100}, blue: {240,100,100} 7 ] 8 ] 9 10 def sigil_c(color_name, []), do: _c(color_name, :rgb) 11 def sigil_c(color_name, 'r'), do: _c(color_name, :rgb) 12 def sigil_c(color_name, 'h'), do: _c(color_name, :hsb) 13 14 defp _c(color_name, color_space) do 15 @color_map[color_space][String.to_atom(color_name)] 16 end 17 18 defmacro __using__(_opts) do 19 quote do 20 import Kernel, except: [sigil_c: 2] 21 import unquote(__MODULE__), only: [sigil_c: 2] 22 end 23 end 24end 25 26defmodule Example do 27 use ColorSigil 28 29 def rgb, do: IO.inspect ~c{red} 30 def hsb, do: IO.inspect ~c{red}h 31end 32 33Example.rgb #=> 16711680 (== 0xff0000) 34Example.hsb #=> {0,100,100}

sigil_c 函数的三个函数头允许我们根据传入的选项选择颜色空间。因为单引号字符串 'r' 实际上表示为列表 [?r],我们可以使用字符串字面量来匹配选项参数。

由于我们要覆盖内置符号,因此决定实现一个 __using__ 宏,自动移除 Kernel 中的版本,并在调用 use 的作用域内引入我们自己的版本。

能够自定义符号确实令人自由。但若滥用,也可能导致代码难以理解。

运行 elixir line_sigil.exs 和 elixir color.exs 命令来执行以下文件。

协议——多态函数

多应用伞形项目