Типы
В Elixir есть обозначение для объявления типов и спецификаций. Elixir — язык с динамической типизацией, и поэтому спецификации типов никогда не используются компилятором для оптимизации или изменения кода. Тем не менее, использование спецификаций типов полезно, потому что
- они предоставляют документацию (например, инструменты, такие как ExDoc, отображают спецификации типов в документации)
- они используются инструментами, такими как Dialyzer, которые могут анализировать код со спецификациями типов, чтобы обнаружить несоответствия типов и возможные ошибки
Спецификации типов (иногда называемые типовыми спецификациями) определяются в различных контекстах с помощью следующих атрибутов:
-
@type -
@opaque -
@typep -
@spec -
@callback -
@macrocallback
Дополнительную информацию об определении типов и типовых спецификаций см. в подразделах «Определение типа» и «Определение спецификации» ниже.
Типы и их синтаксис
Синтаксис Elixir для спецификаций типов похож на синтаксис Erlang. Большинство встроенных типов, предоставляемых в Erlang (например, pid()) выражаются аналогично: pid() (или просто pid). Поддерживаются также параметризованные типы (например, list(integer)) и удаленные типы (например, Enum.t). Целые числа и атомарные литералы разрешены как типы (например, 1, :atom, или false). Все остальные типы построены из объединений предопределенных типов. Разрешены некоторые сокращения, такие как [...], <<>>, и {...}.
Базовые типы
type :: any() # the top type, the set of all terms
| none() # the bottom type, contains no terms
| atom()
| map() # any map
| pid()
| port()
| reference()
| struct() # any struct
| tuple() # tuple of any size
## Numbers
| float() # float
| integer() # integer
| neg_integer() # ..., -3, -2, -1
| non_neg_integer() # 0, 1, 2, 3, ...
| pos_integer() # 1, 2, 3, ...
## Lists
| list(type) # proper list ([]-terminated)
| nonempty_list(type) # non-empty proper list
| maybe_improper_list(type1, type2) # proper or improper list
| nonempty_improper_list(type1, type2) # improper list
| nonempty_maybe_improper_list(type1, type2) # non-empty proper or improper list
| Literals # Described in section "Literals"
| Builtin # Described in section "Built-in types"
| Remotes # Described in section "Remote types" Литералы
В спецификациях типов также поддерживаются следующие литералы:
type :: :atom ## Atoms
| true | false | nil # Special atom literals
## Bitstrings
| <<>> # empty bitstring
| <<_::size>> # size is 0 or a positive integer
| <<_::_*unit>> # unit is an integer from 1 to 256
| <<_::size, _::_*unit>>
## Functions
| (... -> type) # any arity, returns type
| (() -> type) # 0-arity, returns type
| (type1, type2 -> type) # 2-arity, returns type
## Integers
| 1 # integer
| 1..10 # integer from 1 to 10
## Lists
| [type] # list with any number of type elements
| [] # empty list
| [...] # shorthand for nonempty_list(any())
| [type, ...] # shorthand for nonempty_list(type)
| [key: value_type] # keyword list with key :key of value_type
## Maps
| %{} # empty map
| %{key: value_type} # map with required key :key of value_type
| %{required(key_type) => value_type} # map with required pairs of key_type and value_type
| %{optional(key_type) => value_type} # map with optional pairs of key_type and value_type
| %SomeStruct{} # struct with all fields of any type
| %SomeStruct{key: value_type} # struct with required key :key of value_type
## Tuples
| {} # empty tuple
| {:ok, type} # two-element tuple with an atom and any type Встроенные типы
Elixir также предоставляет следующие типы в качестве сокращений на основе базовых и литеральных типов, описанных выше.
| Встроенный тип | Определяется как |
|---|---|
term() | any() |
arity() | 0..255 |
as_boolean(t) | t |
binary() | <<_::_*8>> |
bitstring() | <<_::_*1>> |
boolean() |
false | true
|
byte() | 0..255 |
char() | 0..0x10FFFF |
charlist() | [char()] |
fun() | (... -> any) |
identifier() |
pid() | port() | reference()
|
iodata() |
iolist() | binary()
|
iolist() | maybe_improper_list(byte() | binary() | iolist(), binary() | []) |
keyword() | [{atom(), any()}] |
keyword(t) | [{atom(), t}] |
list() | [any()] |
nonempty_list() | nonempty_list(any()) |
maybe_improper_list() | maybe_improper_list(any(), any()) |
nonempty_maybe_improper_list() | nonempty_maybe_improper_list(any(), any()) |
mfa() | {module(), atom(), arity()} |
module() | atom() |
no_return() | none() |
node() | atom() |
number() |
integer() | float()
|
struct() | %{:__struct__ => atom(), optional(atom()) => any()} |
timeout() |
:infinity | non_neg_integer()
|
Удаленные типы
Любой модуль также может определять свои собственные типы, и модули в Elixir не являются исключением. Например, модуль Range определяет тип t/0, который представляет диапазон: к этому типу можно обратиться как Range.t/0. Аналогично, строка является String.t/0, любой перечислимый объект может быть Enum.t/0 и так далее.
Карты
Ключевые типы в картах могут перекрываться, и в этом случае приоритет отдается левому ключу. Значение карты не относится к этому типу, если оно содержит ключ, который не указан в разрешенных ключах карты.
Если вы хотите указать, что ключи, которые ранее не были определены в карте, разрешены, принято завершать тип карты с помощью optional(any) => any.
Обратите внимание, что синтаксическое представление map() — %{optional(any) => any}, а не %{}. Обозначение %{} определяет одиночный тип для пустой карты.
Определение типа
Атрибуты модуля @type, @typep, и @opaque могут использоваться для определения новых типов:
@type type_name :: type @typep type_name :: type @opaque type_name :: type
Тип, определенный с помощью @typep, является закрытым. Непрозрачный тип, определенный с помощью @opaque, — это тип, внутренняя структура которого не будет видна, но тип по-прежнему является общедоступным.
Типы могут быть параметризованы путём определения переменных в качестве параметров; эти переменные затем могут использоваться для определения типа.
@type dict(key, value) :: [{key, value}] Определение спецификации
@spec function_name(type1, type2) :: return_type @callback function_name(type1, type2) :: return_type @macrocallback macro_name(type1, type2) :: Macro.t
Обработчики используются для определения функций обработчиков поведений (см. страницу «Поведения» в документации для получения дополнительной информации о поведении).
Ограничения могут быть использованы для ограничения переменных типов, заданных в качестве аргументов функции.
@spec function(arg) :: [arg] when arg: atom
Если вы хотите указать более одной переменной, разделяйте их запятой.
@spec function(arg1, arg2) :: [arg1, arg2] when arg1: atom, arg2: integer
Также можно определить переменные типов без ограничений.
@spec function(arg) :: [arg] when arg: var
Вы также можете именовать свои аргументы в спецификации типов с использованием синтаксиса arg_name :: arg_type. Это особенно полезно в документации для различения нескольких аргументов одного типа (или нескольких элементов одного типа в определении типа):
@spec days_since_epoch(year :: integer, month :: integer, day :: integer) :: integer
@type color :: {red :: integer, green :: integer, blue :: integer} Спецификации могут быть перегружены так же, как и обычные функции.
@spec function(integer) :: atom @spec function(atom) :: integer
Примечания
Elixir не рекомендует использовать тип t:string/0, так как он может быть перепутан с двоичными данными, которые в Elixir называются «строками» (в отличие от списков символов). Чтобы использовать тип, который называется t:string/0 в Erlang, необходимо использовать тип charlist/0, который является синонимом для string. Если вы используете string, компилятор выдаст предупреждение.
Если вы хотите обратиться к типу «строка» (к тому, с которым работают функции в модуле String), используйте тип String.t/0 вместо этого.
© 2012 Plataformatec
Licensed under the Apache License, Version 2.0.
https://hexdocs.pm/elixir/1.4.5/typespecs.html