Elixir는 동적으로 타입이 지정되는 언어예요. 따라서 컴파일 시점의 타입 검사를 제공하지 않아요. 그래도 타입 명세는 유용할 수 있는데, 그 이유는 다음과 같아요:
함수 정의 바로 앞에 @spec 모듈 속성을 쓰면 함수에 타입 명세를 추가할 수 있어요.
@spec longer_than?(String.t(), non_neg_integer()) :: boolean()
def longer_than?(string, length), do: String.length(string) > length
타입 명세에 쓸 수 있는 표현식은 다음과 같아요:
기본 타입, 예를 들면:
boolean()integer(), non_neg_integer(), pos_integer(), float()
list()map()any()타입의 합집합:
integer() | list(integer())
매개변수화된 타입:
list(integer()) - 정수의 배열%{age: integer()} - :age 키 아래에 정수 값을 가진 맵원격 타입(어떤 모듈에 정의된 타입), 예를 들면:
리터럴, 예를 들면:
%{} - 빈 맵[] - 빈 배열 (하지만 [any()]는 비어 있지 않은 배열이에요):ok - 아톰 리터럴내장 특수 타입, 예를 들면:
char() - 0..0x10FFFF 범위의 정수charlist() - 문자(char)의 배열keyword() - 첫 번째 원소가 아톰인, 원소 두 개짜리 튜플의 배열사용자 정의 타입
모든 타입의 전체 목록은 공식 문서의 "Typespecs" 섹션에서 확인할 수 있어요.
타입 명세의 인자에는 이름을 붙일 수도 있는데, 같은 타입의 인자가 여러 개일 때 구분하는 데 유용해요.
@spec to_hex({hue :: integer, saturation :: integer, lightness :: integer}) :: String.t()
사용자 정의 타입은 다음 세 가지 모듈 속성 중 하나로 정의할 수 있어요:
@type - 공개 타입을 정의해요@typep - 비공개 타입을 정의해요@opaque - 구조는 비공개인 공개 타입을 정의해요@type color :: {hue :: integer, saturation :: integer, lightness :: integer}
@spec to_hex(color()) :: String.t()
String.t() vs binary() vs string()
String.t()는 Elixir 문자열에 사용해야 하는 올바른 타입이에요. Elixir 문자열은 UTF-8로 인코딩된 바이너리예요. 엄밀히 말하면 String.t()는 binary()로 정의되어 있고, 분석 도구에게 이 두 타입은 동등해요. 하지만 Elixir 문자열을 다루는 함수를 문서화할 때는 String.t()가 의도를 더 잘 드러내는 선택이에요.
반면에 string()은 다른 타입이에요. Erlang 문자열이고, Elixir에서는 charlist라고 불러요. 타입 명세에서는 string() 타입을 피하고 대신 charlist()를 사용해야 해요.
Dialyzer는 Erlang과 Elixir 코드에서 타입 오류 같은 문제를 찾아낼 수 있는 정적 분석 도구예요. Elixir 프로젝트에서 Dialyzer를 사용하는 가장 쉬운 방법은 Dialyxir를 쓰는 거예요.