behaviour를 사용하면 _behaviour 모듈_에 인터페이스(함수와 매크로의 집합)를 정의해 두고, 나중에 여러 _콜백 모듈_에서 이 인터페이스를 구현할 수 있어요. 인터페이스를 공유하기 때문에 그 콜백 모듈들은 서로 바꿔서 사용할 수 있어요.
참고로 "behaviours"는 영국식 철자예요.
behaviour를 정의하려면 새 모듈을 만들고, 원하는 인터페이스에 포함되는 함수 목록을 지정해야 해요. 각 함수는 @callback 모듈 속성으로 정의해요. 문법은 함수 타입스펙(@spec)과 똑같아요. 함수 이름과 인자 타입 목록, 그리고 가능한 모든 반환 타입을 지정해야 해요.
defmodule Countable do
@callback count(collection :: any) :: pos_integer
end
기존 behaviour를 우리 모듈에 추가하려면(콜백 모듈을 만들려면) @behaviour 모듈 속성을 사용해요. 이 속성의 값은 추가하려는 behaviour 모듈의 이름이어야 해요.
그다음에는 그 behaviour 모듈이 요구하는 모든 함수(콜백)를 정의해야 해요. Elixir에 내장된 Access나 GenServer처럼 다른 사람이 만든 behaviour를 구현한다면, 그 behaviour의 모든 콜백 목록은 hexdocs.pm 문서에서 찾을 수 있어요.
콜백 모듈이 자기 behaviour에 속한 함수만 구현해야 하는 건 아니에요. 하나의 모듈이 여러 behaviour를 구현할 수도 있어요.
어떤 함수가 어느 behaviour에서 온 것인지 표시하려면, 각 함수 앞에 @impl 모듈 속성을 붙여요. 이 속성의 값은 그 콜백을 정의한 behaviour 모듈의 이름이어야 해요.
defmodule BookCollection do
@behaviour Countable
defstruct [:list, :owner]
@impl Countable
def count(collection) do
Enum.count(collection.list)
end
def mark_as_read(collection, book) do
# other function unrelated to the Countable behaviour
end
end
behaviour를 정의할 때는 콜백의 기본 구현을 함께 제공할 수 있어요. 이 구현은 __using__/1 매크로의 quoted 표현식 안에 정의해요. behaviour 모듈을 사용하는 쪽에서 기본 구현을 재정의할 수 있게 하려면, 함수 구현 뒤에 defoverridable/1 매크로를 호출해요. 이 매크로는 함수 이름을 키로, 함수의 인자 수를 값으로 하는 키워드 리스트를 받아요.
defmodule Countable do
@callback count(collection :: any) :: pos_integer
defmacro __using__(_) do
quote do
@behaviour Countable
def count(collection), do: Enum.count(collection)
defoverridable count: 1
end
end
end
__using__/1 안에서 함수를 정의하는 건 기본 콜백 구현을 정의할 때 외에는 권장하지 않아요. 하지만 함수를 다른 모듈에 정의하고 __using__/1 매크로에서 import하는 건 언제든 할 수 있어요.