20-Typespecs和behaviors

    1. 声明自定义数据类型
    2. 声明含有显式类型说明的函数签名(即函数的规格说明)

    默认地,Elixir提供了一些基础数据类型,表示为 或者 pid
    还有一些复杂情形:如函数round/1为例,它对一个float类型的数值四舍五入。
    它以一个number(一个integerfloat)作为参数,返回一个integer

    那么,它在
    里面记载的函数签名为:

    :: 表示其左边的函数 返回 一个其右面声明的类型的值。函数名后面括号中是参数类型的列表。

    如果想特别注明某个函数的参数类型及返回值类型,那么可以在定义函数的时候,
    在函数前面使用@spec指令附加上函数的规格说明(spec)。

    比如,在函数库源码中,函数round/1是这么写的:

    1. @spec round(number) :: integer
    2. def round(number), do: # 具体实现 ...

    Elixir还支持组合类型。例如,整数的列表,它的类型表示为[integer]
    可以阅读
    查看Elixir提供的所有内建类型的表示方法。

    比如我们有个模块叫做LuosyCalculator,可以执行常见的算术计算(如求和、计算乘积等)。
    但是,它的函数不是返回结果数值,而是返回一个元祖,
    该元祖第一个元素是计算结果,第二个是随机的文字记号。

    从例子中可以看出,元组是复合类型。每个元组都定义了其具体元素类型。
    至于为何是String.t而不是string的原因,可以参考
    这篇文章
    此处不多说明。

    像这样定义函数规格说明是没问题,但是一次次重复写这种复合类型的
    表示方法{number, String.t},很快会厌烦的吧。
    我们可以使用指令来声明我们自定义的类型:

    1. Just a number followed by a string.
    2. """
    3. @type number_with_remark :: {number, String.t}
    4. @spec add(number, number) :: number_with_remark
    5. def add(x, y), do: {x + y, "You need a calculator to do that?"}
    6. @spec multiply(number, number) :: number_with_remark
    7. def multiply(x, y), do: {x * y, "It is like addition on steroids."}
    8. end

    指令@typedoc,和@moduledoc指令类似,用来解释说明自定义的类型,放在前面。

    另外,通过@type定义的自定义类型,实际上也是模块的成员,可以被外界访问:

    如果想要将某个自定义类型保持私有,可以使用 指令代替 @type

    行为(behavior)

    许多模块公用相同的公共API。可以参考下,
    正如它的描述所言,是一个用于互联网应用的、可编辑的模块的规格声明
    每个所谓plug就是一个必须实现至少两个公共函数:和call/2的模块。

    行为提供了一种方法,用来:

    • 确保模块实现所有这些函数

    你也可以把这些行为想象为面向对象语言里的接口:模块必须实现的一系列函数签名。

    假如说我们希望实现一系列parser,每个parser解析结构化的数据:比如,一个JSON parser或是YAML parser。
    这两个parser的行为几近相同:
    它们都提供一个parse/1函数和一个extensions/0函数。parse/1函数返回一个数据对应的Elixir表达。
    extensions/0函数返回一个可被其解析的文件的扩展名列表(如,JSON文件是.json)。

    我们可以创建一个名为Parser的行为:

    1. defmodule Parser do
    2. @callback parse(String.t) :: any
    3. @callback extensions() :: [String.t]
    4. end

    那么,采用Parser这个行为的模块,必须实现所有被@callback指令标记的函数。正如你所看到的,
    指令不但可以接受一个函数名,还可以接受一个函数规格定义(我们在本文开头讲述的,函数的spec)。

    1. defmodule YAMLParser do
    2. @behaviour Parser
    3. def parse(str), do: # ... parse YAML
    4. def extensions, do: ["yml"]

    如果一个模块采用了一个尚未完全实现其所需回调方法的行为(behavior),这将生成一个编译时错误。