البنى امتداد مبني فوق الخرائط، وهي توفر فحوصات وقت الترجمة وقيمًا افتراضية. تُسمّى البنية على اسم الوحدة التي عُرّفت فيها. لتعريف بنية، استخدم التركيب 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 مترًا وتستهلك واحدًا بالمئة من البطارية. ولا يُعرف لقب السيارة إلا عند إنشائها.
تحتوي السيارة التي يتم التحكم فيها عن بُعد على شاشة LED أنيقة تعرض معلومتين:
"<METERS> meters"."Battery at <PERCENTAGE>%".إذا كانت البطارية عند 0%، فلن تتمكن من قيادة السيارة بعد الآن، وستعرض شاشة البطارية "Battery empty".
نفّذ الدالة RemoteControlCar.new/0 بحيث تُرجع struct سيارة جديدة تمامًا يتم التحكم فيها عن بُعد:
RemoteControlCar.new()
# => %RemoteControlCar{
# battery_percentage: 100,
# distance_driven_in_meters: 0,
# nickname: "none"
# }
اللقب مطلوب في struct، فتأكد من تهيئة قيمة له في الدالة new، وليس في struct.
نفّذ الدالة RemoteControlCar.new/1 بحيث تُرجع struct سيارة جديدة تمامًا يتم التحكم فيها عن بُعد مع لقب مُحدَّد:
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"
تأكد من أن الدالة لا تقبل سوى struct من نوع RemoteControlCar كوسيط.
نفّذ الدالة RemoteControlCar.display_battery/1 لإرجاع نسبة شحن البطارية كما تُعرض على شاشة LED:
car = RemoteControlCar.new()
RemoteControlCar.display_battery(car)
# => "Battery at 100%"
تأكد من أن الدالة لا تقبل سوى struct من نوع RemoteControlCar كوسيط. وإذا كانت البطارية عند 0%، فستعرض شاشة البطارية "Battery empty".
نفّذ الدالة RemoteControlCar.drive/1 التي:
RemoteControlCar.new("Red")
|> RemoteControlCar.drive()
# => %RemoteControlCar{
# battery_percentage: 99,
# distance_driven_in_meters: 20,
# nickname: "Red"
# }
تأكد من أن الدالة لا تقبل سوى struct من نوع RemoteControlCar كوسيط.
حدّث الدالة RemoteControlCar.drive/1 بحيث لا تزيد المسافة المقطوعة ولا تنقص نسبة شحن البطارية عندما تكون البطارية فارغة (عند 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"
# }
سجّل في Exercism لتتعلّم وتتقن Elixir عبر 58 مفهومًا168 تمرينًا، وإرشاد بشري حقيقي، وكل ذلك مجانًا.