Structs are an extension built on top of maps which provide compile-time checks and default values. A struct is named after the module it is defined in. To define a struct use the defstruct
construct. The construct usually immediately follows after the module definition. defstruct
accepts either a list of atoms (for nil
default values) or a keyword list (for specified default values). The fields without defaults must precede the fields with default values.
defmodule Plane do
defstruct [:engine, wings: 2]
end
plane = %Plane{}
# => %Plane{engine: nil, wings: 2}
Since structs are built on maps, we can use most map functions to get and manipulate values. The Access Behaviour is not implemented for structs. It is recommended to use the static access operator .
to access struct fields instead.
get/fetch field values:
plane = %Plane{}
plane.engine
# => nil
Map.fetch(plane, :wings)
# => {:ok, 2}
update field values
plane = %Plane{}
%{plane | wings: 4}
# => %Plane{engine: nil, wings: 4}
Structs can be used in pattern matching with or without the struct name.
plane = %Plane{}
%Plane{wings: wings} = plane
%{wings: wings} = plane
By including the struct name in the pattern, you can ensure that both the left and right side are structs of the same type.
defmodule Helicopter do
defstruct [:engine, rotors: 1]
end
%Plane{} = %Helicopter{}
# => (MatchError) no match of right hand side value: %Helicopter{engine: nil, rotors: 1}
We can use the @enforce_keys
module attribute with a list of the field keys to ensure that the values are initialized when the struct is created. If a key is not listed, its value will be nil
as seen in the above example. If an enforced key is not initialized, an error is raised.
defmodule User do
@enforce_keys [:username]
defstruct [:username]
end
%User{}
# => (ArgumentError) the following keys must also be given when building struct User: [:username]
In this exercise you'll be playing around with a remote controlled car, which you've finally saved enough money for to buy.
Cars start with full (100%) batteries. Each time you drive the car using the remote control, it covers 20 meters and drains one percent of the battery. The car's nickname is not known until it is created.
The remote controlled car has a fancy LED display that shows two bits of information:
"<METERS> meters"
."Battery at <PERCENTAGE>%"
.If the battery is at 0%, you can't drive the car anymore and the battery display will show "Battery empty"
.
Implement the RemoteControlCar.new/0
function to return a brand-new remote controlled car struct:
RemoteControlCar.new()
# => %RemoteControlCar{
# battery_percentage: 100,
# distance_driven_in_meters: 0,
# nickname: "none"
# }
The nickname is required by the struct, make sure that a value is initialized in the new
function, but not in the struct.
Implement the RemoteControlCar.new/1
function to return a brand-new remote controlled car struct with a provided nickname:
RemoteControlCar.new("Blue")
# => %RemoteControlCar{
# battery_percentage: 100,
# distance_driven_in_meters: 0,
# nickname: "Blue"
# }
Implement the RemoteControlCar.display_distance/1
function to return the distance as displayed on the LED display:
car = RemoteControlCar.new()
RemoteControlCar.display_distance(car)
# => "0 meters"
Make sure the function only accepts a RemoteControlCar
struct as the argument.
Implement the RemoteControlCar.display_battery/1
function to return the battery percentage as displayed on the LED display:
car = RemoteControlCar.new()
RemoteControlCar.display_battery(car)
# => "Battery at 100%"
Make sure the function only accepts a RemoteControlCar
struct as the argument. If the battery is at 0%, the battery display will show "Battery empty".
Implement the RemoteControlCar.drive/1
function that:
RemoteControlCar.new("Red")
|> RemoteControlCar.drive()
# => %RemoteControlCar{
# battery_percentage: 99,
# distance_driven_in_meters: 20,
# nickname: "Red"
# }
Make sure the function only accepts a RemoteControlCar
struct as the argument.
Update the RemoteControlCar.drive/1
function to not increase the distance driven nor decrease the battery percentage when the battery is drained (at 0%):
%RemoteControlCar{
battery_percentage: 0,
distance_driven_in_meters: 1980,
nickname: "Red"
}
|> RemoteControlCar.drive()
# => %RemoteControlCar{
# battery_percentage: 0,
# distance_driven_in_meters: 1980,
# nickname: "Red"
# }
Sign up to Exercism to learn and master Elixir with 57 concepts, 159 exercises, and real human mentoring, all for free.