프로토콜은:
defprotocol로 정의해요.defimpl로 특정 데이터 타입에 대해 구현해요.프로토콜의 큰 장점은, 특정 데이터 타입에 대한 프로토콜 구현이 그 데이터 타입의 정의나 프로토콜의 정의와 함께 묶여 있을 필요가 없다는 점이에요. 덕분에 라이브러리를 수정하지 않고도, 사용자가 자신만의 데이터 타입에서도 동작하도록 라이브러리를 쉽게 확장할 수 있어요.
엘릭서의 모든 타입, 즉 Tuple, Atom, List, BitString, Integer, Float, Function, PID, Map, Port, Reference, Any와 구조체에 대해 프로토콜을 구현할 수 있어요.
프로토콜은 한 번에 여러 타입에 대해 구현할 수 있어요:
defimpl Reversible, for: [Map, List] do
def reverse(term) do
Enum.reverse(term)
end
end
구조체를 정의하는 모듈 안에서 그 구조체에 대한 프로토콜을 직접 구현할 때는 :for 인자를 생략할 수 있어요:
defmodule ShoppingCart do
defstruct [:items]
defimpl Reversible do
def reverse(shopping_cart) do
Enum.reverse(shopping_cart)
end
end
end
Any로의 폴백기본적으로, 구현되지 않은 데이터 타입에 대해 프로토콜을 호출하면 Protocol.UndefinedError 오류가 발생해요:
to_string({})
# => ** (Protocol.UndefinedError) protocol String.Chars not implemented for {} of type Tuple
# (elixir 1.10.3) lib/string/chars.ex:3: String.Chars.impl_for!/1
# (elixir 1.10.3) lib/string/chars.ex:22: String.Chars.to_string/1
이런 경우에 사용될 폴백 구현을 제공하면 이 문제를 피할 수 있어요. 그러려면 프로토콜을 정의할 때 @fallback_to_any 모듈 속성을 true로 설정하고, Any 타입에 대한 구현을 작성하면 돼요:
defprotocol Reversible do
@fallback_to_any true
def reverse(term)
end
defimpl Reversible, for: Any do
def reverse(term) do
term
end
end
참고로, 대부분의 경우에는 오류를 발생시키는 쪽이 더 바람직해요. 모든 데이터 타입에 대해 프로토콜에 어울리는 합리적인 가정을 세우기는 어렵기 때문이에요.
Enumerable - Enum과 Stream 모듈의 함수들은 이 프로토콜을 구현한 데이터 타입과 함께 동작해요.Collectable - Enum.into/2 함수는 이 프로토콜을 사용해서 열거 가능한 값을 컬렉션에 넣어요.Inspect - 이 프로토콜을 구현하면 Kernel.inspect/2가 특정 데이터 타입을 문자열로 표현하는 방식을 바꿀 수 있어요.String.Chars - 이 프로토콜을 구현하면 Kernel.to_string/1이 특정 데이터 타입을 문자열로 표현하는 방식을 바꿀 수 있어요.