印记

  1. 正则表达式
  2. 字符串,字符列表和单词的印记
    1. 字符串
    2. 字符列表
    3. 单词列表
  3. 印记的插值与转义
  4. 自定义印记

我们已经知道Elixir提供了双引号包裹的字符串和单引号包裹的字符列表.然而,这只覆盖了语言中由着文本表示的结构体表面.例如,创造原子大多数是以:atom来表示.

Elixir的目标之一是扩展性:开发者应当可以将语言扩展以适应任何特定领域.计算机科学的应用是如此之广,没有任何语言能够在核心中处理许多的领域.所以,我们最好的选择是提高语言的扩展性,使得开发者,公司和社区能够将语言扩展到他们相关的领域.

本章,我们将探索印记,它是语言提供的一种用于处理文本表示的机制.印记由波浪符(~)开头,之后是定义印记的单词,然后是分隔符;修饰符可以选择性添加在最后的分隔符之后.

正则表达式

Elixir中最常用的印记是~r,用于创造正则表达式:

  1. # A regular expression that matches strings which contain "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兼容的正则表达式(regexes),由PCRE库实现.正则也支持修饰符.例如,修饰符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>

支持不同的分隔符的原因是可以更好地适应不同的印记.例如,使用括号作为分隔符可能会和正则中的括号搞混.然而,括号对于其它的印记可能很好用,比如我们将看到的.

字符串,字符列表和单词的印记

除了正则表达式,Elixir还提供了3种印记.

字符串

~s印记用于生成字符串,作用于双引号相同.当字符串包含双引号和单引号时,~s印记就很有用了:

  1. iex> ~s(this is a string with "double" quotes, not 'single' ones)
  2. "this is a string with \"double\" quotes, not 'single' ones"

字符列表

~c印记用于生成字符列表:

  1. iex> ~c(this is a char list containing 'single quotes')
  2. 'this is a char list containing \'single quotes\''

单词列表

~w印记用于生成单词列表(单词 只是普通字符串).在~w印记中,单词由空格分隔.

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

~w印记也支持c,sa修饰符(分别适用于字符列表,字符串和原子),能够指定最后的列表中元素的数据类型:

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

印记的插值与逃避

除了小写印记,Elixir也支持用于处理转义字符与插值的大写印记.虽然~s~S都会返回字符串,但前者支持转义代码和插值,而后者不支持:

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

下列转义代码可以用于字符串和字符列表:

  1. \" 双引号
  2. \' 单引号
  3. \\ 单反斜杠
  4. \a 响铃/警告
  5. \b 推格
  6. \d - 删除
  7. \e - 退出
  8. \f - 换页符
  9. \n 换行符
  10. \r 回车
  11. \s 空格
  12. \t 制表符
  13. \v 垂直制表符
  14. \0 - 空字节
  15. \xDD - 16进制表示单字节 (例如 \x13)
  16. \uDDDD and \u{D...} - 16进制表示Unicode代码点 (例如 \u{1F600})

印记也支持大段注释,以三个单引号或双引号分隔:

  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(...)

自定义印记

正如本章开始时提到的,Elixir中的印记也具有扩展性.事实上,使用印记~r/foo/i相当于使用一个二进制数和一个字符列表作为参数调用sigil_r:

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

我们可以通过sigil_r获取~r印记的文档:

  1. iex> h sigil_r
  2. ...

我们也可以简单地在sigil_{identifier}模式之后添加需要实现的函数来自定义我们的印记.例如,让我们用~i印记来返回一个整数(使用修饰符n可以将其变为负数):

  1. iex> defmodule MySigils do
  2. ...> def sigil_i(string, []), do: String.to_integer(string)
  3. ...> def sigil_i(string, [?n]), do: -String.to_integer(string)
  4. ...> end
  5. iex> import MySigils
  6. iex> ~i(13)
  7. 13
  8. iex> ~i(42)n
  9. -42

印记也可以在宏的帮助下进行编译时工作.例如,在编译源代码时,Elixir中的正则表达式会被编译成一个高效的表示方法,在运行时就可以跳过此步骤.如果你对此感兴趣,我们建议你学习宏的知识,并了解印记是如何在Kernel模块中被实现的(sigil_*函数就是在那里定义的).