في Elixir، يعمل كل الكود داخل عمليات.
افتراضيًا، تُنفَّذ الدالة في العملية نفسها التي استُدعيت منها. وعندما تحتاج إلى تشغيل دالة معيّنة صراحةً في عملية جديدة، استخدم spawn/1:
spawn(fn -> 2 + 2 end)
# => #PID<0.125.0>
تُنشئ spawn/1 عملية جديدة تنفّذ الدالة المعطاة بلا وسائط وتُرجع معرّف العملية (PID). وتبقى العملية الجديدة حيّة طالما أن الدالة قيد التنفيذ، ثم تنتهي بصمت.
لا ينبغي الخلط بين عمليات Elixir وعمليات نظام التشغيل. فعمليات Elixir تستهلك قدرًا أقل بكثير من الذاكرة والمعالج. ولا بأس على الإطلاق في وجود تطبيقات Elixir تشغّل مئات من عمليات Elixir.
لا تتبادل العمليات المعلومات مباشرةً فيما بينها. بل ترسل العمليات الرسائل وتستقبلها لمشاركة البيانات.
يمكنك إرسال رسالة إلى أي عملية باستخدام send/2. الوسيط الأول في send/2 هو معرّف العملية (PID) للمستقبِل، والوسيط الثاني هو الرسالة.
يمكن أن تكون الرسالة من أي نوع. وغالبًا ما تتكوّن من ذرات وصفوف. وإذا أردت الحصول على رد، فينبغي أن تدرج معرّف العملية (PID) للمُرسِل في مكان ما داخل الرسالة. ويمكنك الحصول على معرّف العملية (PID) للعملية الحالية باستخدام self().
لا يتحقّق send/2 مما إذا كان المستقبِل قد استلم الرسالة، ولا مما إذا كان ما زال حيًّا. تنتهي الرسالة في صندوق رسائل المستقبِل، ولن تُقرأ إلا عندما يطلب المستقبِل صراحةً استقبال الرسائل.
يمكن قراءة رسالة من صندوق الرسائل باستخدام الماكرو receive/1. وهو يقبل كتلة do يمكنها مطابقة الأنماط على الرسائل.
receive do
{:ping, sender_pid} -> send(sender_pid, :pong)
:do_nothing -> nil
end
سيأخذ receive/1 رسالة واحدة من صندوق الرسائل تطابق أيًّا من الأنماط المعطاة، ثم ينفّذ التعبير المعطى لذلك النمط. وإذا لم تكن في صندوق الرسائل أي رسائل، أو لم يطابق أي منها أيًّا من الأنماط، فسينتظر receive/1 واحدة.
إذا أردت استقبال أكثر من رسالة واحدة، فأنت بحاجة إلى استدعاء receive/1 بشكل عودي. ومن الأنماط الشائعة تنفيذ دالة عودية، تُسمّى مثلًا loop، تستدعي receive/1 وتفعل شيئًا بالرسالة ثم تستدعي نفسها لانتظار المزيد من الرسائل. وإذا احتجت إلى نقل شيء من الحالة من استدعاء receive/1 إلى آخر، فيمكنك فعل ذلك بتمرير وسيط إلى دالة loop تلك.
معرّفات العمليات نوع بيانات مستقل بذاته. وهي تعمل بمثابة عناوين صندوق الرسائل: إذا كان لديك معرّف عملية (PID)، فيمكنك إرسال رسالة إلى تلك العملية. وعادةً ما تُنشأ معرّفات العمليات بشكل غير مباشر، كقيمة مُرجَعة من دوال تُنشئ عمليات جديدة، مثل spawn.
أنت تكتب نظامًا مدمجًا لآلة Take-A-Number. إنه طراز بسيط جدًا. يمكنه توزيع أرقام متتالية والإبلاغ عن آخر رقم وزّعه.
نفّذ الدالة start/0. يجب أن تُنشئ عملية جديدة وأن تُرجع PID الخاص بها. لا تحتاج العملية الجديدة إلى فعل أي شيء بعد.
TakeANumber.start()
# => #PID<0.138.0>
لاحظ أن PID قد يختلف في كل مرة تُشغّل فيها هذا الكود.
عدّل الآلة بحيث تكون العملية المُنشأة حديثًا جاهزة لاستقبال الرسائل (ابدأ حلقة استقبال) بحالة ابتدائية قيمتها 0. يجب أن تكون قادرة على استقبال رسائل {:report_state, sender_pid}. وكردّ على تلك الرسائل، تُرسل حالتها الحالية (رقم التذكرة الأخيرة التي وزّعتها) إلى sender_pid ثم تنتظر مزيدًا من الرسائل.
machine_pid = TakeANumber.start()
# a client sending a message to the machine
send(machine_pid, {:report_state, self()})
# a client receiving a message from the machine
receive do
msg -> msg
end
# => 0
عدّل الآلة بحيث تستطيع استقبال رسائل {:take_a_number, sender_pid}. يجب أن تزيد حالتها بمقدار 1، وتُرسل الحالة الجديدة إلى sender_pid، ثم تنتظر مزيدًا من الرسائل.
machine_pid = TakeANumber.start()
# a client sending a message to the machine
send(machine_pid, {:take_a_number, self()})
# a client receiving a message from the machine
receive do
msg -> msg
end
# => 1
عدّل الآلة بحيث تستطيع استقبال رسالة :stop. يجب أن تتوقف عن انتظار مزيد من الرسائل.
عدّل الآلة بحيث تتجاهل أي رسالة غير متوقعة تستقبلها وتواصل انتظار مزيد من الرسائل.
سجّل في Exercism لتتعلّم وتتقن Elixir عبر 58 مفهومًا168 تمرينًا، وإرشاد بشري حقيقي، وكل ذلك مجانًا.