«ساختارها» افزونهای هستند که روی «نقشه»ها بنا شدهاند و بررسیهای زمان کامپایل و مقادیر پیشفرض را فراهم میکنند. هر ساختار به اسم ماژولی که در آن تعریف شده نامگذاری میشود. برای تعریف یک ساختار از سازهی 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]
در این تمرین با یک ماشین کنترلی بازی میکنید؛ همان ماشینی که بالاخره پول کافی برای خریدش را پسانداز کردهاید.
باتری ماشینها در ابتدا کاملاً پر است (۱۰۰٪). هر بار که ماشین را با کنترل از راه دور میرانید، ۲۰ متر جلو میرود و یک درصد از باتری را خالی میکند. لقب ماشین تا وقتی که ساخته نشده باشد، مشخص نیست.
ماشین کنترلی یک نمایشگر LED شیک دارد که دو مورد اطلاعات را نشان میدهد:
"<METERS> meters"."Battery at <PERCENTAGE>%".اگر باتری به ۰٪ برسد، دیگر نمیتوانید ماشین را برانید و نمایشگر باتری "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"
# }
تابع RemoteControlCar.display_distance/1 را پیادهسازی کنید تا مسافت را همانطور که روی نمایشگر LED نمایش داده میشود برگرداند:
car = RemoteControlCar.new()
RemoteControlCar.display_distance(car)
# => "0 meters"
مطمئن شوید که تابع فقط یک ساختار RemoteControlCar را به عنوان آرگومان میپذیرد.
تابع RemoteControlCar.display_battery/1 را پیادهسازی کنید تا درصد باتری را همانطور که روی نمایشگر LED نمایش داده میشود برگرداند:
car = RemoteControlCar.new()
RemoteControlCar.display_battery(car)
# => "Battery at 100%"
مطمئن شوید که تابع فقط یک ساختار RemoteControlCar را به عنوان آرگومان میپذیرد. اگر باتری به ۰٪ رسیده باشد، نمایشگر باتری «Battery empty» را نشان میدهد.
تابع RemoteControlCar.drive/1 را پیادهسازی کنید که:
RemoteControlCar.new("Red")
|> RemoteControlCar.drive()
# => %RemoteControlCar{
# battery_percentage: 99,
# distance_driven_in_meters: 20,
# nickname: "Red"
# }
مطمئن شوید که تابع فقط یک ساختار RemoteControlCar را به عنوان آرگومان میپذیرد.
تابع 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"
# }