구조체는 맵 위에 구축된 확장 기능으로, 컴파일 시점 검사와 기본값을 제공해요. 구조체는 정의된 모듈의 이름을 따라 이름이 붙어요. 구조체를 정의하려면 defstruct 구문을 사용해요. 이 구문은 보통 모듈 정의 바로 뒤에 와요. defstruct는 아톰 리스트(nil 기본값용)나 키워드 리스트(지정된 기본값용)를 받아요. 기본값이 없는 필드는 기본값이 있는 필드보다 먼저 와야 해요.
defmodule Plane do
defstruct [:engine, wings: 2]
end
plane = %Plane{}
# => %Plane{engine: nil, wings: 2}
구조체는 맵을 기반으로 하기 때문에, 대부분의 맵 함수를 사용해 값을 가져오고 조작할 수 있어요. _Access Behaviour_는 구조체에 대해 구현되어 있지 않아요. 대신 구조체 필드에 접근하려면 정적 접근 연산자 .를 사용하는 것이 좋아요.
필드 값 가져오기:
plane = %Plane{}
plane.engine
# => nil
Map.fetch(plane, :wings)
# => {:ok, 2}
필드 값 업데이트
plane = %Plane{}
%{plane | wings: 4}
# => %Plane{engine: nil, wings: 4}
구조체는 구조체 이름을 사용하거나 사용하지 않고 패턴 매칭에 사용할 수 있어요.
plane = %Plane{}
%Plane{wings: wings} = plane
%{wings: wings} = plane
패턴에 구조체 이름을 포함하면, 왼쪽과 오른쪽이 같은 타입의 구조체인지 확인할 수 있어요.
defmodule Helicopter do
defstruct [:engine, rotors: 1]
end
%Plane{} = %Helicopter{}
# => (MatchError) no match of right hand side value: %Helicopter{engine: nil, rotors: 1}
@enforce_keys 모듈 속성을 필드 키 리스트와 함께 사용하면, 구조체가 생성될 때 값이 초기화되도록 할 수 있어요. 키가 리스트에 없으면, 위 예제에서 본 것처럼 값이 nil이 돼요. 강제된 키가 초기화되지 않으면 오류가 발생해요.
defmodule User do
@enforce_keys [:username]
defstruct [:username]
end
%User{}
# => (ArgumentError) the following keys must also be given when building struct User: [:username]
이 연습 문제에서는 드디어 돈을 모아서 장만한 무선 조종 자동차를 가지고 놀아볼 거예요.
자동차는 완전히 충전된(100%) 배터리로 시작해요. 리모컨으로 자동차를 운전할 때마다 20미터를 이동하고 배터리를 1퍼센트 소모해요. 자동차의 별명은 자동차를 만들기 전까지는 알 수 없어요.
무선 조종 자동차에는 두 가지 정보를 보여주는 멋진 LED 디스플레이가 있어요:
"<METERS> meters"로 표시돼요."Battery at <PERCENTAGE>%"로 표시돼요.배터리가 0%라면 더 이상 자동차를 운전할 수 없고, 배터리 표시에는 "Battery empty"가 나타나요.
새 무선 조종 자동차 구조체를 반환하도록 RemoteControlCar.new/0 함수를 구현해요:
RemoteControlCar.new()
# => %RemoteControlCar{
# battery_percentage: 100,
# distance_driven_in_meters: 0,
# nickname: "none"
# }
별명은 구조체에 반드시 필요한데, 값은 구조체가 아니라 new 함수에서 초기화해야 해요.
전달받은 별명을 가진 새 무선 조종 자동차 구조체를 반환하도록 RemoteControlCar.new/1 함수를 구현해요:
RemoteControlCar.new("Blue")
# => %RemoteControlCar{
# battery_percentage: 100,
# distance_driven_in_meters: 0,
# nickname: "Blue"
# }
LED 디스플레이에 표시되는 대로 거리를 반환하도록 RemoteControlCar.display_distance/1 함수를 구현해요:
car = RemoteControlCar.new()
RemoteControlCar.display_distance(car)
# => "0 meters"
이 함수가 인자로 RemoteControlCar 구조체만 받도록 해요.
LED 디스플레이에 표시되는 대로 배터리 잔량을 반환하도록 RemoteControlCar.display_battery/1 함수를 구현해요:
car = RemoteControlCar.new()
RemoteControlCar.display_battery(car)
# => "Battery at 100%"
이 함수가 인자로 RemoteControlCar 구조체만 받도록 해요. 배터리가 0%라면 배터리 표시에는 "Battery empty"가 나타나요.
다음을 수행하는 RemoteControlCar.drive/1 함수를 구현해요:
RemoteControlCar.new("Red")
|> RemoteControlCar.drive()
# => %RemoteControlCar{
# battery_percentage: 99,
# distance_driven_in_meters: 20,
# nickname: "Red"
# }
이 함수가 인자로 RemoteControlCar 구조체만 받도록 해요.
배터리가 방전된(0%) 경우에는 이동 거리도 늘리지 않고 배터리 잔량도 줄이지 않도록 RemoteControlCar.drive/1 함수를 수정해요:
%RemoteControlCar{
battery_percentage: 0,
distance_driven_in_meters: 1980,
nickname: "Red"
}
|> RemoteControlCar.drive()
# => %RemoteControlCar{
# battery_percentage: 0,
# distance_driven_in_meters: 1980,
# nickname: "Red"
# }
Exercism에 가입하고 Elixir 트랙을 개념 58개연습 문제 168개, 그리고 실제 사람의 멘토링과 함께 배우고 익혀 보세요. 모두 무료예요.