استكشاف العمليات (processes) في Elixir
نبني على مثال FizzBuzz من المقالة السابقة ونطوّره.

مرّت قرابة 3 أشهر على إصدار Elixir 1.0، وما زلنا متحمسين جدًا لهذه اللغة.
في هذه المقالة نبني على مثال FizzBuzz من المقالة السابقة، ونحوّله الآن إلى خادم.
خادم FizzBuzz
defmodule FizzBuzz do
@moduledoc "A very simple FizzBuzz server"
@doc """
Computes and outputs the FizzBuzz value for the
provided argument.
"""
@spec compute(Integer) :: :ok
def compute do
receive do
n -> IO.puts "#{n}: #{compute(n)}"
end
compute
end
defp compute(n) do
case {rem(n, 3), rem(n, 5)} do
{0, 0} -> :FizzBuzz
{0, _} -> :Fizz
{_, 0} -> :Buzz
_ -> n
end
end
end
lang-elixir
هذا module بسيط، فيه بعض التوثيق وtypespec للدالة compute/0، التي تمثل نقطة الدخول الرئيسية إلى module الـ FizzBuzz. تستخدم الدالة receive/1 لجلب الرقم التالي من صندوق بريد العملية (mailbox)، ثم تحسب النتيجة (عبر الدالة الخاصة compute/1) وتطبعها، قبل أن تستدعي نفسها مجددًا لانتظار الرسالة التالية.
التواصل مع الخادم
لاستخدام الخادم الذي أنشأناه للتو، نبدأ تشغيله أولًا باستخدام spawn/3، التي تتوقع اسم module واسم الدالة المراد استدعاؤها ووسائط تلك الدالة. وتُرجع الـ pid الخاص بالعملية الجديدة، ونخزّنه في المتغير fb (تذكروا أن الأقواس حول وسائط الدوال اختيارية في Elixir).
fb = spawn FizzBuzz, :compute, []
lang-elixir
بعد ذلك يمكننا استخدام send/2 لإرسال رسالة إلى الخادم:
send fb, 3
lang-elixir
سيطبع هذا "3: Fizz" ويُرجع 3. لنرسل الآن عدة رسائل للتأكد من أن الاستدعاء التكراري في compute/0 يعمل كما هو متوقع:
10..15 |> Enum.map(&send(fb, &1))
lang-elixir
نستخدم هنا عامل الـ pipe في Elixir (|>/2) لإرسال Range الأرقام من 10 إلى 15 إلى Enum.map/2، التي تستخدم نسخة مطبّقة جزئيًا من send/2 مع وسيط مجهول (&1). والناتج كما توقعنا:
10: Buzz
11: 11
12: Fizz
13: 13
14: 14
15: FizzBuzz
lang-elixir
التعامل مع الأخطاء
قد يتساءل القراء الأكثر انتباهًا الآن: ماذا يحدث لو أرسلنا إلى FizzBuzz.compute/0 نوع بيانات آخر بدلًا من عدد صحيح؟ لنجرب:
send fb, 1.0
lang-elixir
يؤدي هذا إلى رسالة الخطأ التالية:
14:25:32.742 [error] Error in process <0.95.0> with exit value: {badarith,[{'Elixir.FizzBuzz',compute,1,[{file,"fizzbuzz.ex"},{line,10}]},{'Elixir.FizzBuzz',compute,0,[{file,"fizzbuzz.ex"},{line,4}]}]}
lang-elixir
يعني هذا أن خادم FizzBuzz قد تعطل. لكن بما أننا نتعامل مع عملية غير مرتبطة (unlinked)، فإن برنامجنا الرئيسي (وهو في هذه الحالة جلسة iex) لا يتأثر بذلك. لنشغّل الخادم مجددًا، لكن هذه المرة كعملية مرتبطة باستخدام spawn_link/3:
fb = spawn_link FizzBuzz, :compute, []
lang-elixir
الآن ستنتقل أي مشكلة في خادم FizzBuzz إلى الأعلى وتُنهي العملية الرئيسية أيضًا:
14:33:40.426 [error] Error in process <0.119.0> with exit value: {badarith,[{'Elixir.FizzBuzz',compute,1,[{file,"fizzbuzz.ex"},{line,10}]},{'Elixir.FizzBuzz',compute,0,[{file,"fizzbuzz.ex"},{line,4}]}]}
** (EXIT from #PID<0.116.0>) an exception was raised:
** (ArithmeticError) bad argument in arithmetic expression
fizzbuzz.ex:10: FizzBuzz.compute/1
fizzbuzz.ex:4: FizzBuzz.compute/0
lang-elixir
يمكن معالجة ذلك بإضافة guard clause إلى compute/0 في FizzBuzz على هذا النحو:
def compute do
receive do
n when is_integer(n) -> IO.puts "#{n}: #{compute(n)}"
_ -> IO.puts "Invalid argument"
end
compute
end
lang-elixir
ينجح هذا الحل، لكنه يتعارض مع أحد أهم مبادئ Erlang: "let it crash" (دعها تتعطل). فبدلًا من البرمجة الدفاعية، تُنهي الأخطاء العملية ببساطة، وتُترك معالجة الموقف لعمليات المراقبة (إن وُجدت).
المراقبة (Monitoring)
إحدى طرق التعامل مع ما سبق هي إعداد monitor. وعلى عكس الـ link، فهذه علاقة أحادية الاتجاه بين العمليات. لإنشاء monitor، نشغّل خادم FizzBuzz باستخدام spawn_monitor/3، التي تُرجع مرجع مراقبة (monitoring reference) إضافة إلى الـ pid. لا نستخدم هذا المرجع في المثال التالي، لكنه مفيد لإيقاف المراقبة يدويًا عبر استدعاء demonitor/2.
الآن إذا توقف الخادم، تُرسل رسالة إلى عملية المراقبة:
{fb, ref} = spawn_monitor FizzBuzz, :compute, []
send fb, 1.0
lang-elixir
بما أننا لم نستخدم receive/1 لإعداد مستقبِل رسائل في جلسة iex، سنحتاج إلى استدعاء الدالة المساعدة flush/0 يدويًا لرؤية تنبيه المراقبة:
{:DOWN, #Reference<0.0.0.262>, :process, #PID<0.79.0>,
{:badarith,
[{FizzBuzz, :compute, 1, [file: 'fizzbuzz.ex', line: 16]},
{FizzBuzz, :compute, 0, [file: 'fizzbuzz.ex', line: 10]}]}}
lang-elixir
ما التالي؟
التعامل مع العمليات في Elixir سهل جدًا. لكن بتنفيذ خادم FizzBuzz من الصفر، لا نستفيد بالكامل من القوة التي توفرها أسس Erlang/OTP للغة Elixir. لذا سنعود في الجزء القادم من هذه السلسلة إلى خادم FizzBuzz، لكن هذه المرة باستخدام سلوكيات OTP gen_server وsupervisor. تابعونا!


