19-魔法印(Sigils)

看看标题,这个“魔法印”是什么奇葩翻译?
Sigil原意是“魔符,图章,印记”,
如古代西方魔幻传说中的巫女、魔法师画的封印或者召唤魔鬼的六芒星,
中国道士画的咒符,火影里面召唤守护兽的血印等。
在计算机编程领域,Sigil指的是在变量名称上做的标记,用来标明它的作用域或者类型什么的。
例如某语言里面$var中的$就是这样的东西,表示其为全局变量。
不同的魔法印赋予变量不同的属性,这么看,翻译成“魔法印”还挺带感呢。
本章(或者本文)所有的sigil都会翻译成“魔法印”或是采用英文原文。

我们已经学习了Elixir提供的字符串(双引号包裹)和字符列表(单引号包裹)。
但是对于Elixir中所有的文本描述型数据类型来说,这些只是冰山一角。其它的,
例如原子也是一种文本描述型数据类型。

Elixir的一个特点就是高可扩展性:开发者能够为特定的领域来扩展语言。
计算机科学的领域已是如此广阔。几乎无法设计一门语言来涵盖所有范围。
我们的打算是,与其创造出一种万能的语言,不如创造一种可扩展的语言,
让开发者可以根据所从事的领域来对语言进行扩展。

本章将讲述“魔法印(sigils)”,它是Elixir提供的处理文本描述型数据的一种机制。

19.1-正则表达式

魔法印以波浪号(~)起头,后面跟着一个字母,然后是分隔符。最常用的魔法印是~r,
代表正则表达式

  1. # A regular expression that returns true if the text has foo or bar
  2. iex> regex = ~r/foo|bar/
  3. ~r/foo|bar/
  4. iex> "foo" =~ regex
  5. true
  6. iex> "bat" =~ regex
  7. false

Elixir提供了Perl兼容的正则表达式(regex),由PCRE库实现。

正则表达式支持修饰符(modifiers),例如i修饰符使该正则表达式无视大小写:

  1. iex> "HELLO" =~ ~r/hello/
  2. false
  3. iex> "HELLO" =~ ~r/hello/i
  4. true

阅读Regex模块获取关于其它修饰符的及其所支持的操作的更多信息。

目前为止,所有的例子都用了/界定正则表达式。事实上魔法印支持8种不同的分隔符:

  1. ~r/hello/
  2. ~r|hello|
  3. ~r"hello"
  4. ~r'hello'
  5. ~r(hello)
  6. ~r[hello]
  7. ~r{hello}
  8. ~r<hello>

支持多种分隔符是因为在处理不同的魔法印的时候更加方便。
比如,使用括号作为正则表达式的分隔符会让人困惑,分不清括号是正则模式的一部分还是别的什么。
但是,括号对某些魔法印来说就很方便。

19.2-字符串、字符列表和单词魔法印

除了正则表达式,Elixir还提供了三种魔法印。

魔法印~s 用来生成字符串,类似双引号的作用:

  1. iex> ~s(this is a string with "quotes")
  2. "this is a string with \"quotes\""

通过这个例子可以看出,如果文本中有双引号,又不想逐个转义,可以用这种魔法印来包裹字符串。

魔法印~c 用来生成字符列表:

  1. iex> ~c(this is a string with "quotes")
  2. 'this is a string with "quotes"'

魔法印~w 用来生成单词,以空格分隔开:

  1. iex> ~w(foo bar bat)
  2. ["foo", "bar", "bat"]

魔法印~w 还接受csa修饰符(分别代表字符列表,字符串和原子)
来选择结果的类型:

  1. iex> ~w(foo bar bat)a
  2. [:foo, :bar, :bat]

除了小写的魔法印,Elixir还支持大写的魔法印。如,~s~S都返回字符串,
前者会解释转义字符而后者不会:

  1. iex> ~s(String with escape codes \x26 interpolation)
  2. "String with escape codes & interpolation"
  3. iex> ~S(String without escape codes and without #{interpolation})
  4. "String without escape codes and without \#{interpolation}"

字符串和字符列表支持以下转义字符:

  • \” 表示一个双引号
  • \’ 表示一个单引号
  • \\ 表示一个反斜杠
  • \a 响铃
  • \b 退格
  • \d 删除
  • \e 退出
  • \f 换页
  • \n 新行
  • \r 换行
  • \s 空格
  • \t 水平制表符
  • \v 垂直制表符
  • \DDD, \DD, \D 八进制数字(如\377)
  • \xDD 十六进制数字(如\x13)
  • \x{D…} 多个十六进制字符的十六进制数(如\x{abc13}

魔法印还支持多行文本(heredocs),使用的是三个双引号或单引号:

  1. iex> ~s"""
  2. ...> this is
  3. ...> a heredoc string
  4. ...> """

最常见的有多行文本的魔法印就是写注释文档了。
例如,如果你要在注释里写一些转义字符,这有可能会报错。

  1. @doc """
  2. Converts double-quotes to single-quotes.
  3. ## Examples
  4. iex> convert("\\\"foo\\\"")
  5. "'foo'"
  6. """
  7. def convert(...)

使用~S,我们就可以避免问题:

  1. @doc ~S"""
  2. Converts double-quotes to single-quotes.
  3. ## Examples
  4. iex> convert("\"foo\"")
  5. "'foo'"
  6. """
  7. def convert(...)

19.3-自定义魔法印

本章开头提到过,魔法印是可扩展的。事实上,魔法印~r/foo/i等于是
用两个参数调用了函数sigil_r

  1. iex> sigil_r(<<"foo">>, 'i')
  2. ~r"foo"i

就是说,我们可以通过该函数阅读魔法印~r的文档:

  1. iex> h sigil_r
  2. ...

我们也可以通过实现相应的函数来提供我们自己的魔法印。例如,我们来实现一个~i(N)魔法印,
返回整数:

  1. iex> defmodule MySigils do
  2. ...> def sigil_i(string, []), do: String.to_integer(string)
  3. ...> end
  4. iex> import MySigils
  5. iex> ~i("13")
  6. 13

魔法印通过宏,可以用来做一些发生在编译时的工作。例如,正则表达式在编译时会被编译,
而在执行的时候就不必再被编译了。
如果你对此主题感兴趣,可以多阅读关于宏的资料,并且阅读Kernel模块中那些魔法印的实现。