اذهب إلى المحتوى

هدى جبور

الأعضاء
  • المساهمات

    26
  • تاريخ الانضمام

  • تاريخ آخر زيارة

كل منشورات العضو هدى جبور

  1. غالبًا ما نحتاج في التطبيقات العمليّة إلى تلخيص النصوص لاستخلاص المعلومات المفيدة من المستندات أو الوثائق المختلفة. بدءًا من المقالات البحثية المُعقّدة وتقارير الأرباح المالية إلى رسائل البريد الإلكتروني الطويلة والمقالات الإخباريّة. ويتطلّب تلخيص النصوص فهمًا دقيقًا للمقاطع الطويلة، والقدرة على تحليل محتواها المعقد، والكفاءة في صياغة نص موجز متماسك يُلخّص الموضوعات الأساسية للوثيقة الأصلية. خلال هذه المقالة سوف: نبحث عن نص لكي نُنفّذ عملية التلخيص عليه باستخدام تقنيات الذكاء الاصطناعي. نتعرّف على أفضل النماذج الموجودة حاليًّأ لتنفيذ هذه المهمة. نكتب بعض التعليمات البرمجية لتنفيذ ذلك. نُقيّم أداء النماذج باستخدام أدوات تقييم الأداء ذات الصلة بهذه المهمة. النص الذي سنؤدي عليه المهام الموضحة أعلاه هو عبارة عن نص يوضح مجموعة من الاعتبارات والمبادئ الأساسية لتطوير ونشر أنظمة الذكاء الاصطناعي، وقد جلبناه من أحد المواقع يدويًّا للتجريب وتبسيط العمل، لكن في التطبيقات العمليّة، قد نحتاج إلى استخراج هكذا نصوص من مواقع الويب، وهذا يتطلب استخدام أدوات ومكتبات إضافية أخرى. import textwrap content = """Mozilla's "Trustworthy AI" Thinking Points: PRIVACY: How is data collected, stored, and shared? Our personal data powers everything from traffic maps to targeted advertising. Trustworthy AI should enable people to decide how their data is used and what decisions are made with it. FAIRNESS: We’ve seen time and again how bias shows up in computational models, data, and frameworks behind automated decision making. The values and goals of a system should be power aware and seek to minimize harm. Further, AI systems that depend on human workers should protect people from exploitation and overwork. TRUST: People should have agency and control over their data and algorithmic outputs, especially considering the high stakes for individuals and societies. For instance, when online recommendation systems push people towards extreme, misleading content, potentially misinforming or radicalizing them. SAFETY: AI systems can carry high risk for exploitation by bad actors. Developers need to implement strong measures to protect our data and personal security. Further, excessive energy consumption and extraction of natural resources for computing and machine learning accelerates the climate crisis. TRANSPARENCY: Automated decisions can have huge personal impacts, yet the reasons for decisions are often opaque. We need to mandate transparency so that we can fully understand these systems and their potential for harm.""" نحن الآن جاهزون للبدء بتلخيص هذا النص. لكن دعنا قبل ذلك نناقش بداية أبرز التحديات والحلول المرتبطة بتقنيات الذكاء الاصطناعي. التحديات والحلول في الوضع الراهن للذكاء الاصطناعي يتطلّب الوضع الراهن في الذكاء الاصطناعي أن يبقى مهندس الذكاء الاصطناعي يقظًا ومتابعًا لأهم الأعمال والتقنيات والأوراق البحثيّة الجديدة، فكل أسبوع نشاهد العديد من الاختراقات في هذا المجال، فمثلًا التقنيات التي كانت تُستخدم مع مهمةً ما منذ سنة، أصبحت الآن قديمة بكل معنى الكلمة، فربما كنت تستخدم فرضًا نموذج GPT 3 في مشروعك، لكن في لحظة كتابة هذا المقال لدينا GPT 3.5 و GPT 4، وهما نموذجان يُمثلان قفزة هائلة عن GPT 3 وفي المستقبل القريب ستظهر بالتأكيد نماذج أخرى أحدث وأكثر استقرارًا وكفاءة. نستنتج من ذلك أن البيئة الحاليّة للذكاء الاصطناعي هي بيئة ديناميكيّة تتغيّر باستمرار، لذا لن يكون من السهل على المهندسين الجدد المهتمين بهذا المجال مواكبة تلك التطورات السريعة، لذا فالأمر يتطلّب اجتهادًا وصبرًا طويلين، وهذا يتضمّن: التعرّف على النماذج مفتوحة المصدر المتوفرة. معرفة مدى مُلاءمة النماذج للمهام المحددة. مثلًا إذا كنت تعمل على مهام تتضمّن توليد اللغة مثل توليد النص أو تلخيصه، فالأفضل غالبًا هي النماذج التي تعتمد على بنية مُفكك التشفير Decoder من نموذج المحولات Transformers مثل GPT أو CTRL، أو بنيتي مُفكك التشفير والمُشفّر Encoder معًا كما في T5. فهم المعايير والمقاييس المستخدمة لتقييم هذه النماذج فبعض المقاييس مُصمّمة لمهام محددة فقط. معرفة النماذج التي تُحقّق أفضل النتائج على المهمة المطلوبة، وذلك وفقًا للمقاييس والمعايير النموذجيّة للمهمة المدروسة. ضمان التوافق مع الأجهزة المتوفرة، مثلًا ChatGPT لايدعم الآندرويد لحظة كتابة هذا المقال. أما بالنسبة للمهندسين المُنخرطين بالفعل في سوق العمل ويعملون في ظل مواعيد نهائية ضيقة، فربما سيكون من الصعب جدًا عليهم مواكبة أحدث التطورات، والذي يزيد من تعقيد المشكلة هو كثرة المواقع والمجلات والمؤتمرات التي تُنشر فيها أحدث التطورات أو يجري التحدث فيها عن أحدث التطورات، وهذا ما يجعل من الصعب على المهندسين الوصول إلى معلومات شاملة وموحدة. كيف يمكنني معرفة النماذج مفتوحة المصدر المتاحة لمهمة تلخيص النصوص؟ بالعودة لمهمة تلخيص النص التي تحدثنا عنها في بداية المقال مبدئيًّا، نوصي باستكشاف مكتبة HuggingFace التي تتضمّن دليلًا شاملًا للنماذج مفتوحة المصدر مُصنّفةً حسب المهمة، فهي ممتازة كنقطة انطلاق. يمكنك الدخول إلى هذا الرابط على سبيل المثال ورؤية العديد من النماذج الخاصة بهذه المهمة. طبعًا بعض هذه النماذج التي ستراها في القائمة بالتأكيد ستتضمّن نماذج اللغة الكبيرة LLMs، وكما تعلم فهذه النماذج تكون مصممة للأغراض العامة (النماذج اللغوية تُصمّم للتعامل مع العديد من المهام) وليس فقط مهمة تصنيف النص، لذا قد لاترغب بذلك، أو إذا كنت ترغب بذلك (الحل الأفضل)، فيتوجّب عليك تكييفها أو ضبطها Fine-tune على مهمة التلخيص المدروسة إن لم تكن مضبوطة عليها مُسبقًا. لكن في ظل هذا العدد الكبير جدًا من النماذج، قد تنتساءل: ما هي النماذج التي يجب أن أختارها؟ وتزداد الحيرة أكثر بسبب كون هذه النماذج مُدرّبة على أنواع مُختلفة من البيانات، فأحدها ربما يكون مُدرّبًا على تلخيص النشرات الإخباريّة، بالتالي يكون مُدرّبًا على بيانات تتضمّن نشرات أخبار؛ هذا يعني أن هذا النموذج يصلح فقط لتلخيص النصوص الإخباريّة وليس أي نوع آخر، والأسوأ أن بعض النماذج غير معروف ما نوع البيانات المُدرّبة عليها. والإجابة عن هذا السؤال هي "استخدام المقاييس metrics" التي تقيس جودة النموذج بالنسبة للمهمة المطلوبة. كيف يمكننا تقييم أداء نماذج تلخيص النصوص؟ يمكن أن تكون الخطوات التالية بمثابة إطار عام لتقييم أي نموذج كان بغض النظر عن المهمة المدروسة، أي سواء كانت مهمة تلخيص النص أم غيرها. تجدر الملاحظة أنّه من أجل أن يكون التقييم دقيقًا، يجب علينا اختبار أداء النموذج على أكثر من مجموعة بيانات، لكن من أجل البساطة، سنجرّب حاليًا على مجموعة بيانات واحدة فقط. ابحث عن مجموعة بيانات معياريّة (نموذجيّة) تُستخدم في تقييم النموذج المدروس وفقًا للمهمة المطلوبة. طبعًا عندما نقول نموذجيّة أو معياريّة، فهذا يعني أنها مُعتمدة وموثوقة وصالحة وخالية من التحيزات (إلى أكبر حد ممكن) وذات جودة عالية، وذلك لتتماشى مع مبادئ الاستخدام المسؤول للذكاء الاصطناعي. على سبيل المثال يكمن استخدام موقع Paper with code، فهو يوفر أفضل النماذج ومجموعات البيانات التي يُجري الباحثون تقييماتهم عليها. مثلًا هنا يمكنك أن تجد أبرز المجموعات المناسبة لمهمة تلخيص النص (فمجموعات البيانات التي ستراها في الرابط وفي الموقع عمومًا هي مجموعات بيانات معياريّة). التعرّف على المقاييس المُستخدمة لتقييم المهمة المطروحة. طبعًا هناك مقاييس مُحددة لكل مهمة، وقد يكون مقياسًا ما صالحًا لمهمة واحدة أو عدة مهام. إجراء التقييم باستخدام مقياس واحد أو أكثر، وعلى مجموعة بيانات واحدة أو أكثر (يُفضّل أكثر من واحدة). العثور على مجموعات البيانات كما ذكرت في الفقرة السابقة؛ أفضل طريقة هي الذهاب إلى موقع Papers With Code، فهو موقع موثوق تٌنشر فيه أبرز الأوراق البحثيّة في الذكاء الاصطناعي مع مستودعات الشيفرات البرمجية المرتبطة بها. اذهب أولاً إلى الموقع ثم إلى انتقل لقسم Datasets، ثم ابحث عن "Text Summarization" ثم انتقل إلى قسم Datasets واضغط على "توسيع أو See all" ومن هناك تظهر جميع مجموعات البيانات ذات الصلة مع إمكانية فرزها أو تصفيتها وفق رغبتك. في حالتنا نريد أن نفرزها حسب عدد مرات الاقتباس cite. نختار الآن مجموعة البيانات الأكثر اقتباسًا والتي غالبًا ماتكون الأكثر شعبية، وهي مجموعة بيانات "CNN/DailyMail" في وقت كتابة هذه المقالة. لاداعي لتحميل مجموعة البيانات حاليًّا، وإنما سنراجع المعلومات المُتعلقة بمجموعة البيانات هذه والموجودة على موقع Papers With Code، وذلك من أجل الخطوة التالية. تجدر الملاحظة إلى أن مجموعة البيانات هذه متاحة أيضًا على Huggingface. الآن عليك التحقق من 3 أشياء: الترخيص license: مجموعة البيانات مُرخصة من معهد ماساتشوستس للتكنولوجيا MIT، أي يمكننا استخدامها لكل من المشاريع التجارية والشخصية. الأوراق البحثية الأحدث على هذه البيانات: وذلك لمعرفة فيما إذا كانت الأوراق البحثية التي تستخدم هذه البيانات حديثة أم قديمة. لمعرفة ذلك نفرز الأوراق تنازليًّا. في حالتنا فإن الأوراق التي تستخدم هذه البيانات حديثة (من عام 2023)، وهذا مانرغب به! مدى توفر المعلومات المتعلقة بأصل مجموعة البيانات وعملية جمعها وشفافية الأساليب (مدى صراحة وشمولية توثيق الأساليب المستخدمة لجمع ومعالجة وتنظيم مجموعة البيانات) المستخدمة في إنشائها: مجموعة البيانات مُنشئة من قبل شركة IBM بالشراكة مع جامعة مونتريال، وذلك عظيم! فهذه مصادر موثوقة تلتزم بهذه المتطلبات. دعونا الآن نستكشف المقاييس المُستخدمة في تقييم أداء النماذج على مجموعة البيانات هذه. تقييم النماذج في هذه المرحلة يجب علينا استكشاف المقاييس التي يُشاع استخدامها من قبل الباحثين في تقييّم نماذج التلخيص على مجموعات البيانات. طبعًا يُفترض أن يكون لديك فهم نظري لمهمة تلخيص النص، وإلا فلن تكون قادرًا على فهم هذه المقاييس. هناك نوعان مختلفان لمهمة تلخيص النص (يمكن اعتبار كل نوع بمثابة مهمة فرعيّة)، النوع الأول هو "تلخيّص النص التجريدي Abstractive Text Summarization" والثاني "تلخيّص النص الاستخراجي Extractive Text Summarization" في النوع الأول يكون الهدف هو تلخيص النص من حيث المعنى، وهذا يعني أن يكون لدينا جمل جديدة، أما في النوع الثاني فيكون عبارة عن استخلاص مقتطفات بسيطة من النص. بالنسبة لمجموعة البيانات التي سنعمل عليها (مقالات CNN)، فهي مرتبطة بالنوع الأول. نلاحظ هنا العديد من المصطلحات وهي ROUGE-1 و ROUGE-2 و ROUGE-3، وهي المقاييس الشائعة لهذه المهمة الفرعية، كما نلاحظ العديد من النماذج ونتائجها وفقًا لهذه المقاييس الثلاث، وهو ما نحتاجه. الآن إذا قارنا بين هذه النماذج وفقًا للمقياس الأول، فنلاحظ أن أول 3 نماذج نتائجها متقاربة، حيث نلاحظ أن جميعها تقترب من 50 وهي قيمة واعدة وفقًا لمقياس ROUGE. اختبار النموذج بعد أن تعرّفنا على المقاييس التي يجب أخذها بعين الاعتبار لتقييم النماذج ورؤيتنا لأفضل ثلاثة منها، يمكننا الانتقال إلى مرحلة اختبار النماذج. تجدر الملاحظة أن هذه النماذج يُفضّل أن تُشغّل على وحدة معالجة الرسومات GPU لكونها تنفذ المهام بصورة متزامنة لتحسين الأداء (وبعضها قد لا يعمل إلا عليها)، لكن يمكن أن يكون بعضها قادرًا على العمل على وحدات CPU وبأداء مقبول. دعونا نختار نموذج Pegasus من بين تلك الخيارات، والأمر الذي يجعل اختيارنا موفقًا هو أن هذا النموذج جرى تدريبه على مجموعة بيانات CNN التي نعمل معها بالنسبة لمهمة "استخلاص النص التجريدي". بعد ذلك لنكتب الكود التالي بلغة البرمجة بايثون والذي يستخدم إطار عمل باي توش PyTorch للغة بايثون ومكتبة Transformers من Hugging Face ونموذج Pegasus لتلخيص النصوص: # We start by installing the transformers library %pip install transformers sentencepiece بعد ذلك علينا الوصول إلى نموذج Pegasus من مكتبة المحولات (مكتبة Huggingface): from transformers import PegasusForConditionalGeneration, PegasusTokenizer import torch # Set the seed, this will help reproduce results. Changing the seed will # generate new results from transformers import set_seed set_seed(248602) # We're using the version of Pegasus specifically trained for summarization # using the CNN/DailyMail dataset model_name = "google/pegasus-cnn_dailymail" # If you're following along in Colab, switch your runtime to a # T4 GPU or other CUDA-compliant device for a speedup device = "cuda" if torch.cuda.is_available() else "cpu" # Load the tokenizer tokenizer = PegasusTokenizer.from_pretrained(model_name) # Load the model model = PegasusForConditionalGeneration.from_pretrained(model_name).to(device) # Tokenize the entire content batch = tokenizer(content, padding="longest", return_tensors="pt").to(device) # Generate the summary as tokens summarized = model.generate(**batch) # Decode the tokens back into text summarized_decoded = tokenizer.batch_decode(summarized, skip_special_tokens=True) summarized_text = summarized_decoded[0] # Compare def compare(original, summarized_text): print(f"Article text length: {len(original)}\n") print(textwrap.fill(summarized_text, 100)) print() print(f"Summarized length: {len(summarized_text)}") compare(content, summarized_text) عند تنفيذ الكود سنحصل على تلخيص النص الأصلي content المكون من 1427 حرفًا، وكما تلاحظ يحتوي التلخيص summarized_text على 320 حرفًا فقط ويركز على العبارات والجمل الهامة في النص الأصلي. Article text length: 1427 Trustworthy AI should enable people to decide how their data is used.<n>values and goals of a system should be power aware and seek to minimize harm.<n>People should have agency and control over their data and algorithmic outputs.<n>Developers need to implement strong measures to protect our data and personal security. Summarized length: 320 تعمل الأمور بطريقة صحيحة، لكن يبدو أن النص المُلخّص موجزًا جدًا ويحتاج إلى تضمين المزيد من التفاصيل أو السياق. لنرى فيما إذا كان بالإمكان جعل المُلخّص أكثر شمولًا وتفصيلًا. لنقم بإجراء بعض التعديلات على الكود بهدف تغيير طول ومحتوى التلخيص الناتج، بداية لنغير قيمة متغير "البذرة seed" من 248602 إلى 860912 لتوليد نتائج جديدة كما يلي: ملاحظة: نستخدم مفهوم البذور seeds لتهيئة مولّد الأرقام العشوائية، حيث أنّه يحتاج إلى رقم يبدأ به (قيمة أولية) حتى يتمكن من إنشاء رقم عشوائي. افتراضيًّا يستخدم وقت النظام الحالي. set_seed(860912) # Generate the summary as tokens, with a max_new_tokens summarized = model.generate(**batch, max_new_tokens=800) summarized_decoded = tokenizer.batch_decode(summarized, skip_special_tokens=True) summarized_text = summarized_decoded[0] compare(content, summarized_text) ستكون النتيجة على النحو التالي: Article text length: 1427 Trustworthy AI should enable people to decide how their data is used.<n>values and goals of a system should be power aware and seek to minimize harm.<n>People should have agency and control over their data and algorithmic outputs.<n>Developers need to implement strong measures to protect our data and personal security. Summarized length: 320 يبدو أن الأمر لم ينجح أيضًا. لنحاول تجريب أسلوب فك تشفير يُسمّى "أخذ العينات sampling"، حيث يختار النموذج الكلمة التالية في النص بناءً على توزيعها الاحتمالي الشرطي (أي احتمال اختيار كلمة ما، بالنظر إلى الكلمات السابقة). هنا يكون لدينا مُتغير يسمى "درجة الحرارة temperature" للتحكم في مستوى العشوائية والإبداع في المخرجات المولّدة. هذه المعلمة تُعدّل الاحتمالات قبل أخذ العينات. تؤدي درجة الحرارة المرتفعة إلى زيادة العشوائية (أي ليس من الضروري أن يأخذ الكلمة الأكبر احتمالًا دومًا)، بالتالي مخرجات أكثر تنوعًا وإبداعًا، والعكس من ذلك يجعل عملية أخذ العينات أكثر حتمية ودقة. set_seed(118511) summarized = model.generate(**batch, do_sample=True, temperature=0.8, top_k=0) summarized_decoded = tokenizer.batch_decode(summarized, skip_special_tokens=True) summarized_text = summarized_decoded[0] compare(content, summarized_text) سنحصل الآن على النتيجة التالية: Article text length: 1427 Mozilla's "Trustworthy AI" Thinking Points:.<n>People should have agency and control over their data and algorithmic outputs.<n>Developers need to implement strong measures to protect our data. Summarized length: 193 نلاحظ أن الخرج أقصر، لكنه ذو جودة أعلى. ربما إذا رفعنا متغير درجة الحرارة temperature، ستكون النتيجة أفضل. set_seed(108814) summarized = model.generate(**batch, do_sample=True, temperature=1.0, top_k=0) summarized_decoded = tokenizer.batch_decode(summarized, skip_special_tokens=True) summarized_text = summarized_decoded[0] compare(content, summarized_text) هذا هو التخليص الناتج وهو كما تلاحظ أكثر وضوحًا من التلخيص السابق: Article text length: 1427 Mozilla's "Trustworthy AI" Thinking Points:.<n>People should have agency and control over their data and algorithmic outputs.<n>Developers need to implement strong measures to protect our data and personal security.<n>We need to mandate transparency so that we can fully understand these systems and their potential for harm. Summarized length: 325 دعونا نستخدم الآن طريقة فك تشفير تدعىtop_k، وهي طريقة تعتمد على أخذ الكلمات k الأعلى احتمالًا (الكلمات المُرشّحة)، ثم الاختيار عشوائيًّا بينها. يهدف هذا النهج إلى السماح للنموذج بأن يكون دقيقًا مع هامش من الإبداع بفضل إدخال العشوائية في الاختيار. والمعلمة k هي معلمة يجب ضبطها بدقة لتحقيق التوازن بين الدقة والإبداع، فمع القيم الكبيرة لها قد يبدو النص بلا معنى وأقل تماسكًا، ومع القيم الصغيرة ستكون النتائج حتميّة وخاليّة من أي إبداع، وقد تتضمّن جملًا مكررة. set_seed(226012) summarized = model.generate(**batch, do_sample=True, top_k=50) summarized_decoded = tokenizer.batch_decode(summarized, skip_special_tokens=True) summarized_text = summarized_decoded[0] compare(content, summarized_text) بعد القيام بالتغييرات السابقة لاحظ التلخيص الناتج: Mozilla's "Trustworthy AI" Thinking Points look at ethical issues surrounding automated decision making.<n>values and goals of a system should be power aware and seek to minimize harm.People should have agency and control over their data and algorithmic outputs.<n>Developers need to implement strong measures to protect our data and personal security. Summarized length: 355 دعونا الآن نجرّب نهجًا يدعى top-p أو أخذ عينات النواة Nucleus sampling، وهنا تشير p إلى قيمة حديّة، عند تجاوزها نتوقف عن ترشيح كلمات جديدة. بمعنى أوضح، تُفرز الكلمات تنازليًّا وفقًا لاحتمالاتها، ثم نبدأ باختيار الكلمات واحدة تلو الأخرى حتى يصبح مجموع احتمالاتها أكبر من p، ثم بعد ذلك نختار إحدى الكلمات المُرشحة عشوائيًا. توفّر هذه الطريقة أيضًا التوازن بين العشوائية والحتمية. فقيم p القريبة من 1 تعطي خرجًا أكثر تنوعًا والقيم القريبة من 0 أكثر حتمية. الفرق بينه وبين top_k هو أن عدد الكلمات المُرشّحة يكون ديناميكيًّا وأكثر قابليّة للتكيّف وفقًا للتوزيع الاحتمالي للكلمة التالية على طول مجموعة المفردات. set_seed(21420041) summarized = model.generate(**batch, do_sample=True, top_p=0.9, top_k=50) summarized_decoded = tokenizer.batch_decode(summarized, skip_special_tokens=True) summarized_text = summarized_decoded[0] compare(content, summarized_text) # saving this for later. pegasus_summarized_text = summarized_text سيكون التخليص الآن على النحو التالي: Article text length: 1427 Mozilla's "Trustworthy AI" Thinking Points:.<n>People should have agency and control over their data and algorithmic outputs.<n>Developers need to implement strong measures to protect our data and personal security.<n>We need to mandate transparency so that we can fully understand these systems and their potential for harm. Summarized length: 325 قد نتساءل ما هي الطريقة الأفضل لفك التشفير. لسوء الحظ، لا توجد طريقة "أفضل" لفك التشفير، ويعتمد اختيار النهج الأفضل على طبيعة البيانات التي تُنشئ مُلخصًا لها ونوع هذه النصوص، وعلى درجة الإبداع و الحتمية التي تريد أن تراها في النص الناتج. وبذلك نكون قد وصلنا إلى نهاية المقالة، حيث تحدّثنا فيها عن مهمة تلخيص النص، وعن كيفية استخدام أحدث النماذج لإنجاز ذلك. ترجمة -وبتصرُّف- للمقال Mozilla AI Guide Launch with Summarization Code Example لصاحبه Melissa Thermidor. اقرأ أيضًا مدخل إلى الذكاء الاصطناعي وتعلم الآلة. تعلم الآلة Machine Learning - الذكاء الاصطناعي. عشرة مشاريع عملية عن الذكاء الاصطناعي. أدوات برمجة نماذج تعلم الآلة.
  2. تُقدّم هذه المقالة مجموعةً من الأسئلة والأجوبة المتقدمة حول تخصص الذكاء الاصطناعي Artificial Intelligence. تشمل الأسئلة موضوعات عديدة مثل تأثير استخدام تقنيات الذكاء الاصطناعي وتعلّم الآلة Machine Learning، واستراتيجيات التعامل مع نماذج اللغة الكبيرة Large language models، ورؤى حول التطبيقات المستقبليّة للذكاء الاصطناعي. طُرحت هذه الأسئلة على مهندس ذكاء اصطناعي، ونظمت إجاباتها ضمن فقرات هذا المقال، ونرجو أن تقدم لكم المعرفة التي تفيدهم حول هذا التخصص الرائد. أسئلة حول التأثير الحالي والمستقبلي للذكاء الاصطناعي هناك العديد من الأسئلة التي تطرح نفسها حول التأثير الحالي والمستقبلي للذكاء الاصطناعي في العديد من المجالات والقطاعات. إليك أهم هذه الأسئلة وإجاباتها. ما هي التطبيقات والفوائد الجوهريّة للذكاء الاصطناعي في مجال الرعاية الصحية؟ يستخدم الذكاء الاصطناعي في العديد من التطبيقات ويعد الذكاء الاصطناعي جزءًا أساسيًّا في قطاع الرعاية الصحية في وقتنا الراهن، وهناك تمويل جيد لتعزيز تطبيق الذكاء الاصطناعي في هذا القطاع بهدف تطويره وتحسينه. ولعل أكثر الاتجاهات البحثية المثيرة للاهتمام المُتعلقة بهذا المجال هو استخدام تقنيات التعلّم العميق Deep Learning لاكتشاف الأدوية المناسبة (مثل اكتشاف المُركبات الدوائية ذات الخصائص المضادة لبكتيريا محددة) وسيعود بفوائد كبيرة لقطاع الرعاية الصحيّة، ويُعتقد أنها ستعطي دفعة كبيرة لمستقبل البشرية. لكن من ناحيةٍ أخرى، فإن أحد المخاوف المرتبطة باستخدام تقنيات الذكاء الاصطناعي في مجال الطب والرعاية الصحية هو بطء وتيرة إجراءات الموافقة القانونيّة على استخدام هكذا تقنيات، والتأخر في صياغة ووضع اللوائح التنظيميّة والقواعد ذات الصلة، مقارنًة بوتيرة التقدّم السريع للذكاء الاصطناعي. ماهي حدود قدرات التحليلات التنبؤية التي تُقدّمها أنظمة الذكاء الاصطناعي وما أفضل خوارزميات التحليل التنبؤي؟ قد يعتقد البعض أن بإمكان تقنيات الذكاء الاصطناعي الحاليّة أن تتنبأ بأي شيء تريده لكن هذا غير صحيح إطلاقًا في الوقت الحالي. فقدرة الذكاء الاصطناعي على التنبؤ تعتمد على عدة عوامل، مثل توفر البيانات الضرورية والملائمة للمهمة التي يُراد التنبؤ بها، ودرجة تعقيد المهمة فبعض المهام أكثر تعقيدًا وصعوبة من غيرها وجودة وفعالية النموذج المستخدم للتنبؤ. فإذا لم يكن النموذج جيدًا أو لم يتم تدريبه بشكل كافٍ، سيكون التنبؤ غير دقيق. بالنسبة للشق الثاني من السؤال والمتعلّق بأفضل خوارزميات الذكاء الاصطناعي، فالعديد من الخبراء يميلون إلى استخدام خوارزميات الشبكات العصبيّة Neural Networks في وقتنا الحالي، وهي خوارزميات مفيدة لكنها ليست بالضرورة هي الخيار الأفضل لكل المهام مثل حالات تحليل الانحدار Regression analysis التي تركز على توقع القيم المستقبلية استنادًا إلى العلاقات الإحصائية بين المتغيرات، أو الحالات التي تكون فيها البيانات مُنظّمة في جداول، في هذه الحالة تكون خوارزميات أشجار القرار Decision Trees وخوارزميات الغابات العشوائية Random Forests أنسب منها. كيف يمكن أن تحسن تقنيات الذكاء الاصطناعي مثل معالجة اللغة الطبيعية والرؤية الحاسوبيّة محركات البحث؟ تُطرَحُ تساؤلات عديدة حول تأثير تقنيات معالجة اللغة الطبيعية ونماذج اللغة الكبيرة مثل ChatGPT على سيو SEO محركات البحث، فهو موضوع يثير اهتمام العديد من الأشخاص والشركات، إذ يمكن أن يؤثر استخدام التقنيات الذكية في إنتاج المحتوى على أداء مواقع الويب في نتائج محركات البحث. ويُتوقّع أن الأفراد والشركات بعيدي النظر والذين يفهمون كيف تُدار الأعمال، سيعتمدون على استخدام تقنيات معالجة اللغة الطبيعية ونماذج اللغة الكبيرة والتحليلات الإحصائية المُعتمدة على الذكاء الاصطناعي في العديد من المهام مثل تحليل السوق ومراقبة وتحليل المنافسين، ويُعتقد أن هذه الأدوات والممارسات ستصبح مُنتشرة في المستقبل وستكون متاحة للجميع. هل من المتوقّع أن تُحدث شريحة الذكاء الاصطناعي التي كشفت عنها شركة AMD ثورةً في مجال الحوسبة؟ توفر شريحة AMD على معالجة البيانات بسرعة فائقة ويتوقع أن تكون مثالية لتطبيقات الذكاء الاصطناعي، لكن لا نمتلك حاليًا بيانات كافية لتحديد ما إذا كانت هذه الشريحة ستُحدث ثورة حقيقية في الحوسبة أم لا. لكنها بالتأكيد ستخلق جوًا من المنافسة بين الشركات التي تطوّر شرائح خاصة لمهام الذكاء الاصطناعي. ما هي أهمية تعلم المواضيع الأساسيّة في الذكاء الاصطناعي للجيل القادم؟ لقد أحدثت التطورات الأخيرة في الذكاء الاصطناعي ضجة كبيرة، وكثر الحديث عن أنها ستُغيّر شكل الصناعات وحياتنا عمومًا، وكما يبدو فالذكاء الصناعي قادر على ذلك وهو موجود ليبقى. لذا فنحن بحاجة فعلًا إلى تدريس أساسيات الذكاء الاصطناعي لطلاب الثانويّة وحتى الطلاب الأصغر سنًا، وأحد أهم المواضيع التي يجب أن تُدرّس، هي أن الذكاء الاصطناعي ليس سحرًا ولا يملك وعيًا، إنّه ببساطة علم يعتمد على الرياضيات وتكمن أهمية تعليم أدوات الذكاء الاصطناعي للجيل القادم، بأنه سيُمهّد الطريق لهم، ويعزّز رغبتهم بالانخراط في هذا العلم، ويجعلهم أكثر وعيًا بحقيقته، وما يمكنه ولا يمكنه فعله، فالإنسان يخاف مما يجهله بل هو عدو ما يجهله كما يقول المثل. كيف يمكن الاستفادة من الذكاء الاصطناعي والتعلم الآلي ونماذج اللغة الكبيرة في تطوير التطبيقات من الضروري لك لمطوّر ليس لديه خبرة في نظريات الذكاء الاصطناعي وتعلّم الآلة، البدء باستخدام هذه التقنيات في عمليات تطوير المنتجات، هناك أدوات سهلة الاستخادم وعالية المستوى تُغلّف كل التفاصيل البرمجية المُعقدة لكن يفضل أن يعتمد المطور عليها في بداية رحلته (لاحقًا نعم، لكن في البداية لابد أن تعرف كواليس تلك الأدوات والتنفيذات). أما إذا لم يكن هناك الوقت الكافي أو أن المرء لايرى شغفًا في الذكاء الاصطناعي أو التعلّم الآلي، فإن الأدوات الجاهزة عالية المستوى فكرة رائعة. كما تجدر الإشارة إلى أنه في الآونة الأخيرة حدث تطوّر كبير في أدوات الذكاء الاصطناعي، وهذا يجعل عمليّة التعلّم أصعب بالتأكيد، وفي نفس الوقت يجعل فكرة استخدام الأدوات الجاهزة أسهل. باختصار، اختر معاركك بحكمة. هل من الممكن تحسين نموذج لغة كبير LLM ليجيب على الأسئلة في الزمن الحقيقي؟ نعم بالتأكيد هناك إمكانية للتحسين. فالنماذج اللغوية الكبيرة هي نماذج معقدة تجري ضمنها العديد من العمليات الحسابيّة قبل إعطاء الخرج أو التنبؤ، وهذا مايترتب عليه تأخيرًا في الاستجابة، ويزداد هذا التأخير كلما زاد حجم النموذج أو الموارد اللازمة لتشغيل هذا النموذج (مثل وحدات معالجة الرسومات GPUs). كيف يمكن نشر النماذج اللغوية في بيئة الإنتاج؟ بالنسبة لشخص يريد نشر نموذج لغة كبير LLM لأغراض الإنتاج. قد تتضمّن خطة النشر إنشاء واجهة برمجة تطبيقات باستخدام FastAPI ونشرها إما على Hugging Face أو منصة سحابية أخرى. العامل المركزي المؤثر على القرار هو الميزانية المخصصة للمشروع. إذا كانت الميزانيّة كبيرة نسبيًّا يمكنك شراء وحدات GPUs باهظة الثمن من Amazon Web Services، وبالتالي ضمان أداء عالي المستوى. أما إذا كانت الميزانيّة التي لديك محدودة فربما استخدام FastAPI (إطار ويب سريع لبناء واجهات برمجة التطبيقات) ونموذج لغة أقل تعقيدًا مثل بيرت BERT سيكون خيارًا أقل كلفة، ولاسيما مع إمكانية تشغيله على وحدة المعالجة المركزية CPU بدلاً من GPU. أسئلة حول تطوير المهارات وتعلّم تطوير الذكاء الاصطناعي يطرح المهتمون يتطوير تطبيقات ذكية الكثير من التساؤلات حول المهارات الأساسية التي يحتاجون إليها لمواكبة التطورات ودمج تقنيات الذكاء الاصطناعي في تطبيقاتهم، نسلط الضوء في فقراتنا التالية على أهم هذه الأسئلة. ما هي المهارات الأساسية التي يجب أن يكتسبها المطوّر لينافس في ظل تطور الذكاء الاصطناعي؟ في ظل ظهور نماذج اللغات الكبيرة LLMs القادرة على كتابة التعليمات البرمجية تظهر مخاوف عديدة حول إمكانية الاستغناء عن المطورين والمبرمجين لكننا في الواقع لم نصل بعد إلى هذه النقطة حاليًا ولا يُتوقع أن الذكاء الاصطناعي سوف يتفوق في أمور مثل التعامل مع المواقف أو الظروف غير العادية أو غير النمطيّة أو متطلبات وتفضيلات المُستخدم الخاصة (على الرغم من أن هذا قد يحدث خلال السنوات العشر إلى الخمس عشرة القادمة). لذا يُنصح بتعلم كيفية استخدام الذكاء الاصطناعي التوليدي، فمن شأنه توفير الوقت والجهد في كتابة التعليمات البرمجية، وتوفير قدراتنا العقلية لإجراء مهام مثل التأكد من أن التعليمات البرمجية تعمل على النحو المنشود في السيناريوهات العملية المختلفة. بالتالي بدلاً من قضاء 40 ساعة في تطوير برنامج واحد، يمكن أن نعمل على تطوير 10 برامج في نفس المدة. ما هي الدورات والمهارات اللازم تعلّمها للانتقال إلى مجال تطوير نماذج اللغات الكبيرة؟ بالنسبة لشخص لديه معلومات حول أساسيات الذكاء الصناعي، يجب بدايةً التركيّز على أساسيات معالجة اللغة الطبيعيّة NLP، ثم الخوض واستكشاف النماذج اللغوية الكبيرة عبر منصات التعلّم عبر الإنترنت لفهم الأمور الأساسيّة المتعلّقة بها مثل التضمينات Embeddings والمحوّلات Transformers، ثم الانتقال إلى مكتبة HuggingFace والتعرّف عليها وعلى كيفيّة استخدام نماذج اللغة من خلالها، وهذا يفترض أن يكون أكثر سهولة بالنسبة لشخص لديه خلفية في الذكاء الصناعي ومكتباته. ماهي المصادر أو الأدوات أو أطر العمل أو المشاريع النموذجية لمهندسي الذكاء الاصطناعي؟ يقترح الخبراء أمرين رئيسيين غالبًا؛ الأمر الأول هو الدورات عبر الإنترنت التي توفّر لك مواردًا متنوعةً وقيّمة، أو في حال كنت جديدًا على مجال الذكاء الاصطناعي وعلوم البيانات.الأمر الثاني هو المشاركة في ورش العمل فهي تعمل على صقل ماتعلمته من خلال البدء في بناء وتجربة نماذج التعلّم الآلي والذكاء الاصطناعي. الخلاصة وصلنا لختام هذا المقال الذي أجبنا فيه على أبرز أسئلة الذكاء الاصطناعي، واستكشفنا فيه أهمية تعلم الذكاء الاصطناعي وأبرز المهارات الواجب تعلمها للتخصص في مجال الذكاء الاصطناعي ومصادر مفيدة لتعلم استخدام نماذج اللغات الكبيرة LLMs في التطبيقات المختلفة. هل لديك أي سؤال آخر وتود معرفته حول تخصص الذكاء الاصطناعي، لا تتردد في طرحه في قسم النقاش أسفل المقال. ترجمة -وبتصرُّف- للمقال Ask an AI Engineer: Trending Questions About Artificial Intelligence لصاحبه Joao Diogo de Oliveira. اقرأ أيضًا مدخل إلى الذكاء الاصطناعي وتعلم الآلة. تعلم الآلة Machine Learning - الذكاء الاصطناعي. عشرة مشاريع عملية عن الذكاء الاصطناعي. أدوات برمجة نماذج تعلم الآلة.
  3. يقود الذكاء الاصطناعي Artificial Intelligence في يومنا هذا طليعة التقدم التقني ويمثل قفزة ثورية في الطريقة التي يمكن بها للآلات وأنظمة الحاسب محاكاة الذكاء البشري وأداء المهام التي تتطلّب عادةً ذكاءًا بشريًّا. يشمل هذا المجال مجموعة واسعة من المجالات مثل الروبوتات ومعالجة اللغات الطبيعية والرؤية الحاسوبية والتعلم الآلي وعلوم البيانات ..إلخ، ويؤثر على جوانب مختلفة من المجتمع، من الأعمال والرعاية الصحية إلى الترفيه والتعليم. يستكشف هذا المقال إيجابيات وسلبيات الذكاء الاصطناعي، ويسلط الضوء على الطرق العديدة التي يؤثر بها على عالمنا والتحديات التي يفرضها، فعلى الرغم من أن الذكاء الاصطناعي يوفر إمكانيات مهمة لتحسين الكفاءة والدقة والابتكار، فإنه في نفس الوقت يثير المخاوف بشأن إزاحة الوظائف والتحيز وانتهاك الخصوصية والاعتبارات الأخلاقية الأخرى. إيجابيات الذكاء الاصطناعي يُقدم الذكاء الاصطناعي وتطبيقاته المتنوعة العديد من المزايا المهمة التي لها تأثيرات جذرية على مختلف القطاعات. يتعمق هذا القسم في الجوانب الإيجابية للذكاء الاصطناعي، ويسلط الضوء على كيفية إحداثه ثورة في الصناعات وتغيير الطريقة التي نعيش بها ونعمل بها. إيجابيات الذكاء الاصطناعي في مجال التعليم أدخل الذكاء الصناعي العديد من التطورات الإيجابية في مجال التعليم، مما أدى إلى تغيير الطريقة التي يتعلم بها الطلاب ويدرّس بها المعلمون ومن أهم هذه الفوائد. التعلم المخصّص: تستطيع خوارزميات الذكاء الاصطناعي تحليل أنماط التعلم الفردي وتفضيلاته، وتسمح بتوفير بتجارب تعليمية مخصصة. حيث يمكن للطلاب التقدم في وتيرة التعلم بالسرعة التي تناسبهم، مع التركيز على المجالات التي يحتاجون إلى تحسين فيها، وهذا ما يؤدي إلى عملية تعليمية أكثر كفاءة وفعالية. منصات التعلّم المتكفية: تعمل منصات التعلم المتكيفة المدعومة بالذكاء الاصطناعي على ضبط مستوى صعوبة المحتوى التعليمي في الزمن الحقيقي بناءً على أداء الطلاب. وهذا يضمن أن الطلاب يواجهون التحديات المناسبة لمستوياتهم مما يعزز فهمهم ومعرفتهم. أنظمة التدريس الذكية: توفر أنظمة التدريس المعتمدة على الذكاء الاصطناعي تعليقات وتوجيهات مخصصة للطلاب على غرار المعلم البشري. وتقدم هذه الأنظمة مساعدة فورية، حيث تساعد الطلاب في الواجبات المنزلية وحل المشكلات التي قد تصادفهم وتعزيز التعلم الذاتي. التقييم التلقائي: يمكن لخوارزميات الذكاء الاصطناعي تقييم المهام والاختبارات وتصنيفها بسرعة ودقة وتوفر وقت المعلمين، مما يسمح لهم بالتركيز على تقديم تعليقات مفيدة ودعم للطلاب. الفصول الافتراضية والتعلم عن بعد: يسهل الذكاء الاصطناعي الفصول الدراسية الافتراضية وتجارب التعلم عبر الإنترنت، كما تساعد روبوتات الدردشة الذكية الطلاب في الإجابة على أسئلتهم، وتقوم التحليلات المدعومة بالذكاء الاصطناعي بتتبع تقدم الطالب، بالتالي ضمان بيئة تعليمية سلسة وتفاعلية عن بعد. أدوات تعلم اللغات: تساعد أدوات تعلّم اللغات المستندة إلى الذكاء الاصطناعي متعلمي اللغة من خلال توفير الترجمة وتوجيهات النطق وتمارين اللغة، وتعمل هذه الأدوات على تعزيز مهارات اكتساب اللغة وتسهيلها. أنظمة التدخل المبكر: يمكن لخوارزميات الذكاء الاصطناعي تحديد الطلاب الذين يواجهون صعوبات أكاديمية وتتيح أنظمة التدخل المبكر للمعلمين تقديم الدعم في الوقت المناسب، مما يضمن حصول الطلاب على المساعدة التي يحتاجونها لتحقيق النجاح. إنشاء محتوى تعليمي: يستطيع الذكاء الاصطناعي إنشاء محتوى تعليمي، بما في ذلك عمليات المحاكاة التفاعلية والاختبارات والبرامج التعليمية. ويمكن للمعلمين الاستفادة من هذه الموارد لتعزيز أنشطة الفصل الدراسي وإشراك الطلاب في تجارب تعليمية تفاعلية. دعم ذوي الاحتياجات الخاصة: تعمل بعض تقنيات الذكاء الاصطناعي، مثل التعرف على الكلام وتطبيقات تحويل النص إلى كلام، على جعل المواد التعليمية في متناول الطلاب ذوي الاحتياجات الخاصة وتوفر بيئة تعليمية أكثر شمولاً، مما يضمن إمكانية مشاركة جميع فئات الطلاب بشكل كامل في العملية التعليمية. من خلال الاستفادة من قدرات الذكاء الاصطناعي، يمكن لمجال التعليم أن يتطور أكثر، وأن يقدم حلولًا مبتكرة تعزز نتائج التعلّم وتدعم المعلمين. زيادة الكفاءة والأتمتة يلعب الذكاء الاصطناعي دورًا أساسيًّا في تعزيز الكفاءة والأتمتة في مختلف القطاعات والصناعات. ويظهر تأثيرها الإيجابي بعدة طرق: أتمتة المهام: تعمل الأنظمة التي تعمل بالذكاء الصناعي على أتمتة المهام المتكررة، مما يقلل من التدخل البشري في الأنشطة العادية. لا يوفر هذا الوقت فحسب، بل يضمن أيضًا الدقة وتقليل الأخطاء التي قد تنشأ عن العمليات اليدوية. تقليل التكلفة: تؤدي أتمتة المهام من خلال تقنيات الذكاء الصناعي إلى خفض التكلفة بشكل كبير، حيث يمكن للشركات تحقيق إنتاج أعلى بموارد أقل، مما يؤدي إلى توفير كبير في التكاليف التشغيلية. السرعة والدقة: تعالج أنظمة الذكاء الصناعي المعلومات بسرعات غير مسبوقة، حيث يمكنهم تحليل بيانات ضخمة وإجراء حسابات معقدة بسرعة ودقة، مما يؤدي إلى عمليات اتخاذ قرار أسرع. العمل على مدار الساعة طوال أيام الأسبوع: يمكن للأدوات والأنظمة التي تعمل بالذكاء الصناعي أن تعمل على مدار الساعة دون تعب، مما يضمن استمرار الخدمة دون انقطاع. تعتبر هذه القدرة ذات قيمة خاصة في قطاعات مثل دعم العملاء والأمن السيبراني التي تحتاج إلى تواجد دائم ويقظة مستمرة. التخصيص: تتيح تقنيات الذكاء الصناعي تخصيص المنتجات والخدمات والتوصية بما يناسب منها بناءً على التفضيلات الفردية، وهذا ما يعزز تجارب العملاء، وبالتالي ارتفاع معدلات الرضا وزيادة ولاء العملاء للعلامة التجارية. مراقبة الجودة: في عمليات التصنيع والإنتاج، تقوم الأنظمة المدعومة بالذكاء الصناعي بمراقبة الجودة في الزمن الحقيقي. أي انحرافات أو عيوب تُكتشف على الفور، بالتالي ضمان وصول المنتجات عالية الجودة فقط إلى السوق. تحسين سير العمل: يقوم الذكاء الصناعي بتحليل أساليب سير العمل ويقترح التحسينات. بالتالي، ومن خلال تحديد الاختناقات وأوجه القصور في سير العمل، ويمكن للشركات إعادة تصميم العمليات لتعزيز الإنتاجية والفعالية بشكل عام. العمليات عن بعد: تُسهّل الأتمتة المعتمدة على الذكاء الصناعي العمليات والإدارة عن بعد. وهذه الميزة مفيدة بشكل خاص في قطاعات مثل المرافق، حيث يمكن مراقبة البنية التحتية والتحكم فيها من مواقع مركزية، بالتالي تعزيز الكفاءة والاستجابة عمومًا. إن زيادة الكفاءة والأتمتة من خلال تقنيات الذكاء الصناعي لا تعمل على تبسيط العمليات فحسب، بل تمهد الطريق أيضًا للابتكار، وهذا ما يسمح للشركات والصناعات بالازدهار في ظل هذا التنافس العالمي الكبير. تحسين عملية صنع القرار لقد أثر اندماج الذكاء الاصطناعي بشكل كبير على عمليات صنع القرار في مختلف القطاعات، وهذا يوفر العديد من المزايا: تحليل البيانات وتفسيرها: يمكن لخوارزميات الذكاء الاصطناعي معالجة كميات هائلة من البيانات في الزمن الحقيقي، وهذا ما يتيح للشركات جمع الرؤى من مجموعات البيانات الكبيرة بسرعة ودقة، مما يساعد في اتخاذ قرارات فعّالة. التحليلات التنبؤية: تستخدم أنظمة الذكاء الصناعي النمذجة التنبؤية لتوقع الاتجاهات المستقبلية للأسواق بناءً على البيانات السابقة. يساعد هذا التحليل التنبؤي الشركات على توقع تغيرات السوق وسلوكيات العملاء وأنماط الطلب، بالتالي إمكانية اتخاذ قرارات استباقية. إدارة المخاطر: تعمل خوارزميات الذكاء الاصطناعي على تقييم المخاطر من خلال تحليل البيانات السابقة. يساعد هذا التحليل الشركات في مختلف الصناعات (مثل شركات التمويل و التأمين) على تقييم المخاطر بشكل أكثر دقة وتطوير استراتيجيات فعالة لتخفيف المخاطر. كشف الاحتيال: يمكن لأنظمة الذكاء الصناعي اكتشاف العمليات المرتبطة بالأنشطة الاحتيالية في الزمن الحقيقي، وهذا مفيد بشكل خاص في القطاع المالي، حيث تقوم خوارزميات الذكاء الصناعي بتحليل المعاملات وسلوكيات المستخدم لتحديد الاحتيال المحتمل، بالتالي تجنبها من خلال التدخل السريع. إيجابيات الذكاء الاصطناعي في المجال الطبي لقد قدم الذكاء الصناعي العديد من المزايا التي تعزز رعاية المرضى وتبسّط العمليات وتساهم في البحوث الطبية. فيما يلي بعض الإيجابيات الرئيسية للذكاء الاصطناعي في المجال الطبي: الكشف المبكر عن الأمراض: يمكن لخوارزميات الذكاء الاصطناعي تحليل مجموعات واسعة من البيانات بما في ذلك السجلات الطبية وفحوصات التصوير، وذلك لاكتشاف حالات معينة وتحديد الأمراض المحتملة في مرحلة مبكرة، بالتالي المساعدة في الكشف المبكر والتدخل والعلاج في الوقت المناسب، وهذا ماينعكس إيجابًا على فرص الشفاء. التشخيص الدقيق: تعمل أدوات التشخيص الطبي المدعومة بالذكاء الاصطناعي، على تحسين دقة التشخيص وتساعد هذه الأدوات متخصصي الرعاية الصحية في تفسير الصور المعقدة مثل الأشعة السينية والرنين المغناطيسي، وبالتالي الحصول على تشخيصات أكثر دقة وموثوقية. خطط العلاج الشخصية: يقوم الذكاء الاصطناعي بتحليل بيانات المرضى لإنشاء خطط علاجية مخصصة لهم، وذلك من خلال النظر في العوامل الفردية للمريض مثل علم الوراثة والتاريخ الطبي ونمط الحياة. يساعد الذكاء الاصطناعي الأطباء على تصميم العلاجات لتحقيق أقصى قدر من الفعالية. اكتشاف الأدوية وتطويرها: يعمل الذكاء الاصطناعي على تسريع اكتشاف الأدوية من خلال تحليل البيانات البيولوجية والتنبؤ بالفعالية المحتملة للمركبات الجديدة. يؤدي هذا إلى تسريع عملية البحث والتطوير واكتشاف علاجات جديدة لمختلف الأمراض. التحليلات التنبؤية لنتائج المرضى: يمكن لمقدمي الرعاية الصحية استخدام أدوات التحليل التنبؤية للتدخل بشكل استباقي، بالتالي ضمان حصول المرضى على الرعاية والموارد المناسبة في الوقت المناسب. الجراحة الروبوتية: يمكن أن تساعد الأنظمة الروبوتية المعتمدة على الذكاء الاصطناعي الجراحين أثناء الإجراءات الجراحية المعقدة، مما يعزز الدقة ويقلل من الأخطاء البشرية ويحسن النتائج عمومًا، إذ يمكن لهذه الأنظمة أداء المهام بدقة عالية. إدارة بيانات الرعاية الصحية: يعمل الذكاء الصناعي على تبسيط إدارة بيانات الرعاية الصحية من خلال تنظيم وتحليل كميات هائلة من بيانات المرضى وأتمتتها. المراقبة عن بعد: تتيح بعض الأجهزة التي يمكن ارتداؤها والتي تعمل بالذكاء الاصطناعي -حلولًا للمراقبة عن بعد والتتبع المستمر للعلامات الحيوية للمرضى والمقاييس الصحية. يضمن ذلك التدخل في الوقت المناسب ويقلل الحاجة إلى الزيارات المتكررة إلى المستشفى. تحسين الأبحاث والتجارب السريرية: يمكن استخدام الذكاء الصناعي لمحاكاة التجارب السريرية، مما يوفر وقتًا وجهدًا ويقلل من التكلفة. كما يمكن للذكاء الصناعي الاستفادة من البيانات السريرية الضخمة وتحليلها بسرعة ودقة، مما يساعد الباحثين في فهم الأمراض والاستنتاجات الطبية. يوفر الذكاء الاصطناعي في المجال الطبي فرصًا لا مثيل لها لتعزيز تقديم الرعاية الصحية وتحسين نتائج المرضى ودفع عجلة التقدم الطبي. تمهد هذه التطورات الطريق لنظام رعاية صحية أكثر تخصيصًا وفعالية وسهولة في الحصول عليه. إيجابيات الذكاء الاصطناعي في البحث العلمي والتطوير يعمل الذكاء الاصطناعي على تسريع البحث العلمي من خلال معالجة مجموعات البيانات المعقدة ومحاكاة التجارب واكتشاف العلاقات المعقدة بين الأشياء. إنه يلعب دورًا هامًا في مجالات مثل اكتشاف الأدوية وعلوم المواد وعلم الجينوم وغيرها الكثير. إيجابيات الذكاء الاصطناعي في الأمن السيبراني تكتشف خوارزميات الذكاء الاصطناعي تهديدات الأمن السيبراني وتمنعها في الزمن الحقيقي Real Time. كما تعمل نماذج التعلم الآلي على تحليل سلوكيات الشبكة وتحديد الحالات الشاذة وحماية البيانات الحساسة، مما يضمن اتخاذ تدابير حماية قوية. تسلط هذه الإيجابيات الضوء على التأثير الكبير للذكاء الاصطناعي في مختلف مجالات الحياة، وهذا ما يمهد الطريق لحلول مبتكرة وتحسين الكفاءات في مختلف المجالات.لكن من ناحية أخرى وإلى جانب كل هذه الفوائد، يطرح الذكاء الاصطناعي تحديات وأسئلة أخلاقية تتطلب مراجعة دقيقة أيضًا. لنتناول في القسم التالي بعض هذه القضايا. دورة الذكاء الاصطناعي احترف برمجة الذكاء الاصطناعي AI وتحليل البيانات وتعلم كافة المعلومات التي تحتاجها لبناء نماذج ذكاء اصطناعي متخصصة. اشترك الآن سلبيات الذكاء الاصطناعي نظرا للتقدم الكبير في أبحاث الذكاء الاصطناعي، فقد أصبحنا على حافة عصر الروبوتات المستقلة وإنترنت الأشياء منذ عام 2005 تقريبا. بالتالي، فإننا نواجه بشكل متسارع تبعات الذكاء الاصطناعي في الحياة اليومية. فمن ينوي التخصص في مجال الذكاء الاصطناعي، أن يتعامل أيضًا مع التأثيرات والاعتبارات الأخلاقية والاجتماعية لهذا التخصص. إذًا وعلى الرغم من أن الذكاء الصناعي يجلب لعالمنا تطورات ملحوظة، ولكنه يجلب أيضًا تحديات وقضايا كبيرة. ويعد فهم هذه السلبيات أمرًا بالغ الأهمية لاتخاذ قرارات دقيقة بشأن استخدام الذكاء الاصطناعي وتنظيمه. الإزاحة الوظيفية والبطالة أدى ظهور الذكاء الاصطناعي إلى ظهور المخاوف بشأن تقلص فرص الوظائف وزيادة البطالة. وهذا ليس أمرًا حديثًا فمنذ يناير/كانون الثاني 2016، أصدر المنتدى الاقتصادي العالمي دراسة تنبأت بأن صعود "الصناعة 4.0" سيؤدي إلى إزاحة أكثر من خمسة ملايين وظيفة في غضون خمس سنوات.واليوم تزداد هذه المخاوف نظرا للأتمتة المتزايدة في مختلف القطاعات مثل المصانع والمكاتب ووسائل النقل والمنازل، والمعتمدة على الذكاء الاصطناعي بشكل كبير. على السطح، يبدو هذا الأمر بمثابة نعمة، حيث يحرر البشر من المهام المجهدة جسديًا وغير السارة. تَعِد الأتمتة بزيادة الكفاءة وانخفاض التكاليف وربما المزيد من الرخاء. لكن هذه الرؤية المثالية تصطدم بالواقع. فعلى الرغم من الإنتاجية المتزايدة، لا يزال الناس يعملون لساعات طويلة ويواجهون التوتر والإرهاق وانخفاض الأجور الحقيقية كما أن الضغوط التنافسية تجبر الشركات على خفض التكاليف، غالبًا عن طريق تسريح العمال، مما يؤدي إلى البطالة وزيادة الضغوط على القوى العاملة كما أن ارتفاع الإنتاجية لا ينعكس على العمال ولا يساهم في تحسين أجورهم ويتسبب في تراكم الثروة بين القلة تاركًا لأغلبية تكافح من أجل تغطية نفقاتها ومسببًا تفاوتًا في المستوى الاقتصادي بين طبقات المجتمع. المخاوف الأخلاقية والتحيزيّة تبرز المخاوف الأخلاقية والتحيزيّة في عالم الذكاء الاصطناعي كتحديات كبيرة تتطلب اهتمامًا عاجلًا. فإحدى القضايا البارزة هي التحيز الخوارزمي Algorithmic bias. إذ ترث أنظمة الذكاء الاصطناعي عن غير قصد في كثير من الأحيان التحيزات الموجودة في البيانات التي تدربت عليها ويمكن أن تؤدي هذه التحيزات التي تعكس التحيزات المجتمعية إلى معاملة غير عادلة في مختلف التطبيقات، مثل عمليات التوظيف أو أنظمة العدالة الجنائية. من جانبٍ آخر يشكل الافتقار إلى الشفافية في قرارات الذكاء الاصطناعي معضلة أخلاقية حيث تعمل العديد من خوارزميات الذكاء الاصطناعي كصناديق سوداء يصعب فهم كيفية وصولها إلى استنتاجات أو قرارات محددة ما يعيق القدرة على كشف وتصحيح نتائجها المتحيزة. كما أن المسؤولية في تطوير الذكاء الاصطناعي من الجوانب الهامة التي تحتاج إلى دراسة متأنية فتحديد المسؤول عندما يخطئ نظام الذكاء الاصطناعي أو يتسبب في ضرر ما أمر معقد، ولكنه ضروري لضمان الممارسات الأخلاقية والحماية من اتخاذ القرارات المتحيزة. إن معالجة هذه المخاوف الأخلاقية والتحيزية يتطلب اتباع نهج يتضمن الشفافية والمساءلة والتدقيق المستمر لبناء تطبيقات ذكاء اصطناعي عادلة وشفافة وخاضعة للمساءلة أمام المجتمع الذي تخدمه. التكاليف الأولية المرتفعة والاعتماد على التكنولوجيا يتطلب تنفيذ تقنيات الذكاء الاصطناعي استثمارات مالية كبيرة لذا تحتاج الشركات والمنظمات إلى تخصيص ميزانيات كبيرة للبحث والتطوير وتكامل أنظمة الذكاء الاصطناعي ضمن بنيتها التحتية الحالية. تشمل نفقات التنفيذ هذه التكاليف المرتبطة بتوظيف المتخصصين المهرة والحصول على الأجهزة المتقدمة وترخيص البرامج المتطورة ..إلخ. كما أن عمليات التحسين والصيانة المستمرة تشكل التزامات مالية مستمرة. تتطلب التحديثات المنتظمة وإصلاحات الأخطاء وتكييف أنظمة الذكاء الاصطناعي مع الاحتياجات المتطورة تمويلًا ثابتًا. من ناحية أخرى، هناك قلق بالغ بشأن مخاطر الاعتماد المفرط على أنظمة الذكاء الاصطناعي، ففي حين أن تكنولوجيا الذكاء الاصطناعي يمكن أن تعزز الكفاءة وعمليات صنع القرار، فإن الاعتماد المفرط عليها يمكن أن يؤدي إلى خلق نقاط ضعف. إن الاعتماد بشكل كبير على الذكاء الاصطناعي قد يؤدي إلى تآكل المهارات البشرية والتفكير النقدي، وقد يجعل الخبرة البشرية ثانوية بالنسبة للخوارزميات الآلية. لذا يثير هذا الاعتماد المفرط على الآلات الذكية تساؤلات حول العواقب طويلة المدى المترتبة على تضاؤل المشاركة البشرية في العمليات المهمة ولا شك أن تحقيق التوازن بين تبني إمكانات الذكاء الاصطناعي وفهم حدوده أمر ضروري لتجنب الاعتماد غير المبرر والمخاطر المرتبطة به. الافتقار إلى الذكاء العاطفي تكمن إحدى العيوب الكبيرة للذكاء الاصطناعي في افتقاره إلى الذكاء العاطفي وهي السمة المتجذّرة بعمق في التفاعلات البشرية. فأنظمة الذكاء الاصطناعي وعلى الرغم من خوارزمياتها وقدراتها المتقدمة، تكافح من أجل فهم مدى تعقيد المشاعر الإنسانية. ويبرز هذا القيد بشكل خاص في تطبيقات خدمة العملاء التي يعد التعاطف أمرًا محوريًا فيها في بعض الحالات. فحلول خدمة العملاء المعتمدة على الذكاء الاصطناعي وعلى الرغم من كفاءتها في التعامل مع الاستفسارات الروتينية، إلا أنها غالبًا ما تفشل في فهم الفروق الدقيقة في المشاعر الإنسانية، وهذا قد يشعر العملاء بالخيبة والإحباط. وفي قطاعي الرعاية الصحية، يشكل غياب الذكاء العاطفي في أدوات الذكاء الاصطناعي تحديًا كبيرًا. فقد يشعر المرضى الذين يبحثون عن فهم تعاطفي أثناء الاستشارات الطبية أو جلسات العلاج أن واجهات الذكاء الاصطناعي لا تفهمهم ولا تدرك ما يشعرون به، وهذا قد يؤثر على تجربة المريض بشكل عام. فعجز الذكاء الاصطناعي عن فهم تعقيدات المشاعر الإنسانية يثير أسئلة أخلاقية ويؤكد على أهمية دمج التعاطف البشري في تقنيات الذكاء الاصطناعي لتحسين جودة تفاعلات العملاء وتقديم خدمات الرعاية الصحية. المخاطر الأمنية وقضايا الخصوصية لقد جلب ظهور الذكاء الصناعي معه عددًا لا يحصى من المخاوف المتعلقة بالأمن والخصوصية. إحدى المشكلات الأكثر إلحاحًا هي التعرض لخروقات البيانات وتهديدات الأمن السيبراني، فمع اعتماد أنظمة الذكاء الصناعي بشكل متزايد على كميات هائلة من البيانات الحساسة، أصبح احتمال الوصول غير المصرح به والهجمات الضارة مصدر قلق كبير. تؤدي هذه الانتهاكات إلى تعريض المعلومات الشخصية للخطر، ويمكن أن يكون لها أيضًا عواقب بعيدة المدى تؤثر على الشركات والحكومات والأفراد على حدٍ سواء. إضافةً إلى ذلك فإن شبح الوصول غير المصرح به إلى أنظمة الذكاء الاصطناعي يلوح في الأفق. فقد يستغل المُخترقون Hackers ذوو النوايا الشريرة نقاط الضعف في خوارزميات الذكاء الاصطناعي أو البنى التحتية، ويحصلون على سيطرة غير مصرح بها على هذه الأنظمة. يثير هذا مخاوف بشأن سلامة البيانات ويشكل أيضًا مخاطر كبيرة على وظائف وسلامة التطبيقات التي تعتمد على الذكاء الاصطناعي، وخاصة تلك المستخدمة في القطاعات الحيوية مثل الرعاية الصحية أو التمويل. من جانب آخر، يؤدي انتشار الذكاء الاصطناعي في حياتنا اليومية إلى إثارة مخاوف بشأن انتهاكات الخصوصية إذ يمكن لتقنيات الذكاء الاصطناعي، وخاصة تلك الموجودة في كاميرات المراقبة والمنصات عبر الإنترنت، مراقبة وتحليل سلوك الأفراد، مما يؤدي إلى انتهاك الخصوصية على نطاق غير مسبوق. وقد شعلت هذه مخاوف إساءة استخدام البيانات الشخصية المناقشات حول الآثار الأخلاقية للذكاء الاصطناعي، ودفعت المجتمعات والحكومات إلى إصدار لوائح تنظيمية صارمة لحماية خصوصية الأفراد لتسخير إمكانات الذكاء الاصطناعي لتحقيق التقدم المجتمعي وليس للاستغلال أو الانتهاك. التأثير البيئي يعد التأثير البيئي للذكاء الاصطناعي مصدر قلق متزايد أيضًا. فإحدى القضايا المهمة هي استهلاك الطاقة الهائل المرتبط بأنظمة الذكاء الاصطناعي. إذ تتطلب الحسابات والخوارزميات التي تعمل في مراكز البيانات كميات كبيرة من الكهرباء، مما يساهم في ارتفاع انبعاثات الكربون وإجهاد موارد الطاقة العالمية. بالإضافة إلى ذلك، أدى انتشار أجهزة الذكاء الاصطناعي إلى زيادة كبيرة في النفايات الإلكترونية. فغالبًا ما ينتهي الأمر بأجهزة الذكاء الاصطناعي التي يتم التخلص منها، مثل الهواتف الذكية القديمة والأدوات المنزلية الذكية، في مدافن النفايات، وهذا ما يشكل مخاطر بيئية بسبب مكوناتها السامة والتخلص غير السليم من هذه الأجهزة يزيد من تلويث البيئة ويعرض النظم البيئية للخطر. عمومًا، هناك بصيص من الأمل وسط هذه التحديات، يتجلى من خلال ظهور مبادرات الاستدامة وممارسات "الذكاء الاصطناعي الأخضر". إذ تركز الشركات ومختبرات البحث اليوم على إنشاء تقنيات ذكاء اصطناعي موفرة للطاقة وصديقة للبيئة. وتتضمن هذه المبادرات تطوير الخوارزميات والأجهزة التي تتطلب طاقة حسابية أقل، وبالتالي تقليل استهلاك الطاقة. إضافةً إلى ذلك، هناك تركيز على إعادة التدوير والتخلص المسؤول من أجهزة الذكاء الاصطناعي لتقليل النفايات الإلكترونية، كما يجري استكشاف المواد المستدامة وعمليات التصنيع الجديدة لإنشاء أجهزة ذكاء اصطناعي صديقة للبيئة. إن الجهود المستمرة في مبادرات الاستدامة والذكاء الاصطناعي الأخضر تمهد الطريق لمستقبل أكثر مسؤولية من الناحية البيئية. من خلال الابتكار والوعي والتعاون، يمكن لصناعة التكنولوجيا والمجتمع ككل التخفيف من العواقب البيئية السلبية للذكاء الاصطناعي وضمان التعايش المتناغم بين التكنولوجيا والطبيعة. التنقل في المستقبل: التحديات والفرص في عصر السيارات الروبوتية غالبًا ما يُشار إلى فقدان سائقي سيارات الأجرة لوظائفهم على أنه أحد عيوب السيارات الآلية. من شبه المؤكد أنه لن يكون هناك سائقي سيارات أجرة اعتبارًا من عام 2030 فصاعدًا، لكن هذا ليس مشكلة بالضرورة. كما ذكرنا سابقًا، يحتاج مجتمعنا فقط إلى التعامل مع الإنتاجية المكتسبة حديثًا والتطورات بشكل صحيح. بالإضافة إلى المزايا العديدة التي تجلبها لنا السيارات ذاتية القيادة أو السيارات الآلية، تواجه هذه السيارات مشكلتين خطيرتين. أولًا، ما يسمى "بتأثير الارتداد" الذي سوف يلغي على الأقل بعض المكاسب في توفير الموارد والطاقة والوقت. ففترات القيادة الأقصر بالإضافة إلى القيادة الأكثر راحة والأرخص ستغرينا بالقيادة أكثر. ولا يمكننا التعامل مع هذه المشكلة إلا من خلال إعادة التفكير في موقفنا تجاه الاستهلاك ونوعية الحياة واستخدام كامل الوقت المدخر لمزيد من الأنشطة هناك مشكلة أخرى يجب أن نأخذها على محمل الجد وهي أن السيارات الآلية ستحتاج إلى أن تكون متصلة بالشبكة وهذا قد يمنح المخترقين القدرة على الوصول إلى عناصر التحكم في المركبات والتلاعب بها من خلال الثغرات الأمنية في بروتوكولات الشبكة. إذا تمكن أحد المخترقين من القيام بذلك مرة واحدة، فيمكنه تكرار الهجوم على نطاق واسع، مما قد يؤدي إلى توقف أساطيل المركبات بأكملها أو التسبب في وقوع حوادث أو التجسس على ركاب المركبات أو الشروع في أعمال إجرامية أخرى. هنا كما هو الحال في مجالات أخرى مثل التشغيل الآلي للمنزل وإنترنت الأشياء، ستكون هناك حاجة إلى خبراء في أمن تكنولوجيا المعلومات لضمان أعلى ضمانات أمنية ممكنة باستخدام أدوات التجارة مثل أساليب التشفير. بالمناسبة، ستكون خوارزميات التعلم الآلي مفيدة في اكتشاف هجمات القرصنة. بعد أن ناقشنا أهم إيجابيات وسلبيات الذكاء الاصطناعي يمكن القول أن الراحة والرخاء التي سينتجها لنا الذكاء الاصطناعي لا يخلو من كثير من التحديات التي علينا مواجهتها لتجنب أي خطر قد يعرض البشرية والكوكب للخطر. خاتمة عند الحديث عن الذكاء الاصطناعي، يتضح لنا أن تقنيات الذكاء الاصطناعي تقدم فوائد ملحوظة مثل زيادة الكفاءة وحلول الرعاية الصحية المتقدمة والعمليات التجارية المبسطة ..إلخ، إلا أن ذلك يترافق مع تحديات من أبرزها إزاحة الوظائف والمخاوف الأخلاقية والمخاطر الأمنية. يعد فهم هذه الإيجابيات والسلبيات أمرًا بالغ الأهمية لاتخاذ قرارات مستنيرة بشأن اندماج الذكاء الاصطناعي بطريقة آمنة في حياتنا والحرص على تحقيق التوازن بين تسخير فوائد الذكاء الصناعي والتخفيف من تحدياته. كما يتعين على الشركات وصناع السياسات أن يعملوا بشكل تعاوني لتحقيق الممارسات الأخلاقية ومعالجة التحيزات وضمان الشفافية. ويتعين على الباحثين الحرص على توفير حلول ذكاء اصطناعي أخلاقية وآمنة وغير متحيزة بجانب تطويره، وإنشاء مبادرات تعليمية لتعزيز الفهم العام للذكاء الاصطناعي، كما ينبغي على صناع السياسات أن يتعاونوا على مستوى العالم لإنشاء لوائح تنظيمية قوية تضمن التطوير الأخلاقي والاستخدام الصحيح للذكاء الاصطناعي وبناء مستقبل يفيد فيه الذكاء الاصطناعي البشرية ككل. اقرأ أيضًا تعلم الذكاء الاصطناعي أهمية الذكاء الاصطناعي مجالات الذكاء الاصطناعي فوائد الذكاء الاصطناعي برمجة الذكاء الاصطناعي مستقبل الذكاء الاصطناعي لغات برمجة الذكاء الاصطناعي
  4. الرؤية الحاسوبيّة Computer vision هي مجال فرعي من علوم الحاسوب Computer science، تهدف إلى استخراج معلومات عالية المستوى من الصور ومقاطع الفيديو. يتضمن هذا المجال مهام مثل اكتشاف الكائنات في صورة أو فيديو وتحسين واستعادة الصور (إكمال المصفوفة) وتتبع العناصر في المقاطع المتحركة. ويُغذّي هذا المجال العديد من التقنيات، بما في ذلك مرشحات الدردشة المرئية video chat filters والتعرّف على الوجه (مصادقة الوجه Face Authentication) في الأجهزة المحمولة والسيارات ذاتية القيادة. نستخدم خلال هذه المقالة الرؤية الحاسوبيّة لإنشاء مُترجم للغة الإشارة الأمريكية واستخدامها مع كاميرا الويب الخاصة بك. نستخدم خلال هذه المقالة مكتبة الرؤية الحاسوبية الشهيرة أوبين سي في OpenCV وإطار العمل بايتورش PyTorch لبناء شبكة عصبية عميقة، والنظام البيئي أونكس ONNX لتصدير الشبكة العصبية التي سوف نبنيها. نُطبّق المفاهيم التالية أثناء إنشاء تطبيق الرؤية الحاسوبيّة خاصتنا: نتبع طريقة من ثلاث خطوات وهي: المعالجة المسبقة لمجموعة البيانات ثم تدريب النموذج وأخيرًا تقيّيم النموذج. تحسين كل خطوة من خلال استخدام تقنية زيادة أو تعزيز البيانات Data augmentation لمعالجة حالات الأيدي المدورة أو غير المتمركزة (قد تكون الأيدي في زوايا أو اتجاهات مختلفة) وتغيير جداول مُعدّل التعلّم Learning rate schedule لتحسين دقة النموذج وتصدير النماذج لزيادة سرعة الاستدلال. نستكشف أيضًا المفاهيم ذات الصلة بتعلّم الآلة Machine Learning. بعد الانتهاء من هذه المقالة، سيكون لديك مُترجم لغة الإشارة الأمريكية ومعرفة أساسيّة بالتعلّم العميق Deep Learning. يمكنك أيضًا الوصول إلى الشيفرة المصدرية الكاملة لهذا المشروع هنا. المتطلبات الأساسية لإكمال هذه المقالة سوف تحتاج إلى ما يلي: بيئة تطوير محلية لبايثون Python 3 مع ذاكرة وصول عشوائي RAM لا تقل عن 1 جيجابايت. يمكنك قراءة إحدى المقالات التالية لإعداد كل ما تحتاجه حسب نظام التشغيل الذي تنوي العمل عليه: كيفية تثبيت وإعداد بيئة برمجية محلية لبايثون 3 على نظام أبونتو، كيفية تثبيت وإعداد بيئة برمجية محلية لبايثون 3 على نظام ويندوز 10. كاميرا ويب لاكتشاف الصور في الزمن الحقيقي Real time. الخطوة 1 -إنشاء المشروع وتثبيت التبعيات نُنشئ الآن مُجلّد عمل لهذا المشروع ونُثبّت التبعيات التي نحتاجها. في توزيعات لينكس Linux، ابدأ بإعداد مُدير حزم النظام الخاص بك وثبّت حزمة virtualenv. استخدم: $ apt-get update $ apt-get upgrade $ apt-get install python3-venv سوف نسمي مُجلّد العمل SignLanguage: $ mkdir ~/SignLanguage ننتقل إلى المجلد: $ cd ~/SignLanguage أنشئ بيئة افتراضية جديدة للمشروع: $ python3 -m venv signlanguage نشّط البيئة: $ source signlanguage/bin/activate ثبّت بايتورش PyTorch، وهو إطار عمل للتعلم العميق في بايثون. ثبّت بايتورش على نظام التشغيل ماك أو إس macOS باستخدام الأمر التالي: (signlanguage) $ python -m pip install torch==1.2.0 torchvision==0.4.0 استخدم الأوامر التالية من أجل بناء النموذج على وحدة المعالجة المركزية CPU سواءًا في لينكس أو ويندوز: (signlanguage) $ pip install torch==1.2.0+cpu torchvision==0.4.0+cpu -f https://download.pytorch.org/whl/torch_stable.html (signlanguage) $ pip install torchvision نُثبّت الآن الحزم أوبين سي في opencv ونمباي numpy وأونكس onnx، وهي مكتبات الرؤية الحاسوبيّة والجبر الخطي وتصدير وتنفيذ نماذج الذكاء الاصطناعي على التوالي. تُقدّم opencv أدوات مساعدة مثل تدوير الصور والتقاطها والتعديل عليها، وتُقدّم numpy أدوات مساعدة للجبر الخطي مثل انعكاس وضرب المصفوفات: (signlanguage) $ python -m pip install opencv-python==3.4.3.18 numpy==1.14.5 onnx==1.6.0 onnxruntime==1.0.0 في توزيعات لينكس، ستحتاج إلى تثبيت libSM.so: (signlanguage) $ apt-get install libsm6 libxext6 libxrender-dev بعد تثبيت التبعيات، دعونا نبني الإصدار الأول من مُترجم لغة الإشارة: مُصنّف لغة الإشارة. الخطوة 2 -إعداد مجموعة بيانات تصنيف لغة الإشارة في الأقسام الثلاثة التالية، ستبني مُصنّف لغة الإشارة باستخدام شبكة عصبية. هدفك هو إنتاج نموذج يقبل صورة اليد كمدخل ويخرج حرفًا. الخطوات الثلاث التالية مطلوبة لبناء نموذج تعلّم آلي لمهمة التصنيف التي لدينا: المعالجة المسبقة للبيانات: تطبيق تشفير الواحد النشط One-hot encoding على التسميات Labels وتغليف البيانات بموَتِّرات بايتورش (أو تينسرات بايتورش) PyTorch Tensors . تدريب النموذج على البيانات الإضافية من عملية الزيادة augmentation، وذلك لإعداده للإدخال غير العادي، أي عندما تكون اليد في أوضاع غير قياسيّة، مثل اليد غير المتمركزة أو المدورة. تحديد النموذج وتدريبه: إنشاء شبكة عصبية باستخدام بايتورش وتحديد معلمات التدريب العليا hyper-parameters -مثل مدة التدريب- وتشغيل خوارزميّة النزول الاشتقاقي العشوائي stochastic gradient descent. تتضمّن هذه المرحلة أيضًا تعديل إحدى معلمات التدريب العليا أثناء عملية التدريب، وهي قيمة مُعدّل التعلّم learning rate، وذلك من خلال جدول مُعدّل التعلّم، فكما ذكرنا سابقًا؛ هذا سوف يرفع دقة النموذج. التنبؤ باستخدام النموذج: تقييم الشبكة العصبية على بيانات التحقق لفهم دقتها. ثم تصدير النموذج إلى تنسيق يسمى ONNX للحصول على استدلال أسرع. ملاحظات: تشفير الواحد النشط عبارة عن أشعة Vectors كل شعاع تكون جميع قيمه أصفار ماعدا بعد واحد فقط من الشعاع تكون قيمته واحد. قيمة الواحد تُشير إلى شيء معين. في جالتنا تشير إلى الصنف الذي يُعبر عن العينة المرتبطة به. تُشير موَتِّرات بايتورش PyTorch Tensors إلى هيكل البيانات الأساسي في بايتورش، تُشبه مصفوفات نمباي NumPy مع وظائف إضافية مُحسّنة من أجل دعم عمليات التعلّم العميق Deep Learning. تُعتبر مكونًا أساسيًا يُتيح التنفيّذ الفعّال لحسابات الشبكة العصبية. في هذا القسم من المقالة ستنجز الخطوة الأولى التي تتضمّن تجهيز مجموعة البيانات ومعالجتها. هذا يتضمّن تنزيل البيانات وإنشاء كائن Dataset لتغليفها والتكرار عليها وتطبيق تقنية زيادة البيانات أخيرًا. في نهاية هذه الخطوة، سيكون لديك طريقة برمجية للوصول إلى الصور والتسميات الموجودة في مجموعة البيانات الخاصة بك لتغذية النموذج. أولاً، عليك تنزيل مجموعة البيانات إلى مجلد العمل الحالي الخاص بك: ملاحظة: في نظام التشغيل ماك، لا يتوفر wget افتراضيًا. لذلك يجب عليك تثبيت Homebrew ثم تشغيل برنامج Brew install wget. (signlanguage) $ wget https://assets.digitalocean.com/articles/signlanguage_data/sign-language-mnist.tar.gz فك ضغط الملف المضغوط الذي يحتوي على مجلد /data: (signlanguage) $ tar -xzf sign-language-mnist.tar.gz أنشئ ملفًا جديدًا باسم step_2_dataset.py: (signlanguage) $ nano step_2_dataset.py يجب عليك الآن استيراد الأدوات المساعدة الضرورية، ثم إنشاء الصنف الذي سيُغلّف مجموعات بيانات التدريب والاختبار ويعالجها بالشكل المناسب، وذلك من خلال تنفيذ الواجهة Dataset الخاصة ببايتورش، هذا يسمح لك بتحميل واستخدام خط معالجة (أو خط أنابيب) البيانات المدمج في بايتورش مع مجموعة بيانات تصنيف لغة الإشارة خاصتك: from torch.utils.data import Dataset from torch.autograd import Variable import torch.nn as nn import numpy as np import torch import csv class SignLanguageMNIST(Dataset): """مجموعة بيانات تصنيف لغة الإشارة تُستخدم هذه الفئة كأداة لتحميل مجموعة بيانات لغة الإشارة في بايتورش """ pass يُمثّل الصنف SignLanguageMNIST مجموعة بيانات تصنيف لغة الإشارة. إنها أداة مساعدة لتحميل مجموعة بيانات لغة الإشارة إلى بايتورش. مجموعة البيانات المستخدمة منشورة على موقع كاغل Kaggle في عام 2017، لمؤلف لم يُذكر اسمه الصريح، لكن اسم المستخدم هو tecperson. كل عينة عبارة عن مصفوفة من الشكل 28281*1، وكل تسمية Label هي قيمة عددية scalar. احذف الآن العنصر النائب pass من الصنف SignLanguageMNIST. وضع مكانه تابعًا لتوليد قائمة بالتسميات المتوفرة في مجموعة البيانات اسمها mapping، وذلك لربط كل تسمية بفهرس: @staticmethod def get_label_mapping(): """ تُعيد قائمة تربط بين تسميات مجموعة البيانات وفهارس الحروف تحويل نطاق فهارس التسميات الأصلية إلى نطاق جديد يبدأ من 0 حتى 23 """ mapping = list(range(25)) mapping.pop(9) return mapping تحتوي التسميات الأصلية في مجموعة البيانات على قيم تتراوح من 0 إلى 25. وتتوافق كل قيمة مع حرف معين، باستثناء الحرفين J و Z المستبعدين. لذلك يوجد 24 تسمية صالحة في النطاق الأصلي (من 0 إلى 25 باستثناء 9 و25). الهدف من هذه الشيفرة هو تعديل مجال قيم التسميات إلى مجال آخر، أي تحويل نطاق فهارس التسميات الأصلية إلى نطاق جديد يبدأ من 0 ويكون متجاورًا حتى 23. وبعبارة أخرى، نطاق الفهارس الجديد هو [0، 1، 2، .. ، 23]، وذلك للتأكد من أن قيم التسمية تكون في تسلسل مضغوط متجاور. بشكل أوضح سيكون الخرج هو القائمة التالية: [0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24] نلاحظ أن قيم هذه القائمة تبدأ بالعدد 0 الذي يُشير للحرف A ويقابل الفهرس 0، وتنتهي مع العدد 24 والذي يشير للحرف Y ويقابل الفهرس 23. هذا يُسهل المعالجة والتفسير في خطوات المعالجة اللاحقة. أضف بعد ذلك تابعًا لاستخراج التسميات والعينات من ملف CSV. تفترض الشيفرة التالية أن كل سطر من ملف CSV يبدأ بالتسمية (أي العمود الأول) ثم يتبعه قيم 784 بكسل (باقي الأعمدة). تُمثّل قيم 784 بكسل هذه صورة مقاسها 28 × 28، وكل بكسل تتراوح قيمته بين 0-255: @staticmethod def read_label_samples_from_csv(path: str): """ يُمثّل التسمية وباقي القيم 2^28 هي قيم البكسلات CSV نفترض أن العمود الأول من ملف قيم البكسلات تتراوح بين 0 إلى 255 """ mapping = SignLanguageMNIST.get_label_mapping() labels, samples = [], [] with open(path) as f: _ = next(f) # skip header for line in csv.reader(f): label = int(line[0]) labels.append(mapping.index(label)) samples.append(list(map(int, line[1:]))) return labels, samples لاحظ أن كل سطر من الكائن csv.reader القابل للتكرار عبارة عن قائمة من السلاسل. لهذا السبب نحن نستدعي الدالة int و map لتحويل جميع السلاسل إلى أعداد صحيحة. نضع بعد هذا التابع الثابت static مباشرةً، تابعًا آخر من أجل تهيئة حامل البيانات لدينا: def __init__(self, path: str="data/sign_mnist_train.csv", mean: List[float]=[0.485], std: List[float]=[0.229]): """ المعطيات: path: الذي يحتوي مجموعة البيانات .csv يُستخدم من أجل تحديد مسار ملف """ labels, samples = SignLanguageMNIST.read_label_samples_from_csv(path) self._samples = np.array(samples, dtype=np.uint8).reshape((-1, 28, 28, 1)) self._labels = np.array(labels, dtype=np.uint8).reshape((-1, 1)) self._mean = mean self._std = std يبدأ هذا التابع بتحميل العينات والتسميات. ثم يُغلّف البيانات بمصفوفات نمباي. نشرح معلومات المتوسط والانحراف المعياري قريبًا، في قسم __getitem__ التالي. ملاحظة: ملف csv. الذي يحتوي مجموعة البيانات أعمدته هي: label, pixel0, pixel1, … , pixeln نضيف التابع __len__ بعد التابع __init__ مباشرةً. تتطلب مجموعة البيانات هذه طريقة لتحديد متى يجب إيقاف التكرار على البيانات، وهذه الطريقة هي التابع __len__ الذي يخبرها بآخر عنصر في مجموعة البيانات: ... def __len__(self): return len(self._labels) أخيرًا، أضف التابع __getitem__ الذي يُرجع قاموسًا Dict يحتوي على العينة والتسمية: def __getitem__(self, idx): transform = transforms.Compose([ transforms.ToPILImage(), transforms.RandomResizedCrop(28, scale=(0.8, 1.2)), transforms.ToTensor(), transforms.Normalize(mean=self._mean, std=self._std)]) return { 'image': transform(self._samples[idx]).float(), 'label': torch.from_numpy(self._labels[idx]).float() } نحن نستخدم تقنية تُعرف باسم زيادة البيانات أثناء التدريب، حيث تخضع العينات لتغييرات لتعزيز مرونة النموذج في فهم هذه الاختلافات (مثلًا حتى لو قمنا بقلب الصورة رأسًا على عقب أو كبّرنا الصورة أو غيرنا بعض الألوان، يجب أن يفهم النموذج أن جميعها تنتمي لنفس التسمية). على وجه التحديد، نُجري عمليات تكبير الصورة عشوائيًا على مستويات ومواضع مختلفة باستخدام RandomResizeCrop. من المهم تسليط الضوء على أن عملية التكبير هذه يجب أن لا تؤثر على تسمية لغة الإشارة النهائية، أي يجب أن تظل التسمية دون تغيير. بالإضافة إلى ذلك، يمكنك تسوية أو تقييس Normalize المدخلات، مما يضمن أن قيم بكسلات الصورة تقع ضمن النطاق [0، 1] بدلاً من [0، 255]. لتحقيق ذلك، استخدم المتوسط والانحراف المعياري لمجموعة البيانات (mean_ و std_) أثناء عملية التقييس. سيكون الصنف SignLanguageMNIST الكامل كما يلي: from torch.utils.data import Dataset from torch.autograd import Variable import torchvision.transforms as transforms import torch.nn as nn import numpy as np import torch from typing import List import csv class SignLanguageMNIST(Dataset): """مجموعة بيانات تصنيف لغة الإشارة تُستخدم هذه الفئة كأداة لتحميل مجموعة بيانات لغة الإشارة في بايتورش """ @staticmethod def get_label_mapping(): """ تُعيد قائمة تربط بين تسميات مجموعة البيانات وفهارس الحروف تحويل نطاق فهارس التسميات الأصلية إلى نطاق جديد يبدأ من 0 حتى 23 """ mapping = list(range(25)) mapping.pop(9) return mapping @staticmethod def read_label_samples_from_csv(path: str): """ يُمثّل التسمية وباقي القيم 2^28 هي قيم البكسلات CSV نفترض أن العمود الأول من ملف قيم البكسلات تتراوح بين 0 إلى 255 """ mapping = SignLanguageMNIST.get_label_mapping() labels, samples = [], [] with open(path) as f: _ = next(f) # skip header for line in csv.reader(f): label = int(line[0]) labels.append(mapping.index(label)) samples.append(list(map(int, line[1:]))) return labels, samples def __init__(self, path: str="data/sign_mnist_train.csv", mean: List[float]=[0.485], std: List[float]=[0.229]): """ المعطيات: path: الذي يحتوي مجموعة البيانات .csv يُستخدم من أجل تحديد مسار ملف """ labels, samples = SignLanguageMNIST.read_label_samples_from_csv(path) self._samples = np.array(samples, dtype=np.uint8).reshape((-1, 28, 28, 1)) self._labels = np.array(labels, dtype=np.uint8).reshape((-1, 1)) self._mean = mean self._std = std def __len__(self): return len(self._labels) def __getitem__(self, idx): transform = transforms.Compose([ transforms.ToPILImage(), transforms.RandomResizedCrop(28, scale=(0.8, 1.2)), transforms.ToTensor(), transforms.Normalize(mean=self._mean, std=self._std)]) return { 'image': transform(self._samples[idx]).float(), 'label': torch.from_numpy(self._labels[idx]).float() } ستتحقق الآن من توابع الأداة المساعدة لمجموعة البيانات الخاصة بنا عن طريق تحميل مجموعة بيانات SignLanguageMNIST. أضف الشيفرة التالية إلى نهاية ملفك بعد الصنف SignLanguageMNIST: def get_train_test_loaders(batch_size=32): trainset = SignLanguageMNIST('data/sign_mnist_train.csv') trainloader = torch.utils.data.DataLoader(trainset, batch_size=batch_size, shuffle=True) testset = SignLanguageMNIST('data/sign_mnist_test.csv') testloader = torch.utils.data.DataLoader(testset, batch_size=batch_size, shuffle=False) return trainloader, testloader من خلال هذه الشيفرة تجري عملية تهيئة مجموعة البيانات باستخدام الصنف SignLanguageMNIST، ثم بالنسبة لمجموعات التدريب والتحقق، تُغلّف مجموعة البيانات في DataLoader. يؤدي هذا إلى تحويل مجموعة البيانات إلى نسخة قابلة للتكرار لاستخدامها لاحقًا أثناء تدريب النموذح. تتحقق الآن من أن الأدوات المساعدة لمجموعة البيانات تعمل، وذلك من خلال استدعاء الدالة get_train_test_loaders التي تُرجع مُحملي بيانات من الصنف DataLoader. نكتفي بواحد منهم للاختبار، ونسنده للمتغير loader، ثم نطبع السطر الأول من هذا المُحمّل. أضف ما يلي إلى نهاية ملفك: if __name__ == '__main__': loader, _ = get_train_test_loaders(2) print(next(iter(loader))) يمكنك التحقق من أن ملفك يطابق ملف step_2_dataset الموجود في هذا المستودع. اخرج من المحرر الخاص بك وشغّل البرنامج بما يلي: (signlanguage) $ python step_2_dataset.py يؤدي هذا إلى إخراج الزوج التالي من الموترات tensors. يقوم خط معالجة البيانات الخاص بنا بإخراج عينتين وتسميتين. يشير هذا إلى أن خط معالجة البيانات الخاص بنا جاهز للانطلاق: {'image': tensor([[[[ 0.4337, 0.5022, 0.5707, ..., 0.9988, 0.9646, 0.9646], [ 0.4851, 0.5536, 0.6049, ..., 1.0502, 1.0159, 0.9988], [ 0.5364, 0.6049, 0.6392, ..., 1.0844, 1.0844, 1.0673], ..., [-0.5253, -0.4739, -0.4054, ..., 0.9474, 1.2557, 1.2385], [-0.3369, -0.3369, -0.3369, ..., 0.0569, 1.3584, 1.3242], [-0.3712, -0.3369, -0.3198, ..., 0.5364, 0.5364, 1.4783]]], [[[ 0.2111, 0.2796, 0.3481, ..., 0.2453, -0.1314, -0.2342], [ 0.2624, 0.3309, 0.3652, ..., -0.3883, -0.0629, -0.4568], [ 0.3309, 0.3823, 0.4337, ..., -0.4054, -0.0458, -1.0048], ..., [ 1.3242, 1.3584, 1.3927, ..., -0.4054, -0.4568, 0.0227], [ 1.3242, 1.3927, 1.4612, ..., -0.1657, -0.6281, -0.0287], [ 1.3242, 1.3927, 1.4440, ..., -0.4397, -0.6452, -0.2856]]]]), 'label': tensor([[24.], [11.]])} لقد تحققت الآن من أن خط معالجة البيانات الخاص بك يعمل. بهذا تنتهي الخطوة الأولى -المعالجة المسبقة لبياناتك- والتي تتضمّن الآن تقنية زيادة البيانات لزيادة قوة النموذج. بعد ذلك سوف نبني الشبكة العصبية والمُحسّن. الخطوة 3: بناء وتدريب مصنف لغة الإشارة باستخدام التعلم العميق باستخدام خط معالجة بيانات فعّال، ستتمكن الآن من بناء نموذج وتدريبه على البيانات. على وجه التحديد ستبني شبكة عصبية من ست طبقات وتعرّف دالة الخسارة والمحسّن، وأخيرًا أمثلة تحسين optimize دالة الخسارة لتنبؤات الشبكة العصبية الخاصة بك. في نهاية هذه الخطوة، سيكون لديك مُصنّف لغة إشارة فعّال. أنشئ ملفًا جديدًا يُسمى step_3_train.py: (signlanguage) $ nano step_3_train.py قم باستيراد الأدوات التي تحتاجها: from torch.utils.data import Dataset from torch.autograd import Variable import torch.nn as nn import torch.nn.functional as F import torch.optim as optim import torch from step_2_dataset import get_train_test_loaders نُعرّف الآن شبكة عصبية باستخدام بايتورش تتضمن ثلاث طبقات تلافيفية Convolutional Layers، تليها ثلاث طبقات متصلة بالكامل Fully Connected Layers. أضف الشيفرة التالية إلى نهاية البرنامج الموجود لديك: class Net(nn.Module): def __init__(self): super(Net, self).__init__() self.conv1 = nn.Conv2d(1, 6, 3) self.pool = nn.MaxPool2d(2, 2) self.conv2 = nn.Conv2d(6, 6, 3) self.conv3 = nn.Conv2d(6, 16, 3) self.fc1 = nn.Linear(16 * 5 * 5, 120) self.fc2 = nn.Linear(120, 48) self.fc3 = nn.Linear(48, 24) def forward(self, x): x = F.relu(self.conv1(x)) x = self.pool(F.relu(self.conv2(x))) x = self.pool(F.relu(self.conv3(x))) x = x.view(-1, 16 * 5 * 5) x = F.relu(self.fc1(x)) x = F.relu(self.fc2(x)) x = self.fc3(x) return x نُهيّئ الآن الشبكة العصبية ونُعرّف دالة الخسارة ونحدد معلمات التحسين العليا عن طريق إضافة الشيفرة التالية إلى نهاية البرنامج: def main(): net = Net().float() criterion = nn.CrossEntropyLoss() optimizer = optim.SGD(net.parameters(), lr=0.01, momentum=0.9) أخيرًا، تُدرّب النموذج على دورتين: def main(): net = Net().float() criterion = nn.CrossEntropyLoss() optimizer = optim.SGD(net.parameters(), lr=0.01, momentum=0.9) trainloader, _ = get_train_test_loaders() for epoch in range(2): # loop over the dataset multiple times train(net, criterion, optimizer, trainloader, epoch) torch.save(net.state_dict(), "checkpoint.pth") تشير دورة تدريب أو "epoch" إلى دورة واحدة كاملة عبر مجموعة البيانات التدريبية خلال مرحلة التدريب. خلال كل دورة تدريب تُعرض كل عينات البيانات الموجودة في مجموعة البيانات التدريبية مرة واحدة تمامًا على النموذج. في نهاية الدالة main تُحفظ معلمات النموذج في ملف يسمى checkpoint.pth. أضف التعليمة البرمجية التالية إلى نهاية البرنامج الخاص بك لاستخراج الصورة والتسمية من مُحمل مجموعة البيانات ثم غلّف كل منهما في متغير بايتورش: def train(net, criterion, optimizer, trainloader, epoch): running_loss = 0.0 for i, data in enumerate(trainloader, 0): inputs = Variable(data['image'].float()) labels = Variable(data['label'].long()) optimizer.zero_grad() # انتشار أمامي + انتشار عكسي + تحسيّن outputs = net(inputs) loss = criterion(outputs, labels[:, 0]) loss.backward() optimizer.step() # طباعة الإحصاءات running_loss += loss.item() if i % 100 == 0: print('[%d, %5d] loss: %.6f' % (epoch, i, running_loss / (i + 1))) تُنفّذ هذا الشيفرة أيضًا عملية الانتشار الأمامي على الشبكة العصبية ثم تحسب قيمة الخسارة ثم تُنفّذ الانتشار العكسي وخطوة المُخسّن. في نهاية الملف، أضف ما يلي لاستدعاء الدالة الرئيسية main: if __name__ == '__main__': main() تحقق جيدًا من تطابق ملفك مع ما يلي: from torch.utils.data import Dataset from torch.autograd import Variable import torch.nn as nn import torch.nn.functional as F import torch.optim as optim import torch from step_2_dataset import get_train_test_loaders class Net(nn.Module): def __init__(self): super(Net, self).__init__() self.conv1 = nn.Conv2d(1, 6, 3) self.pool = nn.MaxPool2d(2, 2) self.conv2 = nn.Conv2d(6, 6, 3) self.conv3 = nn.Conv2d(6, 16, 3) self.fc1 = nn.Linear(16 * 5 * 5, 120) self.fc2 = nn.Linear(120, 48) self.fc3 = nn.Linear(48, 25) def forward(self, x): x = F.relu(self.conv1(x)) x = self.pool(F.relu(self.conv2(x))) x = self.pool(F.relu(self.conv3(x))) x = x.view(-1, 16 * 5 * 5) x = F.relu(self.fc1(x)) x = F.relu(self.fc2(x)) x = self.fc3(x) return x def main(): net = Net().float() criterion = nn.CrossEntropyLoss() optimizer = optim.SGD(net.parameters(), lr=0.01, momentum=0.9) trainloader, _ = get_train_test_loaders() for epoch in range(2): # loop over the dataset multiple times train(net, criterion, optimizer, trainloader, epoch) torch.save(net.state_dict(), "checkpoint.pth") def train(net, criterion, optimizer, trainloader, epoch): running_loss = 0.0 for i, data in enumerate(trainloader, 0): inputs = Variable(data['image'].float()) labels = Variable(data['label'].long()) optimizer.zero_grad() # انتشار أمامي + انتشار عكسي + تحسيّن outputs = net(inputs) loss = criterion(outputs, labels[:, 0]) loss.backward() optimizer.step() # طباعة الإحصاءات running_loss += loss.item() if i % 100 == 0: print('[%d, %5d] loss: %.6f' % (epoch, i, running_loss / (i + 1))) if __name__ == '__main__': main() احفظ الملف، ثم ابدأ تجربتك الأولية عن طريق تشغيل الأمر التالي ولاحظ الخرج الناتج: (signlanguage) $ python step_3_train.py [0, 0] loss: 3.208171 [0, 100] loss: 3.211070 [0, 200] loss: 3.192235 [0, 300] loss: 2.943867 [0, 400] loss: 2.569440 [0, 500] loss: 2.243283 [0, 600] loss: 1.986425 [0, 700] loss: 1.768090 [0, 800] loss: 1.587308 [1, 0] loss: 0.254097 [1, 100] loss: 0.208116 [1, 200] loss: 0.196270 [1, 300] loss: 0.183676 [1, 400] loss: 0.169824 [1, 500] loss: 0.157704 [1, 600] loss: 0.151408 [1, 700] loss: 0.136470 [1, 800] loss: 0.123326 للحصول على قيم خسارة أقل، يمكنك زيادة عدد الدورات إلى 5 أو 10 أو حتى 20 دورة. ومع ذلك، بعد فترة معينة من التدريب، ستتوقف قيمة الخسارة في الشبكة عن الانخفاض حتى لو زدنا عدد الدورات (نكون قد علقنا في إحدى القيم الصغرى المحليّة). لتجنب هذه المشكلة، نحتاج إلى تخفيض قيمة مُعدّل التعلّم مع ازدياد عدد دورات التدريب. يمكننا إجراء ذلك من خلال جدولة قيم مُعدّل التعلّم، بحيث نُقلل من معدل التعلم بمرور الوقت. نُعدّل الدالة main بإضافة السطرين التاليين اللذين يتضمنان تعريف المجدول scheduler واستدعاء scheduler.step. إضافة إلى ذلك، ارفع عدد الدورات إلى 12: def main(): net = Net().float() criterion = nn.CrossEntropyLoss() optimizer = optim.SGD(net.parameters(), lr=0.01, momentum=0.9) scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=10, gamma=0.1) trainloader, _ = get_train_test_loaders() for epoch in range(12): # loop over the dataset multiple times train(net, criterion, optimizer, trainloader, epoch) scheduler.step() torch.save(net.state_dict(), "checkpoint.pth") تأكد من تطابق ملفك مع ملف الخطوة 3 الموجود في هذا المستودع. يستمر التدريب لمدة 5 دقائق تقريبًا. سيشبه الخرج الخاص بك ما يلي: [0, 0] loss: 3.208171 [0, 100] loss: 3.211070 [0, 200] loss: 3.192235 [0, 300] loss: 2.943867 [0, 400] loss: 2.569440 [0, 500] loss: 2.243283 [0, 600] loss: 1.986425 [0, 700] loss: 1.768090 [0, 800] loss: 1.587308 ... [11, 0] loss: 0.000302 [11, 100] loss: 0.007548 [11, 200] loss: 0.009005 [11, 300] loss: 0.008193 [11, 400] loss: 0.007694 [11, 500] loss: 0.008509 [11, 600] loss: 0.008039 [11, 700] loss: 0.007524 [11, 800] loss: 0.007608 الخسارة النهائية التي حصلنا عليها هي 0.007608، وهي أصغر بثلاث مرات من قيمة الخسارة في أول دورة تدريبية 3.20. نختم بهذا الخطوة الثانية من سير العمل لدينا، حيث بنينا الشبكة العصبية ودرّبناها. رغم أن قيمة الخسارة هذه صغيرة، إلا أنها قد لاتعطينا نظرة ثاقبة عن أداء النموذج. لذا سنحسب دقة Accuracy النموذج، وهي النسبة المئوية للصور التي صنفها النموذج بطريقة صحيحة. الخطوة 4 -تقييم مصنف لغة الإشارة نُقيّم الآن مصنف لغة الإشارة عن طريق حساب دقته على مجموعة التحقق، وهي مجموعة من الصور لم يراها النموذج أثناء التدريب، أي أنه لايعرفها. سيوفر هذا إحساسًا أفضل بأداء النموذج مقارنةً بقيمة الخسارة النهائية وحدها. علاوة على ذلك، سنضيف أدوات مساعدة لحفظ نموذجنا المُدرّب في نهاية التدريب وتحميل نموذجنا المدرّب مسبقًا عند إجراء الاستدلال. أنشئ ملفًا جديد يسمى step_4_evaluate.py: (signlanguage) $ nano step_4_evaluate.py استيراد الأدوات التي تحتاجها: from torch.utils.data import Dataset from torch.autograd import Variable import torch.nn as nn import torch.nn.functional as F import torch.optim as optim import torch import numpy as np import onnx import onnxruntime as ort from step_2_dataset import get_train_test_loaders from step_3_train import Net عرّف بعد ذلك أداةً مساعدةً لتقييم أداء الشبكة العصبية. تُجري الدالة التالية مقارنةً بين الحرف المتوقع من قبل الشبكة العصبية والحرف الحقيقي لصورة واحدة مُمثّلًا بقيمته الأصلية (أي 1،2،3 … ) وليس بترميز الواحد النشط. def evaluate(outputs: Variable, labels: Variable) -> float: """تقييم جودة توقعات النموذج، مقارنةً بالتسميات الصحيحة""" Y = labels.numpy() Yhat = np.argmax(outputs, axis=1) return float(np.sum(Yhat == Y)) المتغير outputs عبارة عن قائمة باحتمالات الصنف لكل عينة. على سبيل المثال، قد تكون مخرجات عينة واحدة هي [0.1، 0.3، 0.4، 0.2]. المتغير labels عبارة عن قائمة تحمل التسميات المقابلة للعينات. على سبيل المثال، قد تكون التسمية من أجل عينة واحدة هي 3. يحوّل السطر ()Y = labels.numpy التسميات إلى مصفوفة نمباي. بينما يحوّل السطر الذي يليه مباشرةً القيم الاحتمالية من أجل كل عينة إلى قيمة عددية واحدة تقابل فهرس أكبر قيمة احتمالية من أجل تلك العينة. على سبيل المثال، إذا كانت قائمة الاحتمالات من أجل عينة واحدة هي [0.1، 0.3، 0.4، 0.2] فإنها يحولها إلى القيمة 2، لأن قيمة الفهرس 2 البالغة 0.4 هي أكبر قيمة. نظرًا لأن كلا من Y و Yhat أصبحا الآن يشيران إلى صنفين، فيمكنك مقارنتهما. يتحقق Yhat == Y مما إذا كانت التسمية المتوقعة تُطابق التسمية الصحيحة، ويعيد قائمة كل عنصر فيها إما True أو False من أجل كل عينة. أخيرًا ((float(np.sum(Yhat == Y تحسب عدد حالات التطابق بين Y و Yhat (أي كم مرة حصلنا على True). أضف الدالة الثانية batch_evaluate، والتي تُطبّق الدالة evaluate على جميع الصور: def batch_evaluate( net: Net, dataloader: torch.utils.data.DataLoader) -> float: """تقييم الشبكة العصبية على دفعات، إذا كانت مجموعة البيانات كبيرة جدًا""" score = n = 0.0 for batch in dataloader: n += len(batch['image']) outputs = net(batch['image']) if isinstance(outputs, torch.Tensor): outputs = outputs.detach().numpy() score += evaluate(outputs, batch['label'][:, 0]) return score / n الدفعة batch عبارة عن مجموعة من الصور المخزنة كموتر واحد. أولاً، نزيد إجمالي عدد الصور التي نُقيّمها n بعدد الصور في هذه الدفعة من خلال التعليمة ([n += len(batch['image. ثم نُنفّذ عملية الاستدلال على الشبكة العصبية باستخدام مجموعة من الصور. ونُجري عملية التحقق من النوع بالتعليمة (if isinstance(outputs; torch.Tensor فإذا كان نوع outputs هو torch.Tensor نحول outputs إلى مصفوفة نمباي. أخيرًا، يمكنك استخدام evaluate لحساب عدد العينات المُصنّفة بطريقة صحيحة، وفي ختام الدالة يمكنك حساب النسبة المئوية للعينات التي صنفتها بطريقة صحيحة من خلال التعليمة evaluate. أخيرًا، أضف البرنامج التالي للاستفادة من الأدوات المساعدة السابقة: def validate(): trainloader, testloader = get_train_test_loaders() net = Net().float() pretrained_model = torch.load("checkpoint.pth") net.load_state_dict(pretrained_model) print('=' * 10, 'PyTorch', '=' * 10) train_acc = batch_evaluate(net, trainloader) * 100. print('Training accuracy: %.1f' % train_acc) test_acc = batch_evaluate(net, testloader) * 100. print('Validation accuracy: %.1f' % test_acc) if __name__ == '__main__': validate() من خلال هذه الشيفرة، تجري عمليّة تحميل الشبكة العصبية المُدرّبة مسبقًا وتقييم أدائها على مجموعة بيانات لغة الإشارة المتوفرة. على وجه التحديد، يطبع البرنامج دقة النموذج في توقع أصناف الصور التي المُستخدمة للتدريب، كما أنه يطبع دقة النموذج في توقع أصناف الصور التي لم يجري استخدامها أثناء عملية التدريب، وهي مجموعة منفصلة من الصور التي تضعها جانبًا لأغراض الاختبار، وتسمى مجموعة التحقق. نُصدّر بعد ذلك نموذج بايتورش إلى ملف ONNX ثنائي. يمكن بعد ذلك استخدام هذا الملف الثنائي في الإنتاج لتنفيذ عمليات الاستدلال مع النموذج الخاص بك. والأهم من ذلك، أن الشيفرة التي تُشغّل هذا الثنائي لا تحتاج إلى نسخة من تعريف الشبكة الأصلي. في نهاية دالة validate، أضف ما يلي: trainloader, testloader = get_train_test_loaders(1) # export to onnx fname = "signlanguage.onnx" dummy = torch.randn(1, 1, 28, 28) torch.onnx.export(net, dummy, fname, input_names=['input']) # check exported model model = onnx.load(fname) onnx.checker.check_model(model) # check model is well-formed # create runnable session with exported model ort_session = ort.InferenceSession(fname) net = lambda inp: ort_session.run(None, {'input': inp.data.numpy()})[0] print('=' * 10, 'ONNX', '=' * 10) train_acc = batch_evaluate(net, trainloader) * 100. print('Training accuracy: %.1f' % train_acc) test_acc = batch_evaluate(net, testloader) * 100. print('Validation accuracy: %.1f' % test_acc) يؤدي هذا إلى تصدير نموذج ONNX والتحقق من النموذج الذي جرى تصديره، ثم تشغيل الاستدلال على النموذج الذي صُدّر. تحقق جيدًا من تطابق ملفك مع ملف الخطوة 4 الموجود في هذا المستودع: from torch.utils.data import Dataset from torch.autograd import Variable import torch.nn as nn import torch.nn.functional as F import torch.optim as optim import torch import numpy as np import onnx import onnxruntime as ort from step_2_dataset import get_train_test_loaders from step_3_train import Net def evaluate(outputs: Variable, labels: Variable) -> float: """تقييم جودة توقعات النموذج، مقارنةً بالتسميات الصحيحة""" Y = labels.numpy() Yhat = np.argmax(outputs, axis=1) return float(np.sum(Yhat == Y)) def batch_evaluate( net: Net, dataloader: torch.utils.data.DataLoader) -> float: """تقييم الشبكة العصبية على دفعات، إذا كانت مجموعة البيانات كبيرة جدًا""" score = n = 0.0 for batch in dataloader: n += len(batch['image']) outputs = net(batch['image']) if isinstance(outputs, torch.Tensor): outputs = outputs.detach().numpy() score += evaluate(outputs, batch['label'][:, 0]) return score / n def validate(): trainloader, testloader = get_train_test_loaders() net = Net().float().eval() pretrained_model = torch.load("checkpoint.pth") net.load_state_dict(pretrained_model) print('=' * 10, 'PyTorch', '=' * 10) train_acc = batch_evaluate(net, trainloader) * 100. print('Training accuracy: %.1f' % train_acc) test_acc = batch_evaluate(net, testloader) * 100. print('Validation accuracy: %.1f' % test_acc) trainloader, testloader = get_train_test_loaders(1) # export to onnx fname = "signlanguage.onnx" dummy = torch.randn(1, 1, 28, 28) torch.onnx.export(net, dummy, fname, input_names=['input']) # check exported model model = onnx.load(fname) onnx.checker.check_model(model) # check model is well-formed # create runnable session with exported model ort_session = ort.InferenceSession(fname) net = lambda inp: ort_session.run(None, {'input': inp.data.numpy()})[0] print('=' * 10, 'ONNX', '=' * 10) train_acc = batch_evaluate(net, trainloader) * 100. print('Training accuracy: %.1f' % train_acc) test_acc = batch_evaluate(net, testloader) * 100. print('Validation accuracy: %.1f' % test_acc) if __name__ == '__main__': validate() لاستخدام نقطة التحقق checkpoint من الخطوة الأخيرة وتقييمها، شغّل ما يلي: (signlanguage) $ python step_4_evaluate.py سيؤدي هذا إلى الحصول على مخرجات مشابهة لما يلي، مما يؤكد أن النموذج الذي صدرته لا يعمل فحسب، بل يتوافق أيضًا مع نموذج بايتورش الأصلي الخاص بك أيضًا: ========== PyTorch ========== Training accuracy: 99.9 Validation accuracy: 97.4 ========== ONNX ========== Training accuracy: 99.9 Validation accuracy: 97.4 حققت شبكتك العصبية دقة تصل إلى 99.9% على بيانات التدريب ودقة تبلغ 97.4% على بيانات التحقق. تُشير هذه الفجوة بين دقة التدريب والتحقق إلى أن النموذج الخاص بك أفرط في ضبط قيم معلماته على بيانات التدريب، وهذا ماندعوه بالضبط الزائد أو الإفراط في المُلائمة Overfitting. هذا يعني أنه بدلاً من تعلم الأنماط التي يمكن تعميمها على جميع الصور (بما في ذلك صور العالم الحقيقي) ، فإن النموذج الخاص بك تعلّم أنماطًا خاصة للغاية ببيانات التدريب، أي وكأنه حفظ البيانات ولم يفهمها (حافظ مش فاهم). في هذه المرحلة، أكملنا بناء مُصنّف لغة الإشارة. يمكن لنموذجنا أن يُميّز الإشارات بطريقة صحيحة طوال الوقت تقريبًا. هذا نموذج جيد إلى حد معقول، لذلك ننتقل إلى المرحلة النهائية من تطبيقنا. سوف نستخدم مصنف لغة الإشارة هذا في تطبيق كاميرا الويب في الزمن الحقيقي. الخطوة 5 -الربط مع الكاميرا هدفك التالي هو ربط كاميرا الحاسوب بمصنف لغة الإشارة الخاص بك. يتضمن ذلك التقاط المُدخلات من الكاميرا ثم تصنيف لغة الإشارة المعروضة ثم عرض معنى الإشارة للمستخدم. أنشئ الآن برنامجًا بلغة بايثون لاكتشاف الوجه. أنشئ الملف step_6_camera.py باستخدام المحرر nano أو محرر النصوص المفضل لديك: (signlanguage) $ nano step_5_camera.py أضف الشيفرة التالية إليه: """اختبار تصنيف لغة الإشارة""" import cv2 import numpy as np import onnxruntime as ort def main(): pass if __name__ == '__main__': main() تستورد هذا الشيفرة مكتبة OpenCV المُشار إليها بالاسم cv2. تحتوي هذه المكتبة على العديد من الأدوات اللازمة لمعالجة الصور. تستورد أيضًا onnxruntime وهي كل ما تحتاجه لتشغيل الاستدلال على النموذج الخاص بك. ما تبقى من التعليمات البرمجية هو البنية المبدأية لبرنامج بايثون الذي سنكتبه. الآن استبدل pass في الدالة main بالشيفرة التالية، التي تُهيئ مُصنف لغة الإشارة بالمعلمات التي دربنا النموذج معها مسبقًا. أربط أيضًا الفهارس بالحروف وحدد الإحصائيات المتعلقة بالمعالجة المسبقة للصور: def main(): # الثوابت index_to_letter = list('ABCDEFGHIKLMNOPQRSTUVWXY') mean = 0.485 * 255. std = 0.229 * 255. # إنشاء جلسة قابلة للتشغيل باستخدام النموذج المُصدَّر ort_session = ort.InferenceSession("signlanguage.onnx") ستستخدم عناصر من برنامج اختبار موجودة في وثائق OpenCV الرسمية، بالتالي ستحتاج إلى تحديث شيفرة الدالة main. ابدأ بتهيئة كائن VideoCapture الذي يلتقط البث المباشر من كاميرا حاسوبك. ضع ذلك في نهاية الدالة main: def main(): ... # إنشاء جلسة قابلة للتشغيل باستخدام النموذج المُصدَّر ort_session = ort.InferenceSession("signlanguage.onnx") cap = cv2.VideoCapture(0) ثم أضف حلقة while لتقرأ من الكاميرا في كل خطوة زمنية: def main(): ... cap = cv2.VideoCapture(0) while True: # التقط إطارًا بإطار ret, frame = cap.read() اكتب دالة مساعدة للاحتفاظ بالمنطقة الوسطى فقط مع التخلص من المناطق المحيطة من إطار الكاميرا. ضع هذه الدالة قبل الدالة main: def center_crop(frame): h, w, _ = frame.shape start = abs(h - w) // 2 if h > w: frame = frame[start: start + w] else: frame = frame[:, start: start + h] return frame خذ الآن المنطقة الوسطى لإطار الكاميرا، وحوّلها إلى صورة رماديّة grayscale وطبق عمليّة تقييس عليها وعدّل حجمها إلى 28 × 28. ضع هذا داخل الحلقة while داخل main: def main(): ... while True: # التقط إطارًا بإطار ret, frame = cap.read() # معالجة البيانات frame = center_crop(frame) frame = cv2.cvtColor(frame, cv2.COLOR_RGB2GRAY) x = cv2.resize(frame, (28, 28)) x = (frame - mean) / std مازلنا ضمن حلقة while شغّل الاستدلال الآن باستخدام onnxruntime. وحوّل المخرجات إلى فهرس صنف، ثم إلى حرف: ... x = (frame - mean) / std x = x.reshape(1, 1, 28, 28).astype(np.float32) y = ort_session.run(None, {'input': x})[0] index = np.argmax(y, axis=1) letter = index_to_letter[int(index)] اعرض الحرف المتوقع داخل الإطار، ثم اعرض الإطار مرة أخرى للمستخدم: ... letter = index_to_letter[int(index)] cv2.putText(frame, letter, (100, 100), cv2.FONT_HERSHEY_SIMPLEX, 2.0, (0, 255, 0), thickness=2) cv2.imshow("Sign Language Translator", frame) في نهاية الحلقة، أضف هذه الشيفرة للتحقق مما إذا كان المستخدم قد وصل إلى الحرف q، وإذا كان الأمر كذلك، نُغلق التطبيق. نستخدم cv2.waitKey من أجل الانتظار لمدة 1 مللي ثانية. أضف ما يلي: ... cv2.imshow("Sign Language Translator", frame) if cv2.waitKey(1) & 0xFF == ord('q'): break أخيرًا، حرّر الكاميرا وأغلق جميع النوافذ. وضع هذا الكود خارج الحلقة while لإنهاء main: ... while True: ... if cv2.waitKey(1) & 0xFF == ord('q'): break cap.release() cv2.destroyAllWindows() تحقق جيدًا من تطابق ملفك مع الشيفرة التالية أو هذا المستودع: import cv2 import numpy as np import onnxruntime as ort def center_crop(frame): h, w, _ = frame.shape start = abs(h - w) // 2 if h > w: return frame[start: start + w] return frame[:, start: start + h] def main(): # الثوابت index_to_letter = list('ABCDEFGHIKLMNOPQRSTUVWXY') mean = 0.485 * 255. std = 0.229 * 255. # إنشاء جلسة قابلة للتشغيل باستخدام النموذج المُصدَّر ort_session = ort.InferenceSession("signlanguage.onnx") cap = cv2.VideoCapture(0) while True: # التقط إطارًا بإطار ret, frame = cap.read() # معالجة البيانات frame = center_crop(frame) frame = cv2.cvtColor(frame, cv2.COLOR_RGB2GRAY) x = cv2.resize(frame, (28, 28)) x = (x - mean) / std x = x.reshape(1, 1, 28, 28).astype(np.float32) y = ort_session.run(None, {'input': x})[0] index = np.argmax(y, axis=1) letter = index_to_letter[int(index)] cv2.putText(frame, letter, (100, 100), cv2.FONT_HERSHEY_SIMPLEX, 2.0, (0, 255, 0), thickness=2) cv2.imshow("Sign Language Translator", frame) if cv2.waitKey(1) & 0xFF == ord('q'): break cap.release() cv2.destroyAllWindows() if __name__ == '__main__': main() اخرج من الملف وشغّل البرنامج: (signlanguage) $ python step_5_camera.py بمجرد تشغيل البرنامج، ستظهر نافذة تحتوي على البث المباشر لكاميرا الويب الخاصة بك. يُعرض حرف لغة الإشارة المتوقع في أعلى اليسار. ارفع يدك وضع إشارتك المُفضلة لترى عمل المُصنّف الخاص بك. فيما يلي بعض النتائج النموذجية التي توضّح الحرفين L و D. لاحظ أن الخلفية أثناء الاختبار يجب أن تكون واضحة إلى حد ما حتى يعمل هذا النموذج. سبب ذلك هو عدم التنوّع في الخلفيات الموجودة في الصور في مجموعة البيانات التي درّبنا عليها النموذج. لو كانت مجموعة البيانات تتضمن صورًا لإشارات يد ذات خلفيات متنوعة، لكانت الشبكة متينة في مواجهة الخلفيات المتنوعة التي قد تحتوي ضوضاء أو اختلافات. عمومًا، تتميز مجموعة البيانات بخلفيات فارغة وأيادي متمركزة بطريقة جيد. نتيجة لذلك، فإن أداء نموذجنا يكون مثاليًا عندما تكون يدك في المنتصف وخلفها خلفية فارغة. وبهذا ينتهي تطبيق مُترجم لغة الإشارة. خاتمة لقد بنينا في مقال اليوم مُترجمًا للغة الإشارة الأمريكية باستخدام الرؤية الحاسوبية ونموذج تعلّم آلي. ورأيت جوانب جديدة لتدريب نموذج التعلّم الآلي، مثل زيادة البيانات من أجل متانة النموذج وجداول معدّل التعلّم لتقليل الخسارة وتصدير نماذج الذكاء الاصطناعي باستخدام أونكس لاستخدامها في الإنتاج. وقد توّجنا ذلك بإنشاء تطبيق رؤية حاسوبية في الزمن الحقيقي، والذي يُترجم لغة الإشارة إلى أحرف باستخدام خط معالجة أنشأناه. تجدر الإشارة إلى أن مكافحة هشاشة المُصنف النهائي يمكن معالجتها بأي من الطرق التالية أو جميعها. لمزيد من المعرفة، جرّب المواضيع التالية لتحسين هشاشة المُصنّف: التعميم Generalization: هذا ليس موضوعًا فرعيًا ضمن مجال الرؤية الحاسوبية، بل هو مشكلة موجودة في جميع مراحل تعلّم الآلة. التكيف مع المجال Domain Adaptation: لنفترض أن النموذج الخاص بك قد تدرّب في المجال A (على سبيل المثال، البيئات المشمسة). هل يمكنك تكييف النموذج مع المجال B (على سبيل المثال، البيئات الملبّدة بالغيوم) بسرعة؟ أمثلة مخادعة Adversarial Examples: لنفترض أن الخصم يصمم صورًا عمدًا لخداع نموذجك. كيف يمكنك تصميم مثل هذه الصور؟ وكيف يمكنك مكافحتها والتعامل معها؟ ترجمة -وبتصرُّف- للمقال How To Build a Neural Network to Translate Sign Language into English لصاحبه Alvin Wan. اقرأ أيضًا تعلم الذكاء الاصطناعي مكتبات وأطر عمل الذكاء الاصطناعي: القوة الكامنة خلف الأنظمة الذكية لغات برمجة الذكاء الاصطناعي نظرة سريعة على مجال تعلم الآلة الشيفرة المصدرية للتطبيق sign-language-translator-master.zip
  5. في هذا العصر الذي تتسارع فيه وتيرة التطور التقني، يُمثّل الذكاء الاصطناعي Artificial Intelligence كلمة السر التي ترتبط بالابتكار والتقدم. وتحفل مجالات حياتنا المختلفة بتطبيقات الذكاء الاصطناعي، بدءًا من الهواتف الذكية التي نحملها بأيدينا، إلى الأجهزة الطبية في المستشفيات التي تنقذ حياة الناس إلى تحقيق تحوّلات في مختلف المجالات الصناعية والتجارية. إذ يشكل الذكاء الاصطناعي نقلة نوعية في نمط حياتنا وأعملنا، فهو ليس مجرد مصطلح تقني، بل هو تجسيد للمستقبل الذي نتجه نحوه. نستكشف في هذه المقالة فوائد الذكاء الاصطناعي المبهرة، ومن بينها الإبداع الصناعي، وتحسين الرعاية الصحية، وتحسين أداء الشركات، واكتشاف الفضاء ..إلخ. كما نستعرض كيف يمكن للذكاء الصناعي تحسين حياتنا ويجد حلولًا للتحديات الكبرى التي نواجهها في عالم معقد يتغير بسرعة. عشر فوائد للذكاء الاصطناعي من المحتمل أنك استخدمت الذكاء الاصطناعي بشكل ما في العديد من تطبيقات الحاسوب أو الهاتف الذكي التي تعتمد عليها في عملك. على سبيل المثال، يتطلب التحدث إلى هاتفك الذكي استخدام الذكاء الاصطناعي للتعرف على الكلام. وبالمثل، يقوم الذكاء لاصطناعي بتصفية كل البريد غير المرغوب فيه الذي قد يصل إلى صندوق الوارد الخاص بك. حتى أنك قد تجد الذكاء الاصطناعي في أماكن يصعب تخيل استخدام الذكاء الصناعي فيها. على سبيل المثال، يمكن أن يستفيد منظم الحرارة في منزلك، والذي يُستخدم للتحكم في درجة حرارة المنزل تقنيات الذكاء الاصطناعي إذا كان متطورًا ويعدل درجة الحرارة تلقائيًا بناء عوامل عديدة مثل الفصل الحالي من السنة، أو التفضيلات الشخصية لأصحاب المنزل، أو وجود أشخاص في المنزل من عدمه وغيرها من العوامل العديدة الأخرى. نتحدث في هذا المقال عن فوائد الذكاء الاصطناعي والاستخدامات الأكثر شيوعًا له التي نجدها في العديد من القطاعات. 1. الإبداع الاصصناعي لاشك أن فوائد الذكاء الاصطناعي ودوره في تحقيق مفهوم الإبداع الاصطناعي artificial creativity هي أحد أكثر الأمور إثارةً وابتكارًا في التقنيات الحديثة، إذ يمكن أن تحاكي الأجهزة الذكية اليوم الإبداع البشري وتحدث ثورة في طريقة نظرتنا إلى الفن والابتكار، فمن خلال الخوارزميات المتقدمة للذكاء الاصطناعي، يمكن للحواسيب اليوم إنتاج لوحات فنية مذهلة. كما يمكن للبرامج الحاسوبية أيضًا توليد مقاطع موسيقية وأعمال سينمائية كما يستخدم الذكاء الاصطناعي اليوم في تحليل الأنماط الموسيقية وإنشاء ألحان فريدة وملهمة، ويمكن للمؤلفين والمنتجين الاستفادة من هذه التقنيات لتوليد موسيقى جديدة ومبتكرة، كما يمكن لتقنيات الذكاء الاصطناعي اليوم إعادة إنتاج صوت شخص ليبدو كأنه صوت شخص آخر. بالإضافة إلى ذلك، يمكن إنتاج أفلام وأعمال سينمائية ذات جودة عالية و بتكلفة أقل باستخدام تقنيات الرسوم المتحركة والمؤثرات البصرية وغيرها من التقنيات المدعومة بالذكاء الاصطناعي، بالتالي جعل الإبداع السينمائي أكبر وأكثر وتنوعًا، كما أن فوائد الذكاء الاصطناعي في الفنون، لاتأتي فقط من كونها مجرد أداة تقنية، بل هو شريك إبداعي يُلهم العقول البشرية لاستكشاف حدود الفن وتعزيز الابتكار بطرق جديدة. 2. فوائد الذكاء الاصطناعي في تلبية الاحتياجات الطبية يتطلب عالم الطب المعقد مستوىً من الخبرة يتجاوز في كثير من الأحيان القدرات البشرية، فمع تطور التكنولوجيا والمنهجيات يتعمق التعقيد، وهذا ما يجعل من المستحيل تقريبًا على أي فرد بغض النظر عن مدى تخصصه، إتقان كل جانب من جوانب أمر ما. هنا يكمن الدور المحوري للذكاء الاصطناعي في مجال الرعاية الصحية، حيث يتدخل الذكاء الاصطناعي كحليف موثوق ومنطقي وغير متحيز، ليدعم الأطباء والعاملين في القطاع الصحي في تأدية المهام التي تفوق القدرات البشرية. بدءًا من المراقبة الدقيقة للمرضى من خلال إجراء أعداد كبيرة من الفحوصات بالتسلسل المحدد والدقة اللازمة، ووصولاً إلى تمكين المرضى من إدارة المهام العلاجية بشكل مستقل. وقد نجحت التقنيات المعاصرة المدعومة بتقنيات الذكاء الاصطناعي في سدِّ الفجوة بين القيود البشرية والضرورات الطبية. وإضافة لما سبق، فقد أحدثت الابتكارات الذكية مثل المراقبة عن بعد، والتحليل المعتمد على الذكاء الاصطناعي ثورة في التشخيص الطبي، ومكّنت الأطباء من تحديد المشكلات ومعالجتها بسرعة، وغالبًا دون الحاجة إلى الحضور الجسدي للمريض، كما بدأت تظهر الحلول الروبوتية كبدائل فعالة في العمليات الجراحية وعززت من دقة العمليات الجراحية. انقر هنا لمشاهدة الفيديو الذي يتحدث عن مايسمى نظام "دافنشي" الجراحي لرؤية مدى روعة الأمر! 3. الأتمتة وتعزيز الكفاءة والسلامة في البيئات الصناعية أحدثت تقنيات الأتمتة المدعومة بالذكاء الاصطناعي ثورة في العمليات الصناعية، وساعدت البشر في إنجاز المهام بسرعة وكفاءة. على عكس الأساليب التقليدية، إذ تعمل الأتمتة المعتمدة على الذكاء الاصطناعي دون تدخل مباشر، وتمثل أداة حيوية في مختلف البيئات الصناعية في جميع أنحاء العالم. وأحد الأهداف الأساسية للأتمتة هو التخلص من المهام الحياتية المتكررة التي غالبًا ما تؤدي إلى الملل بين العاملين من البشر، أما في الصناعات التي تعتمد على الذكاء الاصطناعي، مثل تصنيع السيارات، تتولى الروبوتات المجهزة بقدرات الذكاء الاصطناعي المتقدمة المهام التي تحمل طابعًا تكراريًا وتنجزها مع ضمان الدقة والجودة دون الاستسلام لمخاطر الملل. علاوة على ذلك، تساهم الأتمتة التي تعمل بالذكاء الاصطناعي في تحسين معايير السلامة بشكل كبير، وذلك من خلال استبدال البشر في المهام الخطيرة، بالتالي تساهم هذه الآلات الذكية في تقليل الحوادث والإصابات في أماكن العمل، وتعزز هذه العلاقة التكافلية بين الخبرة البشرية وتكنولوجيا الذكاء الاصطناعي من الابتكار وتساهم في جعل بيئة العمل أكثر أمانًا وراحة. 4. تعزيز مفهوم التطبيقات سهلة الاستخدام Friendly Applications يشكل الذكاء الاصطناعي محورًا أساسيًا في تحسين تجربة المستخدم وجعل التقننيات سهلة الاستخدام. إذ يمكن للذكاء الاصطناعي تحليل أسلوب الاستخدام للمستخدمين وفهم احتياجاتهم بدقة، ويمكننا من تصميم وتطوير واجهات أكثر فعالية وبساطة وتوقع مدخلات المستخدمين. على سبيل المثال، يتنبأ ببقية الكلمة بعد أن يكتب المستخدم بضعة أحرف، مما يعزز الكفاءة عن طريق تقليل ضغطات المفاتيح. ويخدم هذا النهج الاستباقي عدة أغراض: يكتب المستخدمون بشكل أقل، مما يعزز كفاءتهم. يتلقى التطبيق عددًا أقل من الإدخالات الخاطئة نتيجة الأخطاء المطبعية. يعزز التواصل بين المستخدم والتطبيق ويطالب المستخدم باستخدام المصطلحات الصحيحة. إضافةً إلى ذلك، يمكن للذكاء الاصطناعي التكيف والتطور بناءً على تفاعلات المستخدم السابقة، وتخصيص الاقتراحات والتوصيات لتتماشى مع تفضيلاته في تنفيذ المهام، وتقديم أفكار جديدة ربما لم يفكر فيها المستخدم. ويمكن لهذه الميزات بكل تأكيد أن تحسن تجربة المستخدمين بشكل كبير. 5. فوائد الذكاء الاصطناعي في استكشاف الفضاء لقد أسرت جاذبية الكون خيال الإنسان لعدة قرون، ودفعته لاستكشاف اتساع الفضاء وكشف أسراره. فمنذ العصور القديمة، كان البشر يحدقون في النجوم ويتساءلون عن أسرارها ويتأملون الاحتمالات خارج غلافنا الجوي. وفي السنوات الأخيرة، ومع ظهور التقنيات المتقدمة والذكاء الاصطناعي، حقق فهمنا للفضاء قفزات غير مسبوقة. وقد بدأت رحلة فهم الكون بالتلسكوبات، وهي أدوات أتاحت لعلماء الفلك النظر في أعماق الفضاء وقد تطورت التلسكوبات على مر القرون، وأصبحت أكبر وأكثر تعقيدًا. ومع ذلك، غالبًا ما يشوه الغلاف الجوي للأرض الملاحظات السماوية. وللتخفيف من هذه المشكلة، لجأ العلماء إلى حل هذه القضية من خلال استخدام التصحيحات المعتمدة على الذكاء الاصطناعي، كما ساعدت خوارزميات التعلم الآلي في اكتشاف الكواكب التي تقع خارج نظامنا الشمسي وتحليل الحركات المعقدة للأجرام السماوية، في محاولة لكشف أسرار الكون وقد ساعدت خوارزميات الذكاء الاصطناعي كذلك في معالجة البيانات الفلكية بدقة، مما ساعد العلماء في بناء نماذج مثل نموذج Lambda Cold Dark Matter (LCDM)، الذي يهدف إلى وصف بنية واسعة النطاق للكون وتطوره واليوم يعد الذكاء الاصطناعي حليفًا لا غنى عنه لعلماء الفضاء، حيث يساعدهم في الحسابات والتحليلات المعقدة. 6. الرؤية الحاسوبية والواقع الافتراضي ومعالجة الصور لعبت التقنية في عالمنا الحديث دوراً هامًا في تحسين الاتصال وتوسيع آفاق الواقع الذي نعيش فيه، ويشكل الذكاء الاصطناعي العمود الفقري لهذه التحولات المبهرة ففي مجال الرؤية الحاسوبية ومعالجة الصور، تُعدُّ التقنيات الحديثة المدعومة بالذكاء الاصطناعي مثالاً بارزًا على هذا التقدم الهائل ومن أبرز هذه التقنيات: التعرف على الأشياء والأماكن: إذ يمكن للذكاء الاصطناعي الآن التعرف على الأشياء والأماكن بدقة فائقة. هذا التطور يُستخدم مثلًا في تحقيق الأمان العام، حيث يمكن للكاميرات المزودة بالذكاء الاصطناعي التعرّف على الوجوه والمشاهد المشبوهة والتفاعل بشكل سريع وفعّال. الواقع الافتراضي والواقع المعزز: فقد أحدثت تقنيات الواقع الافتراضي Virtual Reality والواقع المعزز Augmented Reality ثورة في الصناعات الترفيهية والتعليمية. فبفضل الذكاء الاصطناعي، يُمكن تحسين تجارب الواقع الافتراضي لتكون أكثر واقعية وتفاعلية، وذلك عن طريق محاكاة الحركات البشرية بدقة واستجابة سريعة لحركات المستخدم. معالجة الصور: يستخدم الذكاء الاصطناعي في تحسين الصور بطرق مذهلة. إذ يمكن تحسين دقة الصور وتصحيح الألوان وإزالة الضوضاء بفعالية عالية، بالتالي يُمكن للمصورين والمصممين إنتاج صور استثنائية بجودة عالية. مساعدة الأشخاص ذوي الإعاقة البصرية: إذ يمكن للأشخاص ذوي الإعاقة البصرية الوصول إلى المعلومات بفضل التطبيقات المبتكرة المدعومة بالذكاء الاصطناعي، ويمكن للتقنيات تحويل النصوص إلى كلام والتعرف على الأشياء في البيئة المحيطة، وهذا يزيد من استقلالهم ويُسهم في تسهيل حياتهم اليومية. 7. التعرف على خط اليد والتعرف على الكلام والتعرف الضوئي على الحروف تعتبر تقنية التعرف على خط اليد إحدى الابتكارات الرائدة التي تُمكّن من تحويل الكتابة اليدوية إلى نصوص رقمية بشكل فعّال ودقيق. إذ يتيح التعرف على خط اليد للأجهزة الذكية والأنظمة الحوسبية التفاعل مع الكتابة اليدوية للمستخدمين، مما يوفر تجربة استخدام سلسة ومريحة. وتتنوع تطبيقات هذه التقنية بين الأجهزة اللوحية والهواتف الذكية، حيث يمكن للمستخدمين الكتابة باليد ومشاركة ملاحظاتهم وأفكارهم بكل يسر. كما تعد تقنية التعرّف على الكلام مجالًا رائدًا في تطبيقات الذكاء الاصطناعي، حيث تُمكّن هذه التقنية الأنظمة من فهم الكلام البشري وتحويله إلى نصوص مكتوبةوتسهم بشكل كبير في تسهيل التفاعل بين البشر والأجهزة الذكية، سواء كانت هذه الأجهزة هواتف ذكية أو أجهزة منزلية ذكية، وتسهل علينا التحكم في الأجهزة بواسطة الأوامر الصوتية. أخيرًا تسمح تقنية التعرّف الضوئي على الحروف للأجهزة بفهم وتحليل الحروف والنصوص المكتوبة في الصور أو الوثائق. ويمكن لهذه التقنية التعرف على خط اليد أو الخطوط المطبوعة بدقة عالية، ولهذا فوائد عديدة في مجموعة واسعة من التطبيقات مثل مساعدة الأشخاص ذوي الاحتياجات الخاصة في قراءة النصوص، ومساعدة الباحثين في فحص وثائق ضخمة بسرعة ودقة. 8. معالجة اللغات الطبيعية تعد معالجة اللغات الطبيعية NLP أحد التطبيقات المفيدة لتعلم الآلة والتي نستخدمها في حياتنا اليومية ومن التطبيقات الأساسية لهذه التقنية: تستخدم منصات البريد الإلكتروني، مثل جيميل Gmail وآوتلوك Outlook وغيرهم، تقنيات معالجة اللغة الطبيعية NLP على نطاق واسع لتوفير مجموعة من الميزات، مثل تصنيف البريد العشوائي spam والرسائل ذات الأولوية العالية priority inbox واستخراج المواعيد من النصوص calendar event extraction والإكمال التلقائي وما إلى ذلك. تعتمد تقنيات المساعدة الصوتية أو الافتراضية مثل سيري Siri وكورتانا Cortana، على مجموعة من تقنيات معالجة اللغة الطبيعية للتفاعل مع المستخدم وفهم أوامر المستخدم والاستجابة وفقًا لذلك. تستخدم محركات البحث الحديثة، مثل جوجل Google وبينج Bing، والتي تعد أساس الإنترنت اليوم، تقنيات معالجة اللغة الطبيعية بشكل كبير في العديد من المهام الفرعية، مثل فهم الاستعلامات وتوسيع الاستعلام والإجابة على الأسئلة واسترجاع المعلومات وترتيب النتائج وتجميعها ..إلخ. الترجمة الآلية بمساعدة الحاسوب مثل مترجم جوجل أو مترجم Azure AI Translator. هذه القائمة ليست شاملة عمومًا. لمعالجة اللغة الطبيعية فوائد عديدة في مختلف التطبيقات. 9. الذكاء الاصطناعي والروبوتات غالبًا ما يتم الخلط بين الروبوتات والذكاء الاصطناعي، ولكن من الضروري التمييز بين الاثنين، حيث يركز الذكاء الاصطناعي في المقام الأول على حل المشكلات المعقدة التي تتطلّب قدرات بشرية غالبًا، مثل التعرف على الأشياء أو فهم الكلام والنصوص، فإن الروبوتات تتعلّق باستخدام الآلات لأداء المهام الجسدية بطريقة آلية جزئيًا أو كليًا. فكر في الذكاء الاصطناعي باعتباره البرنامج الذي يعالج هذه المشكلات، في حين تعمل الروبوتات بمثابة الأجهزة التي تجلب هذه الحلول إلى الحياة. يختلف استخدام برامج الذكاء الاصطناعي في عالم الروبوتات. يتم تشغيل بعض الروبوتات عن بعد من قبل البشر، مثل روبوت دافنشي الذي ذكرناه سابقًا، في حين يعزز الذكاء الاصطناعي التحكم البشري ولكنه لا يحل محله. وهناك روبوتات تتلقى أوامر مجردة من البشر، مثل الانتقال من النقطة أ إلى النقطة ب على الخريطة أو التقاط كائن، وتعتمد على الذكاء الاصطناعي لتنفيذ هذه التعليمات. بالإضافة إلى ذلك، يمكن لبعض الروبوتات إكمال المهام المعينة بشكل مستقل دون تدخل بشري. يؤدي دمج الذكاء الاصطناعي في برمجة الروبوتات إلى تعزيز قدراته، مما يجعله أكثر ذكاءً وأكثر كفاءة في المهام. ومع ذلك، من المهم ملاحظة أنه ليست كل الروبوتات تحتاج إلى الذكاء الاصطناعي لتعمل بفعالية. غالبًا ما يغذي الخيال العلمي التداخل بين الذكاء الاصطناعي والروبوتات، لكنهما في الواقع يؤديان أدوارًا متباينة. يعد الاستخدام المتزايد للروبوتات في مختلف المجالات، بما في ذلك العمل الصناعي والبحث العلمي والرعاية الصحية وحتى التطبيقات العسكرية، اتجاهًا متزايدًا. وقد أدت التطورات الحديثة في الذكاء الاصطناعي إلى تسريع هذا الاتجاه من خلال معالجة التحديات المعقدة في مجال الروبوتات، مثل التعرف على الأشياء والتنبؤ بالسلوك البشري وفهم الأوامر الصوتية ومعالجة اللغة الطبيعية وحتى مهام مثل المشي في وضع مستقيم. 10. فوائد الذكاء الاصطناعي في الألعاب يفيد الذكاء الاصطناعي في صناعة الألعاب بشكل كبير، ليضيف عمقًا وتعقيدًا إلى العوالم الافتراضية للعب. وقد دُمج الذكاء الاصطناعي بسلاسة في أنواع الألعاب المختلفة، بدءًا من الحركات الإستراتيجية لشخصيات الألعاب وحتى السلوك الذكي للأعداء، وهذا ما منح شخصيات اللعبة استجابة واقعية، مثل أشباح ألعاب الأركيد الكلاسيكية وروبوتات إطلاق النار الحديثة. إن نطاق تقنيات الذكاء الاصطناعي في الألعاب واسع، ويشمل أيضًا مهام مثل تحديد المسارات وكشف التصادمات وغيرها الكثير. وتستخدم العديد من الألعاب السائدة اليوم، مثل Creatures و Black & White و Battlecruiser 3000AD و Dirt Track Racing و Fields of Battle و Heavy Gear أساليب الذكاء الاصطناعي في برمجتها. دورة الذكاء الاصطناعي احترف برمجة الذكاء الاصطناعي AI وتحليل البيانات وتعلم كافة المعلومات التي تحتاجها لبناء نماذج ذكاء اصطناعي متخصصة. اشترك الآن خاتمة لقد برز الذكاء الاصطناعي كشريك لا غنى عنه في رحلتنا المستمرة نحو المستقبل كإنجاز تقني فريد وتجسيد للإمكانيات العظيمة للعقول البشرية. من الإبداع الصناعي إلى تحسين الرعاية الصحية، ومن الأتمتة الصناعية إلى استكشاف الفضاء والواقع الافتراضي، وأصبح الذكاء الاصطناعي جزءًا لا يتجزأ من حياتنا اليومية ومنافسًا حقيقيًا في عدة ميادين. ولا يمكن تجاهل كل هذا التقدم في مجالات التعرّف على الصور والروبوتات ومعالجة اللغات الطبيعية والألعاب، ما يفتح أمامنا أفقًا جديدًا من الإمكانيات ويدفعنا لتعلم واستخدام الذكاء الاصطناعي لنحقق من خلاله أهدافنا وطموحاتنا البشرية. ولنتطلع بثقة وتفاؤل نحو هذا المستقبل المشرق، حيث يبقى الذكاء الاصطناعي مصدر إلهام يشكل الأساس لابتكاراتنا ورحلتنا المستمرة نحو التقدم والتطور. اقرأ أيضًا الذكاء الاصطناعي: أهم الإنجازات والاختراعات وكيف أثرت في حياتنا اليومية أساسيات الذكاء الاصطناعي مستقبل الذكاء الاصطناعي برمجة الذكاء الاصطناعي مستقبل البرمجة
  6. من المرجّح أنك سمعت بمصطلح الذكاء الاصطناعي Artificial Intelligence أو اختصارًا AI كثيرًا في الآونة الأخيرة نظرًا للتطورات المذهلة التي وصل إليها هذا المجال. ولكن ما هو الذكاء الاصطناعي بالضبط، وما أهمية الذكاء الاصطناعي؟ بالنسبة للشق الأول من السؤال، فلقد أجبنا عليه بالتفصيل سابًقا في مقالة الذكاء الاصطناعي: دليلك الشامل، إذ يشير الذكاء الاصطناعي في جوهره إلى قدرة الآلات على تقليد أو محاكاة السلوك البشري، بغية أداء المهام التي يقوم بها الإنسان. ومن الضروري أن نلاحظ أنه على الرغم من أن الذكاء الاصطناعي يمكن أن يحاكي جوانب معينة من الذكاء البشري، إلا أنه يفتقر إلى الوعي والعواطف البشرية. إذ يمتلك البشر الحدس والإبداع والفهم العاطفي وهي صفات لا يستطيع الذكاء الاصطناعي محاكاتها، وبغض النظر عن مدى تقدمه. من أداء ثديبدو مذهلًا إلا أن الذكاء الاصطناعي يعمل ضمن المعايير التي حددها مبرمجوه والبيانات التي تم تدريبه عليها، وهذا لا يتطابق مع تعقيد عمليات التفكير البشري. بالنسبة للشق الثاني من السؤال، فهذا هو موضوع مقالتنا هذه المرة. فقد أحدث الذكاء الصناعي ضجة كبيرة في عالمنا لأنه يمثل تحولًا مذهلًا في طريقة عيشنا وعملنا وتفاعلنا مع التقنية. متميّزًا عن أي ابتكار سابق، فإن الذكاء الصناعي قادر على إحداث ثورة في الصناعات وحل المشكلات المعقدة والارتقاء بحياتنا اليومية وتسهيلها بطرق لم يكن من الممكن تصورها من قبل فالكفاءة والدقة والإمكانيات اللامحدودة هو ما يجعل الذكاء الاصطناعي موضوعًا مهمًا ومثيرًا للغاية. نكتشف معًا في هذه المقالة الأسباب الكامنة وراء هذا الضجيج، ونستكشف سبب أهمية الذكاء الاصطناعي وكيف يعيد تشكيل عالمنا. أهمية الذكاء الاصطناعي: النقاط التي تجعل الذكاء الاصطناعي مهمًا الذكاء الاصطناعي ليس مجرد كلمة طنانة بل هو قوة تحويلية تعيد تشكيل الطريقة التي نعيش بها ونعمل ونتفاعل مع العالم. وسنستكشف في الفقرات التالية التأثير العميق للذكاء الاصطناعي على مجتمعنا ولماذا يعد فهم أهميته أمرًا ضروريًا للجميع. الذكاء الاصطناعي وحل المشكلات: تطبيقات العالم الحقيقي لعل أحد الأسباب الأساسية لأهمية الذكاء الاصطناعي هي قدرته على حل المشكلات التي نواجهها. حيث يمكن لأنظمة الذكاء الاصطناعي تحليل كميات هائلة من البيانات وتحديد الأنماط التي قد تفوتها عقولنا البشرية. مما يوفر لنا هذه القدرة إمكانية برمجة تطبيقات ثورية في مجالات مثل الرعاية الصحية والنقل والأعمال والصناعة والزراعة وإنتاج المحاصيل، ولاشك أن براعة الذكاء الاصطناعي في حل المشكلات يعد مساعدة عظيمة لمعالجة التحديات الصعبة التي تواجه البشرية. الذكاء الاصطناعي والكفاءة: توفير الوقت والموارد تخيل عالماً تتعامل فيه مع المهام المتكررة المملة بسلاسة وسرعة، ويسمح لك بالتركيز على الإبداع والابتكار. يجعل الذكاء الاصطناعي هذا الأمر ممكنًا من خلال أتمتة المهام التي كانت تستغرق منك وقتًا طويلاً. على سبيل المثال في الصناعات، تعمل الروبوتات التي تعتمد على الذكاء الاصطناعي على تبسيط عمليات التصنيع وتحسين الكفاءة وخفض تكاليف الإنتاج. وفي مجال خدمة العملاء، تستجيب روبوتات المحادثة المدعومة بالذكاء الاصطناعي للاستفسارات بشكل فوري، مما يعزز تجربة المستخدمين ويوفر الوقت والموارد. الذكاء الاصطناعي في المستقبل: رسم ملامح المرحلة القادمة يعد الذكاء الاصطناعي حافزًا للابتكار، ويولد أفكارًا وتطبيقات عديدة كانت تعتبر في السابق خيالًا علميًا. فمن السيارات ذاتية القيادة التي تَعِد بطرق أكثر أمانًا إلى الفن والموسيقى التي يولدها الذكاء الاصطناعي والتي تتحدى مفاهيمنا عن الإبداع، فإن الاحتمالات لا حدود لها. كما تعمل الأبحاث المعتمدة على الذكاء الاصطناعي على تسريع الاكتشافات العلمية وتساعد في تحسين مجالات مثل الطب وعلوم البيئة. ومثل هذه الابتكارات لا ترتقي بحياتنا فحسب، بل تمهد الطريق أيضًا لمستقبل تكون فيه الإمكانات البشرية لا حدود لها. إن فهم القوة التحويلية للذكاء الاصطناعي لا يقتصر على مجرد مسألة محو الأمية الرقمية بل هو جانب هام لكي تكون مشاركًا مستنيرًا ونشطًا في العالم الحديث. ونتعمق في الأقسام التالية أكثر في أهمية الذكاء الاصطناعي، ونستكشف كيف ساهم في تحويل الصناعات وإحداث ثورة في الطريقة التي نعيش بها ونعمل بها، كما نكشف عن التأثير الاستثنائي للذكاء الاصطناعي في حياتنا اليومية. الذكاء الاصطناعي في الحياة اليومية لقد دمج الذكاء الاصطناعي نفسه بسلاسة في نسيج روتيننا اليومي، ليشكل بهدوء الطريقة التي نعيش بها ونتفاعل مع العالم. نستكشف في هذا القسم الجوانب المختلفة للذكاء الاصطناعي في حياتنا اليومية، ونسلط الضوء على الطرق التي يؤثر بها علينا. كيف يؤثر الذكاء الاصطناعي عليك يوميًا يؤثر الذكاء الاصطناعي على يومك منذ اللحظة التي تستيقظ فيها. فهو يُسهّل روتينك الصباحي من خلال التنبؤ بطرق حركة المرور، مما يضمن وصولك إلى وجهتك بسرعة، أو أثناء قيامك بتصفح موجز الوسائط الاجتماعية الخاص بك، إذ تعمل خوارزميات الذكاء الاصطناعي على تنظيم المحتوى وتخصيصه وفقًا لاهتماماتك وتجعل تجربتك عبر الإنترنت أكثر فائدة وسهولة وجاذبية. الذكاء الاصطناعي موجود في هواتفنا الذكية يعد هاتفك الذكي، ذلك الرفيق الموجود في كل مكان، مثالًا رئيسيًا على اندماج الذكاء الاصطناعي في حياتنا اليومية. إذ يستجيب المساعدون الافتراضيون الصوتيين المدعومون بالذكاء الاصطناعي مثل سيري Siri وأليكسا Alexa ومساعد جوجل Google Assistant لأوامرك الصوتية ويساعدونك في جدولة المواعيد وتوفير المعلومات في الزمن الحقيق، كما يكمل النص التنبؤي المعتمد على الذكاء الاصطناعي الجمل التي تكتبها، مما يجعل إرسال الرسائل النصية أسرع وأكثر سهولة. أهمية الذكاء الاصطناعي في الرعاية الصحية بالنسبة لمجال الرعاية الصحية، تأتي أهمية الذكاء الاصطناعي من إمكانية تحليل مجموعات كبيرة من البيانات لتعزيز دقة التشخيص ومساعدة الأطباء في تحديد الأمراض في مراحل مبكرة. وتتنبأ خوارزميات الذكاء الاصطناعي بتفشي الأمراض، مما يساعد في التخطيط المسبق للرعاية الصحية. أهمية الذكاء الاصطناعي في النقل تأتي أهمية الذكاء الاصطناعي في مجال النقل من إمكانية تشغيل أنظمة الملاحة وتحسين المسارات في الزمن الحقيقي والتنبؤ بأنماط حركة المرور، وهذا ما يضمن أن تكون رحلتك فعّالة وخالية من التوتر أو المشاكل. كما حققت صناعة السيارات في السنوات الـ 130 الماضية تقدمًا ملحوظًا، مما أدى إلى تغيير الطريقة التي نتحرك بها. في ألمانيا مثلًا، يمتلك أكثر من نصف السكان الآن سيارة خاصة بهم، مما يوفر لهم إمكانية تنقل غير مسبوقة للعمل والأنشطة اليومية والترفيه. هذا الاعتماد على المركبات لا يمكن إنكاره، خاصة في المناطق ذات وسائل النقل العام المحدودة مثل الأرياف. يعد مستقبل النقل بمزيد من الراحة من خلال دمجه مع أنواع مختلفة من الذكاء الاصطناعي إذ ستصبح السيارات الكهربائية ذاتية القيادة أو السيارات الروبوتية، حقيقة واقعة وستنقلنا هذه المركبات ذاتية القيادة إلى أي وجهة تقريبًا، مما يسمح للركاب بالقراءة أو العمل أو الاسترخاء أثناء الرحلة، وفي حين توفر وسائل النقل العام وسائل راحة مماثلة، فإن السيارات الآلية ستوفر هذه الحرية على أي طريق وفي أي وقت. إضافة إلى ذلك ستظهر سيارات الأجرة الآلية التي لا تحتاج لسائق والتي يمكننا استدعاؤها وقت الحالجة من خلال تطبيق هاتف ذكي بسيط، وسيكون لدينا خيار السفر بمفردنا أو مشاركة الرحلة مع الآخرين، مما يلغي الحاجة إلى السيارات الشخصية تمامًا. ولا يوفر لنا هذا التحول المال فحسب، بل يريحنا أيضًا من مسؤوليات مختلفة مثل التزود بالوقود والصيانة والتنظيف ومعضلات ركن السيارات، مما يعد بمستقبل التنقل السهل والفعال من حيث التكلفة. وإلى جانب المكاسب الفورية في مجال الراحة والملاءمة، ستوفر السيارات الآلية مزايا كبيرة أخرى. على سبيل المثال، وفقاً لدراسة أجرتها شركة ماكينزي، سنحتاج إلى عدد أقل بكثير من السيارات، وفي المقام الأول، سنحتاج إلى عدد أقل بكثير من أماكن ركن السيارات في عصر السيارات ذاتية القيادة، وهو ما سيؤدي إلى انخفاض هائل في استهلاك الموارد. ووفقًا لدراسة أجراها مختبر لورانس بيركلي الوطني، ستتسبب السيارات الكهربائية ذاتية القيادة في انخفاض بنسبة 90% في انبعاثات غازات الاحتباس الحراري لكل ميل مسافر بسبب كفاءة استخدام الطاقة في المركبات والتوافق الأمثل بين السيارة والغرض منها. وبالتالي ستكون سيارات الأجرة الآلية أكثر صداقة للبيئة من الحافلات الثقيلة مثلًا، والتي غالبًا ما تعمل بسعة منخفضة، خاصة في المناطق الريفية. بشكل عام، سوف تساهم سيارات الأجرة الروبوتية بشكل كبير في توفير الطاقة. وستكون سلامة الركاب أعلى بكثير مما هي عليه اليوم، ويقدر الخبراء حاليًا معدلات الحوادث المستقبلية بين صفر وعشرة بالمائة مقارنة باليوم فلن يكون هناك قيادة عاطفية أو غضب على الطريق أو قيادة مشتتة. أهمية الذكاء الاصطناعي في الترفيه: البث والألعاب تُحدث خوارزميات الذكاء الاصطناعي ثورة في مجال الترفيه وتبرز أهمية الذكاء الاصطناعي في منصات البث للتوصية بالأفلام والعروض المناسبة لاهتمام المستخدمين، حيث تكون مصممة خصيصًا لسجل المشاهدة الخاص بك، وبالتالي ستظهر أمامك المحتويات التي تهمك دومًا. من الناحية الأخرى، تبرز أهمية الذكاء الاصطناعي في عالم صناعة الألعاب وقدرته على تكييف طريقة اللعب بناءً على تصرفات اللاعبين. أهمية الذكاء الاصطناعي في الأعمال تستفيد الشركات من الذكاء الاصطناعي لتبسيط العمليات وتعزيز خدمة الدعم الفني وتحسين عملية صنع القرار. إذ توفر روبوتات الدردشة Chatbots المدعومة بالذكاء الاصطناعي، دعمًا فوريًا للعملاء وحل الاستفسارات بكفاءة. تقوم التحليلات المعتمدة على الذكاء الاصطناعي بفحص كميات هائلة من البيانات، واستخراج رؤى قيّمة تفيد في اتخاذ قرارات الأعمال الاستراتيجية، مما يضمن بقاء الشركات قادرة على المنافسة في سوق سريع التغير، ولا يقتصر تأثير الذكاء الصناعي على هذه المجالات فحسب، بل يمتد ليشمل كل جانب من جوانب حياتنا تقريبًا. ننتقل في القسم التالي للحديث عن أهمية الذكاء الاصطناعي في مجال مهم وهو الابتكار والإبداع. الابتكار والإبداع على عكس المخاوف من أن تحل الآلات محل البشر أو الإبداع البشري، فإن الذكاء الاصطناعي أداة قوية لتعزيز قدراتنا الإبداعية وتضخيمها وليس استبدالها. فقد كان الابتكار دائمًا قوة دافعة وراء تقدم وتطور المجتمعات على مر العصور، ولطالما كان هناك عناصر وعوامل تُنشّط وتعزز الابتكار في كل حقبة، فسابقًا كان لازدهار الدولة الإسلامية في بغداد السبب الرئيسي لظهور علماء وابتكارات عربية مازالت مرتبطة بأسماء النجوم حتى الآن. مثال آخر، وفي وقت ليس ببعيد، كان لاختراع الكهرباء سببًا مباشرًا لآلاف الابتكارات الأخرى. أما اليوم فلدينا الذكاء الاصطناعي، الذي بدون أدنى شك، هو كهرباء هذه الحقبة. حيث يعمل الذكاء الاصطناعي كمحفز للابتكارات التكنولوجية والعلمية التي من شأنها أن ترتقي بمجتمعاتنا من خلال تقديم وجهات نظر وحلول جديدة للمشاكل أو المهام القديمة ولا شك أن قدرة الذكاء الصناعي على معالجة وتحليل البيانات الضخمة في وقت قصير، سيؤدي إلى مزيد من الاكتشافات والابتكارات الرائدة. دورة الذكاء الاصطناعي احترف برمجة الذكاء الاصطناعي AI وتحليل البيانات وتعلم كافة المعلومات التي تحتاجها لبناء نماذج ذكاء اصطناعي متخصصة. اشترك الآن أهمية الذكاء الاصطناعي في الفن والموسيقى والتصميم لقد حقق الذكاء الاصطناعي نجاحات كبيرة في عالم الإبداع. على سبيل المثال، في عالم الفن، تولد خوارزميات الذكاء الاصطناعي لوحات ومنحوتات وقطع فنية رقمية مذهلة. هذه الإبداعات، التي لا يمكن تمييزها غالبًا عن تلك التي صنعها الفنانون البشريون في الدقة والجمالية. من ناحية أخرى، يعد التأليف الموسيقي المعتمد على الذكاء الاصطناعي مجالًا مثيرًا آخر يؤكّد لنا مدى أهمية الذكاء الاصطناعي في هذا الحقل، حيث تقوم الخوارزميات بتحليل الأنماط الموسيقية وإنشاء مقطوعات موسيقية ملهمة. وفي مجال التصميم الجرافيكي، تساعد أدوات الذكاء الاصطناعي المصممين المحترفين من خلال أتمتة المهام المتكررة وتسمح لهم بالتركيز على التفكير والتصور. إطلاق العنان للإبداع باستخدام أدوات الذكاء الاصطناعي أحد الجوانب الرائعة للذكاء الاصطناعي هو قدرته على تمكين الأفراد من إطلاق العنان لإبداعهم. إذ يمكن الآن بسهولة الوصول إلى العديد من أدوات ومنصات الذكاء الاصطناعي، مما يمكّن الأشخاص ذوي الخبرة التقنية المحدودة من استخدام قدارات الذكاء الاصطناعي. وتتراوح هذه الأدوات من تطبيقات تحرير الصور التي تستخدم الذكاء الاصطناعي لتحسين الصور، إلى روبوتات الدردشة التي تساعد المؤلفين على توسيع وتطوير الأفكار. ومن تسهيل إمكانية الوصول إلى تكنولوجيا الذكاء الاصطناعي، سيصبح الإبداع بلا حدود ومتاحًا لأي شخص لديه شرارة من الخيال. يعمل الذكاء الاصطناعي بمثابة لوحة يمكن للإبداع البشري أن يزدهر عليها، فهو يوفر وسيلة لاستكشاف مناطق مجهولة وتجربة أفكار جديدة وتحويل كل ما يدور بخيالنا لأفكار واقعية، ما يعد بمستقبل لا يعرف فيه الابتكار حدودًا. تبني ثورة الذكاء الاصطناعي في ختام مقالتنا عن أهمية الذكاء الاصطناعي، يصبح من الواضح أن تبني ثورة الذكاء الاصطناعي ليس مجرد خيار، بل هي ضرورة للأفراد والمجتمعات على حد سواء. فتبني الذكاء الصناعي يعني تبني التغيير والتكيف مع التطور التكنولوجي والتعرّف على الإمكانات المذهلة التي يحملها الذكاء الصناعي. ومن خلال فهم قدراته ومحدوياته يمكننا تسخير قوته لخلق مستقبل أفضل. واعتماد الذكاء الصناعي ينطوي على تحوّل في عقليتنا وتفكيرنا، والاعتراف بأن التقنية هي أداة يمكنها حل المشكلات المعقدة وتحسين حياتنا شريطة استخدامها بشكل مسؤول. وأنت كقارئ وعضو في المجتمع، تلعب دورًا هامًا في تشكيل مستقبل الذكاء الاصطناعي، وينبغي أن تبقى على اطلاع دائم بتطورات الذكاء الاصطناعي، وتطرح الأسئلة حوله وتشارك في المناقشات حول آثاره المجتمعية وتشجّع التعليم والوعي حول الذكاء الصناعي في مجتمعك. أفكار أخيرة حول أهمية الذكاء الاصطناعي في هذا النسيج الكبير للتقدم البشري المُمتد على مليارات السنين، يعد الذكاء الاصطناعي خيطًا جديدًا ينسج طريقه إلى كل جانب من جوانب حياتنا. فهو لم يعد مفهومًا بعيدًا ولكنه أصبح واقعًا حاليًّا يؤثر على خياراتنا ويدفع الابتكار ويحل التحديات التي كانت تعتبر في السابق مستحيلة الحل، وبينما نمضي قدمًا، دعونا نتعامل مع الذكاء الصناعي بتوازن بين الحماس والمسؤولية. ومن خلال القيام بذلك، يمكننا إطلاق العنان لإمكاناته الكاملة. في الختام، تذكر أن أهمية الذكاء الاصطناعي تتجاوز مجالات التقينة وتؤثر على كل مجالات الحياة وعلينا أن نتقبل ثورة الذكاء الصناعي بعقل متفتح وقلب مفعم بالأمل ونأمل أنها ستجعل مستقبلنا أكثر إشراقًا. اقرأ أيضًا أساسيات الذكاء الاصطناعي: دليل المبتدئين مجالات الذكاء الاصطناعي مكتبات وأطر عمل الذكاء الاصطناعي: القوة الكامنة خلف الأنظمة الذكية لغات برمجة الذكاء الاصطناعي فوائد الذكاء الاصطناعي
  7. لاشك أن المجتمع اليوم مفتون بشدة بالذكاء الاصطناعي ، خاصةً مع التقدم الأخير في معالجة اللغات الطبيعية NLP والذكاء الاصطناعي التوليدي Generative AI. إذ تُبشّر هذه التقنيات المتطورة بتحسين الإنتاجية اليومية كثيرًا في مجموعة واسعة من المهام. على سبيل المثال، يساعد غيت هاب كوبايلوت GitHub Copilot المطورين على برمجة خوارزميات كاملة بسرعة، وينتج أوتر بايلوت OtterPilot تلقائيًّا ملاحظات الاجتماع للمدراء التنفيذيين، ويمكّن ميكسو Mixo رواد الأعمال من إطلاق مواقع الويب بسرعة. ستوفر هذه المقالة مقدمة موجزة للذكاء الاصطناعي التوليدي، مع عرض أمثلة ذات صلة بتقنية الذكاء الاصطناعي. بعد ذلك، تُطبّق المقالة المفاهيم النظرية المتعلقة بالذكاء الاصطناعي التوليدي، من خلال إنشاء عروض فنية باستخدام نماذج GPT ونماذج الانتشار diffusion models. تعرض الصورة التالية مجموعة صور لمؤلف المقال، وقد أُنشئت جميع هذه الصور بواسطة الذكاء الاصطناعي باستخدام التقنيات البرمجية التي سنتطرّق عليها في هذه المقالة. نظرة عامة موجزة عن الذكاء الاصطناعي التوليدي ملاحظة: إذا كنت على دراية بمصطلحات الذكاء الاصطناعي التوليدي، فيمكنك تجاوز هذه الفقرة والانتقال للفقرة التالية مباشرة. شهد السوق في عام 2022 إدخال العديد من النماذج الأساسية Foundational models، وأصبحت متوفرة للاستخدام والتطوير، وهذا ما أدى إلى دفع عجلة تقدم الذكاء الاصطناعي في قطاعات متنوعة. يُعد الفهم الواضح للمفاهيم الأساسية المرتبطة بهذه النماذج أمرًا محوريًا لتعريفها: الذكاء الاصطناعي: هو مصطلح واسع يشمل البرمجيات القادرة على تنفيذ المهام بذكاء. التعلم الآلي: هو فرع من الذكاء الاصطناعي، يستخدم خوارزميات يُمكنها أن تتعلّم من البيانات. الشبكات العصبية: هي فرع من التعلّم الآلي، تستخدم طبقات تحتوي عُقدًا Nodes (أو تُسمّى خلايا)، وهي مستوحاة من فكرة خلايا الدماغ البشري. الشبكات العصبية العميقة: تُمثل شكلاً متقدمًا من الشبكات العصبية،فهي تتضمّن عددًا أكبر من الطبقات (أكثر عمقًا). النموذج الأساسي عبارة عن شبكة عصبية عميقة مدربة على كميات هائلة من البيانات الخام Raw data. ومن الناحية العملية، يعد النموذج الأساسي نوعًا ناجحًا للغاية من الذكاء الاصطناعي يمكنه التكيّف بسهولة وإنجاز المهام المختلفة. فالنماذج الأساسية هي جوهر الذكاء الاصطناعي التوليدي: تُعد نماذج اللغة Language models المولّدة للنص مثل GPT ونماذج الانتشار المولدة للصور نماذج أساسية. يعتمد الذكاء الاصطناعي التوليدي، الذي يشمل مولدات النصوص مثل GPT ومولدات الصور مثل نماذج الانتشار، على هذه النماذج الأساسية. دورة الذكاء الاصطناعي احترف برمجة الذكاء الاصطناعي AI وتحليل البيانات وتعلم كافة المعلومات التي تحتاجها لبناء نماذج ذكاء اصطناعي متخصصة. اشترك الآن النص: نماذج معالجة اللغة الطبيعية NLP في سياق نماذج الذكاء الاصطناعي التوليدي، تُدرّب نماذج معالجة اللغة الطبيعية على إنتاج نص يبدو كما لو كان قد أُنشئ بواسطة إنسان، وتعد النماذج اللغوية الكبيرة Large Language Models (اختصارًا LLMs) التي تتميّز بضخامتها، أساسًا لأنظمة الذكاء الاصطناعي في الوقت الحالي، فغالبًا ما تُصنّف النماذج اللغوية الكبيرة بناءً على كميات البيانات التي تستخدمها، ويمكنها فهم النصوص وتوليدها أيضًا. يمكن أن تكون هذه النماذج مساعدة في الكتابة أو حتى البرمجة. تتضمن تطبيقات معالجة اللغة الطبيعية إعادة صياغة المفاهيم المعقدة ببساطة وترجمة النصوص وصياغة الوثائق القانونية وحتى إعداد خطط التمارين الرياضية (على الرغم من وجود بعض القيود في مثل هذا الاستخدام). على سبيل المثال، ليكس Lex هي أداة NLP تُستخدم للكتابة، وهي ذات وظائف متعددة من بينها اقتراح العناوين وإكمال الجمل وصياغة فقرات كاملة حول موضوع معين. وأحد أشهر هذه النماذج حاليًا هو GPT الذي طورته شركة OpenAI. حيث يمكن لنموذج GPT الرد على أي سؤال أو أمر تقريبًا في غضون ثوان مع دقة عالية. تتوفر نماذج OpenAI المتنوعة من خلال واجهة برمجة تطبيقات واحدة. يمكن لهذا النموذج فعل العديد من الأشياء خلافًا لليكس المحدود بمهام معينة، حيث يمكنه العمل مع الشيفرات البرمجية واقتراح حلول برمجية تلبي المتطلبات الوظيفية المحددة والتعرف على مشاكل الشيفرات البرمجية لتسهيل الأمور على المطورين إلى حد كبير. الصور: نماذج الانتشار يتضمن نموذج الانتشار Diffusion Model ضمن عالم الشبكات العصبية العميقة، متغيرات كامنة لديها القدرة على فهم البنية الأساسية للصورة المقدمة عن طريق إزالة أي ضبابية أو ضوضاء. بمجرد تدريب شبكة النموذج على فهم التجريد المفاهيمي داخل الصورة، فإنها تكتسب القدرة على توليد أشكال مختلفة جديدة من تلك الصورة. للتوضيح، عند إزالة التشويش من صورة تُظهر قطة، يُدرك نموذج الانتشار بصريًا تصويرًا واضحًا للقطط ويتعلم خصائصها البصرية ويستخدم هذه المعرفة المكتسبة لإنتاج أشكال متنوعة من صور القطط. يمكن استخدام نماذج الانتشار لتقليل الضوضاء أو زيادة حدة الصور (تعزيزها وتنقيتها)، أو معالجة تعابير الوجه أو إنشاء صور تظهر فيها علامات شيخوخة الوجه لاقتراح الشكل الذي قد يبدو عليه الشخص بمرور الوقت. يمكنك تصفح محرك بحث ليكسكا Lexica لمشاهدة قدرات نماذج الذكاء الاصطناعي هذه عندما يتعلق الأمر بإنشاء صور جديدة. البرنامج التعليمي: استخدام نموذج انتشار ونموذج GPT لتوضيح كيفية استخدام هذه التقنيات، دعونا نُنشئ صور رسوم متحركة (أو صور أنمي Anime) باستخدام نموذج نشر و GPT من خلال مكتبة HuggingFace. لا يتطلب الأمر أيّة بُنى تحتية أو كتابة شيّفرات برمجيّة معقدة، حيث أننا سنبدأ بنموذج انتشار جاهز للاستخدام (أي نموذج جاهز مُدرّب مسبقًا) ونحتاج فقط إلى تحسينه (أو بمعنى أدق "تكييفه" مع ما نحتاج، وفي حالتنا إنشاء صور إنمي لأنفسنا). ملاحظة: تشرح هذه المقالة كيفية استخدام نماذج الذكاء الاصطناعي التوليدية ونماذج اللغة لإنشاء صور عالية الجودة لنفسك بأساليب مثيرة للاهتمام، ولا ينبغي إساءة استخدام المعلومات الواردة في هذه المقالة لإنشاء صور مزيفة بشكل ينتهك شروط استخدام Google Collaboratory. متطلبات الإعداد والصور قبل الاستمرار، لابد أن يكون لديك: حساب جوجل، من أجل استخدام جوجل درايف وكولاب Colab. حساب على OpenAI، من أجل إمكانية الوصول إلى واجهة برمجة التطبيقات الخاصة بنموذج GPT لإرسال الأوامر إليه. ستحتاج أيضًا إلى 20 صورة لنفسك - أو أكثر لتحسين الأداء - محفوظة على الجهاز الذي تخطط لاستخدامه تطبيق المعلومات الواردة في هذا المقال . للحصول على أفضل النتائج، يجب أن تحقق الصور الشروط التالية: ألا نقل أعاد الصورة عن 512 × 512 بكسل. أن تكون موجودًا وحدك في الصورة. أن تكون جميعها بنفس التنسيق على سبيل المثال، بتنسيق JPEG أو PNG لتحقيق الاتساق. أن تؤخذ من زوايا مختلفة. يجب أن تتضمّن ثلاث إلى خمس صور لكامل الجسم واثنتين إلى ثلاث للجزء الأوسط من الجسم كحد أدنى. يجب أن يكون الباقي صورًا للوجه. لا يلزم أن تكون الصور مثالية، فقد يكون من المفيد معرفة مدى تأثير الانحراف عن هذه المتطلبات على المخرجات. توليد الصور بالذكاء الاصطناعي باستخدام نموذج انتشار من مكتبة HuggingFace للبدء، افتح ملف جوجل كولاب Google Colab المصاحب لهذا البرنامج التعليمي، والذي يحتوي على الشيفرة البرمجية المطلوبة. شغّل الخليّة الأولى لربط ملف كولاب بجوجل درايف Google Drive لتخزين النموذج وحفظ الصور التي نُنشئها لاحقًا. شغّل الخليّة الثانية لتثبيت التبعيات المطلوبة. شغّل الخليّة الثالثة لتنزيل نموذج الانتشار من HuggingFace. اكتب في الخليّة الرابعة "كيف أبدو" How I Look (اكتبها باللغة الإنجليزية) في حقل اسم الجلسة Session_Name، ثم شغّل الخليّة. يحدد اسم الجلسة عادةً المفهوم الذي سوف يتعلمه النموذج. شغّل الخليّة الخامسة وحمّل صورك. انتقل إلى الخليّة السادسة لتدريب النموذج. اضبط الخيار Resume_Training على True قبل تشغيّل الخليّة، حيث يمكنك تكييف نموذج الانتشار المُستخدم عدة مرات، من خلال تدريبه على عينات بيانات تخدم أغراض محددة. قد تستغرق هذه الخطوة حوالي ساعة حتى تكتمل. أخيرًا، شغّل الخليّة السابعة لاختبار النموذج الخاص بك ومشاهدته أثناء العمل. سيقوم النظام بعرض عنوان URL، حيث ستجد واجهة تُمكّنك من إنتاج صورك. من خلال هذه الواجهة يمكنك إدخال أمر، ثم اضغط على زر الإنشاء "Create" لعرض الصور. بعد اكتمال تدريب النموذج، يُمكننا الآن تجربة العديد من الأوامر التي تُنتج أنماطًا بصرية مختلفة (على سبيل المثال، يمكنك أن تجرب "أنا كشخصية متحركة me as an animated character" أو "أنا كلوحة انطباعية me as an impressionist painting"). هذه النماذج حساسة للأوامر، أي أنها تُعطي نتائج مُختلفة لأوامر لها نفس المعنى، لكن مكتوبة بطريقة مختلفة (لهذا السبب ظهر مؤخرًا مُسمّى هندسي جديد اسمه هندسة النصوص المُدخَلة Prompt engineering. ويُعد استخدام GPT لمساعدتنا في صياغة الأوامر فكرة رائعة، لأنه يوفر تفاصيل إضافية أدق من تلك التي يُنشئها المستخدم عمومًا، بالتالي يزيد من جودة نتائج نموذجنا. تحسين مدخلات نموذج الانتشار: تعزيز الفعاليّة باستخدام GPT سنضيف GPT إلى خط المعالجة الخاص بنا عبر مكتبة مخصصة توفرها OpenAI، على الرغم من وجود خيارات أخرى توفر وظائف مماثلة لخدمة أهدافنا، لكن بالطبع GPT هو أفضل نموذج موجود حاليًّا لهكذا أهداف. بدايةً عليك التسجيل على منصة OpenAI وإنشاء مفتاح واجهة برمجة التطبيق API key خاص بك. الآن، في قسم "إنشاء أوامر جيدة Generating good prompts" في ملف كولاب الذي نعمل عليه، ثبّت مكتبة OpenAI: pip install openai حمّل بعد ذلك المكتبة وأسند مفتاح API الخاص بك: import openai openai.api_key = "YOUR_API_KEY" سننتج أوامر محسّنة من خلال نموذج GPT من أجل إنشاء صورنا بأسلوب شخصيات الأنيمي. ما عليك سوى استبدال 'YOUR_SESSION_NAME' باسم الجلسة الذي حددته في الخلية 4 من الملف، والذي يجب أن يكون "كيف أبدو" How I Look. ASKING_TO_GPT = 'Write a prompt to feed a diffusion model to generate beautiful images '\ 'of YOUR_SESSION_NAME styled as an anime character.' response = openai.Completion.create(model="text-davinci-003", prompt=ASKING_TO_GPT, temperature=0, max_tokens=1000) print(response["choices"][0].text) تتراوح درجة الحرارة temperature بين القيمة 0 و 2، وهي تحدد ما إذا كان يجب على النموذج الالتزام الصارم بالبيانات التي تدرّب عليها (نمرر في هذه الحالة قيم قريبة من 0)، أو أن يكون أكثر إبداعًا في مخرجاته (نمرر في هذه الحالة قيم قريبة من 2). وتحدد المعلمة max_tokens عدد الوحدات النصيّة الأعظمي الذي يمكن إنشاؤه، حيث أن الوحدة النصيّة قد تكون رقمًا أو رمزًا أو حرفًا أو كلمة أو جزء من كلمة. سيكون الخرج كما يلي (تجدر الملاحظة إلى أن هذا الخرج قد يتغير عندما تُجرّب تنفيذ الشيفرة بنفسك): "Juan is styled as an anime character, with large, expressive eyes and a small, delicate mouth. His hair is spiked up and back, and he wears a simple, yet stylish, outfit. He is the perfect example of a hero, and he always manages to look his best, no matter the situation." أخيرًا، من خلال تمرير هذا النص كمُدخل في نموذج الانتشار، يكون الخرج كما يلي: إن استخدام نموذج GPT لصياغة النصوص المدخلة إلى نموذج النشر يُلغي الحاجة إلى النظر بعناية في الفروق الدقيقة في مظهر شخصية الأنمي -يمكن لنموذج GPT إنشاء وصف مناسب نيابة عنك. ومن خلال GPT سيكون لديك المرونة اللازمة لتحسين الأوامر بناءً على تفضيلاتك. والآن بعد أن أكملت هذا البرنامج التعليمي، سيكون بإمكانك الآن إنشاء صور معقدة ومبتكرة، سواء كانت لنفسك أو لأي مفهوم ترغب فيه. مزايا الذكاء الاصطناعي بين يديك تُعد نماذج GPT ونماذج الانتشار من التطبيقات الحديثة الأساسية للذكاء الاصطناعي. وقد تعلمت في هذا المقال كيفيّة تطبيق إحدى نماذج الانتشار، ثم كيفيّة تحسين مخرجاته عن طريق استخدام خرج نموذج GPT كمدخل لنموذج الانتشار. وبذلك تكون قد أنشأت خط معالجة من نموذجين لغويين كبيرين للاستفادة بأقصى حد منهما. تؤثر تقنيات الذكاء الاصطناعي هذه على حياتنا بعُمق، ويتوقع كثيرون أن تؤثر النماذج اللغوية الكبيرة جذريًّا على سوق العمل في مجموعة متنوعة من المهن وتساعد على أتمتة مهام معينة وإعادة تشكيل الأدوار المهنيّة الحالية. وعلى الرغم من أننا لا نستطيع التنبؤ بالمستقبل، إلا أنه لا جدال في أن الفرص ستتاح مسبقلًا للذين يستفيدون بذكاء من تقنيات معالجة اللغة الطبيعية NLP والذكاء الاصطناعي التوليدي لتحسين عملهم وإنتاجياتهم من أولئك الذين الذين لا يفعلون. ترجمة -وبتصرُّف- للمقال Advantages of AI: Using GPT and Diffusion Models for Image Generation لصاحبه Juan Manuel Ortiz de Zarate. اقرأ أيضًا مكتبات وأطر عمل الذكاء الاصطناعي: القوة الكامنة خلف الأنظمة الذكية خوارزميات الذكاء الاصطناعي الذكاء الاصطناعي: مراحل البدء والتطور والأسس التي نشأ عليها تعلم الذكاء الاصطناعي
  8. يعد الذكاء الاصطناعي Artificial Intelligence مجالًا حديثًا يُحدث تغييرًا في الطريقة التي نعيش ونعمل ونتفاعل بها مع العالم والتكنولوجيا. إن فهم أساسيات الذكاء الاصطناعي في هذا العصر الذي يتسم بالتطورات المذهلة والتحوّل الرقمي والتقدّم الذي يحدث بسرعة الضوء -ليس مجرد خيار؛ إنه ضرورة. يدور الذكاء الاصطناعي في جوهره حول إنشاء آلات ذكية يمكنها أداء المهام التي تتطلب عادةً الذكاء البشري. يمكن أن تكون هذه المهام متعلقة بمواضيع مثل التعرّف على الكلام وفهم اللغة البشرية وحل المشكلات وحتى التنبؤ بالأحداث المستقبلية كحالة الطقس أو حدوث الكوارث. لقد صُممت أنظمة الذكاء الاصطناعي للتعلم من البيانات، مما يسمح لها بتحسين أدائها بمرور الوقت دون الحاجة إلى برمجة صريحة. تخيل عالمًا تتمكّن فيه أجهزة الحاسب من الفهم والتعلم من الخبرات والتكيف مع المعلومات الجديدة -هذا هو جوهر الذكاء الاصطناعي. سواء كنت مهتمًا بمفهوم الآلات التي يمكنها التعلّم واتخاذ القرارات مثل البشر أو كنت مهتمًا ببساطة بالتكنولوجيا التي تعمل على تشغيل المساعدين الصوتيين والسيارات ذاتية القيادة وغيرهم الكثير، فإن هذه الرحلة إلى أساسيات الذكاء الاصطناعي مصممة خصيصًا لك. إننا نزيل فيها الغموض عن التعقيدات المحيطة بالذكاء الاصطناعي ونقسّم المفاهيم المعقدة إلى أجزاء بسيطة وسهلة. ما هو الذكاء الاصطناعي؟ الذكاء الصناعي هو فرع من فروع علوم الحاسوب يركز على إنشاء آلات ذكية قادرة على أداء المهام التي تتطلب عادةً الذكاء البشري. وتشمل هذه المهام التعلم والتفكير وحل المشكلات وفهم اللغة البشرية والتعرف على الكلام ..إلخ. يُمكّن الذكاء الصناعي في جوهره الآلات من محاكاة الوظائف المعرفيّة البشريّة. صُمّمت أنظمة الذكاء الصناعي لتحليل وتفسير البيانات المعقدة والتكيف مع البيئات المتغيرة وتحسين أدائها بمرور الوقت، كل ذلك دون تدخل بشري. إذًا الهدف من الذكاء الصناعي هو تطوير آلات يمكنها التفكير والتعلم مثل البشر وإظهار سمات مثل الإدراك والتفكير واتخاذ القرار. هناك نوعان رئيسيان من الذكاء الصناعي: الذكاء الصناعي الضيق أو الضعيف Narrow or Weak AI: تم تصميم هذا النوع من الذكاء الصناعي لأداء مهمة محددة أو نطاق ضيق من المهام. وهي تعمل في ظل مجموعة من الشروط المحددة مسبقًا وغير قادرة على تعميم معرفتها على مجالات أخرى. تشمل الأمثلة الأشهر المساعدين الافتراضيين مثل سيري Siri وأليكسا Alexa، بالإضافة إلى خوارزميات التوصية التي تستخدمها مواقع الويب مثل اليوتيوب أو الفيس بوك أو أمازون أو نصات التجارة الإلكترونية ..إلخ. الذكاء الاصطناعي العام أو القوي General or Strong AI: يشير الذكاء الاصطناعي العام إلى آلة تتمتع بالقدرة على الفهم والتعلم وتطبيق المعرفة بطريقة لا يمكن تمييزها عن الذكاء البشري. يظل هذا المستوى من الذكاء الاصطناعي نظريًا ويخضع للبحث والاستكشاف المستمر. يمكنك قراءة المزيد من التفاصيل عن أنواع الذكاء الاصطناعي هنا. تاريخ وتطور الذكاء الاصطناعي إن تاريخ الذكاء الصناعي هو رحلة مثيرة للاهتمام تمتد على مدى عدة عقود وتتميز بمعالم هامة واختراقات وتحولات نموذجية في فهمنا للآلات الذكية. نتعمق هنا في كيفية تطور الذكاء الصناعي من المفاهيم النظرية إلى التقنيات التحويلية. ولادة الذكاء الاصطناعي: الخمسينيات والستينيات تمت صياغة مصطلح "الذكاء الاصطناعي" في منتصف القرن العشرين، مما وضع الأساس لمجال جديد من الأبحاث. في تلك الفترة، اقترح علماء رائدون مثل آلان تورينج وجون مكارثي فكرة الآلات التي يمكنها محاكاة الذكاء البشري. ذهب مكارثي لتنظيم مؤتمر دارتموث في عام 1956، والذي اعتبر على نطاق واسع ولادة الذكاء الاصطناعي كنظام أكاديمي. شتاء الذكاء الاصطناعي: السبعينيات والثمانينيات خلال فترة "شتاء الذكاء الاصطناعي" التي امتدت من الستينيات حتى منتصف الثمانينيات، واجهت صناعة الذكاء الاصطناعي العديد من التحديات الرئيسية. كانت التكنولوجيا المتاحة في ذلك الوقت غير كافية لدعم تطوير الذكاء الاصطناعي بالطريقة المطلوبة، حيث كانت الحواسيب والمعالجات بطيئة وضعيفة في الأداء، مما حد من إمكانية تطوير تقنيات الذكاء الاصطناعي المتقدمة. بالإضافة إلى ذلك، كانت هناك توقعات متفائلة جدًا غير واقعية بخصوص قدرة الآلات على فهم اللغة والتعلم تلقائيًا بشكل سريع، وهو ما كان صعب المنال في ظل الوضع التكنولوجي في ذلك الوقت. كما تعثرت الكثير من المشاريع البحثية والتطويرية في هذا المجال بسبب نقص التمويل، حيث لم تكن الحكومات والشركات مستعدة للاستثمار بشكل كبير في هذا المجال بسبب عدم رؤية العائد المالي الفوري. ولم يكن هناك كمية كافية من البيانات المتاحة لتدريب النماذج الذكية بفعالية، مما قيد إمكانية تطوير النماذج الذكية. ظهور الأنظمة الخبيرة: الثمانينات لقد تحققت خلال شتاء الذكاء الاصطناعي الكثير من التطورات في مايسمى الأنظمة الخبيرة أو المتخصصة Expert systems، وهي عبارة عن برامج ذكاء اصطناعي مصممة لتقليد الخبرة البشرية في مجالات محددة. وقد وجدت هذه الأنظمة تطبيقات عديدة في مجالات مثل الطب والتمويل، مما أدى إلى تجديد الاهتمام بأبحاث الذكاء الاصطناعي. عودة التعلم الآلي: التسعينيات إلى العقد الأول من القرن الحادي والعشرين شهد أواخر القرن العشرين عودة ظهور الذكاء الصناعي مدفوعًا بالتقدم في خوارزميات التعلم الآلي. بدأ الباحثون في تطوير تقنيات تُسمى الشبكات العصبية (المستوحاة من بنية الدماغ البشري) والنماذج الإحصائية التي يمكنها التعلم من البيانات. كانت هذه الفترة بمثابة بداية التطبيقات العملية للذكاء الاصطناعي، بما في ذلك التعرّف على الكلام والتنقيب في البيانات. ثورة التعلم العميق: 2010 إلى الوقت الحاضر شهد العقد الأول من القرن الحادي والعشرين قفزة ملحوظة إلى الأمام مع ظهور تقنيات تدعى تقنيات التعلم العميق التي مكنت أنظمة الذكاء الصناعي من معالجة كميات هائلة من البيانات وحل المهام المعقدة. شهد هذا العصر اختراقات في التعرف على الصور ومعالجة اللغات الطبيعية (اللغة البشرية) والأنظمة المستقلة Autonomous systems مما أدى إلى تحوّل في الصناعات والحياة اليومية. المشهد الحالي والآفاق المستقبلية يعد الذكاء الصناعي اليوم جزءًا لا يتجزأ من مختلف القطاعات، بما في ذلك الرعاية الصحية والتمويل والنقل والترفيه والتجارة والألعاب والإعلام وكل شيء تقريبًا. يمكنك الاطلاع على مقالة مستقبل الذكاء الاصطناعي للحصول على استشرافٍ أوسع لقدرات الذكاء الصناعي الممكنة في والمستقبل. إن تاريخ الذكاء الصناعي وتطوره لا يعكس التقدم التكنولوجي فحسب، بل يعكس أيضًا مرونة العقل البشري وإبداعه. إن استمرار الذكاء الصناعي في التطور يحمل وعدًا بإحداث ثورة في الصناعات وحل التحديات العالمية وزيادة الإمكانات البشرية بطرق لم يكن من الممكن تصورها من قبل، ويمكن أن تجد مزيد من التفاصيل هنا. المفاهيم الأساسية اللازمة لتطوير أنظمة الذكاء الاصطناعي هناك العديد من المفاهيم والعناصر المرتبطة بأنظمة الذكاء الاصطناعي والتي تُمكّن الآلات من محاكاة الذكاء البشري وأداء مجموعة واسعة من المهام. تشمل هذه المفاهيم والعناصر: البيانات الضخمة البيانات هي شريان الحياة لأنظمة الذكاء الصناعي. إنها بمثابة الأساس الذي تعمل عليه خوارزميات الذكاء الصناعي. بدون البيانات لم نكن لنرى ذكاءًا صناعيًا، فنماذج الذكاء الاصطناعي كالنار والبيانات كالحطب، كلما أعطيتها بيانات توهجت أكثر. إن ثورة البيانات الضخمة Big data ليست مجرد مصطلح عصري يطرحه بائعو التقنية الذين يقترحون طرقًا مبتكرة لتخزين المعلومات وتحليلها؛ بل تعد قوة كبيرة تعيد تشكيل حياتنا اليومية. ومن المحتمل أنك صادفت هذا المصطلح في العديد من المنشورات العلمية والتجارية، وأثار فضولك لمعرفة معناه الحقيقي. من الناحية الفنية، تمثل البيانات الضخمة مجموعات بيانات ضخمة ومعقدة لدرجة أن الأساليب التقليدية التي تتضمن تخزينًا إضافيًا أو زيادة قوة الحوسبة تفشل في إدارتها. يمثل هذا المفهوم تحولًا عميقًا في كيفية تخزين البيانات ومعالجتها، مما يؤدي إلى أكثر من مجرد تقدم كمي فهو يتيح إجراء تحسينات نوعية، ليعزز قدرتنا على أداء المهام باستخدام هذه البيانات. فهم أنواع البيانات: المُهيكلة وغير المُهيكلة بينما ينظر البشر إلى البيانات بتنسيقات مختلفة، فإن أجهزة الحاسب تقوم بمعالجتها بشكل أساسي كتدفق مستمر من الواحدات والأصفار، فهي لغة الحواسيب الأساسية. ويمكن تصنيف البيانات إلى نوعين أساسيين بناءً على كيفية إنشائها واستهلاكها. البيانات المُهيكلة أو المُنظّمة Structured data، وهي ذات تنسيق واضح مع مواقع معروفة لكل جزء من المعلومات، وغالبًا ما تشبه جداول قاعدة البيانات حيث يحتوي كل عمود على أنواع بيانات محددة. تُنظّم البيانات المهيكلة بتأني وتُجمع بشكل انتقائي وتخزّن بطريقة منهجية، تمامًا مثل ترتيب الكتب في المكتبة، مما يجعل من السهل تحديد موقع بيانات محددة عند الحاجة. ومن ناحية أخرى، تتضمن البيانات غير المُهيكلة أو غير المنظّمة Unstructured data عناصر مثل الصور ومقاطع الفيديو والتسجيلات الصوتية، ويمكن أن تشمل نصًا في نموذج يتطلب وضع علامات عليه بخصائص مثل الحجم أو التاريخ أو نوع المحتوى. تفتقر البيانات غير المُهيكلة إلى بنية محددة مسبقًا، مما يجعل من الصعب تحديد بيانات محددة ضمن مجموعة بيانات، حيث يتم تمثيلها كتسلسلات من الواحدات والأصفار التي تتطلب تفسيرًا أو تصورًا بواسطة التطبيق. تمثيل المعرفة إن تمثيل المعرفة knowledge representation هو مفهوم أساسي في الذكاء الاصطناعي يركز على كيفية التقاط المعرفة حول العالم وتنظيمها بطريقة يمكن لأنظمة الحاسوب الاستفادة منها بفعالية. في أنظمة الذكاء الاصطناعي، يتضمن تمثيل المعرفة تخزين المعلومات بحيث يمكن استخدامها لاستخلاص الاستدلالات واتخاذ القرارات وحل المشكلات. الخوارزميات تلعب البيانات دورًا أساسيًّا في ثورة الذكاء الاصطناعي، وتشير التطورات الحديثة إلى أنه في بعض الحالات، يكون اختيار الحجم المناسب من البيانات له أهمية أكبر من العثور على الخوارزمية المثالية. هذا ما أوضحته ورقة بحثية هامة أجراها باحثون في مايكروسوفت Microsoft عام 2001 بانكو وبريل والمتعلقة بنماذج اللغة نماذج الذكاء الاصطناعي التي تهدف إلى معالجة النصوص وتوليدها وفهمها فقد كشفت أبحاثهما عن رؤية عميقة: عندما نهدف إلى إنشاء نموذج لغوي، لا ينبغي أن تكون الخوارزمية هي الأكثر تعقيدًا وصحة. فمن خلال غمر النموذج بمليارات الكلمات المتعلقة بسياق المشكلة، فإن أي خوارزمية، بغض النظر عن مدى تعقيدها، تبدأ في عرض أداء عالي بشكل ملحوظ. على الرغم من أنه لا غنى عن كمية كبيرة من البيانات، إلا أن وجود خوارزمية مناسبة يظل ضروريًا لاستخلاص معلومات ذات معنى. كما أن التحليل الدقيق للبيانات، والذي يتضمن سلسلة من الخطوات المنهجية، أمر ضروري لضمان التناغم بين الخوارزميات المختارة والبيانات. لا توجد طرق مختصرة؛ حتى في مجال الأتمتة الذكية، يكون للتحليل أحيانًا الأسبقية على الأتمتة. إن الآلات القادرة على التعلم المستقل وتجنب التدخل البشري تمامًا هي احتمال بعيد، وليست حقيقة حالية. دور الخوارزمية غالبًا ما يجذب الذكاء الاصطناعي انتباهنا عندما يتجلى في الأدوات التي تقدم تفاعلات مبتكرة تشبه تفاعلات الإنسان، فكر مثلًا في المساعدين الافتراضيين مثل سيري Siri وكورتانا Cortana أو الأدوات اليومية مثل أجهزة توجيه نظام تحديد المواقع العالمي GPS وأنظمة تجنب الاصطدام في المركبات والطائرات التي تدمج الذكاء الاصطناعي بشفافية دون لفت الانتباه، وذلك ببساطة لأنها أصبحت منتشرة في كل مكان وأصبحت أمرًا مفروغًا منه. هذه الظاهرة، التي صاغتها باميلا ماكوردوك بدقة "تأثير الذكاء الاصطناعي AI effect" في عام 1979، تسلط الضوء على أنه بمجرد أن تصبح أنظمة الذكاء الاصطناعي ماهرة وبارعة جدًا في مهامها، فإنها تتلاشى في الخلفية (تفقد اهتمام الجمهور والخبراء)، وتطغى التحديات الأحدث في هذا المجال على أهميتها. بمعنى آخر، بمجرد أن تصبح أنظمة الذكاء الاصطناعي شائعة وتؤدي المهام ببراعة، فإنها تصبح جزءًا طبيعيًا من بيئتنا ويختفي توهجها. ينتج عن ذلك أن يميل الناس إلى نسيان الدور الأساسي الذي تلعبه الخوارزميات الكلاسيكية في قيادة كل التقنيات الموجودة والتقليل من أهمية مساهمتها المحورية في الذكاء الاصطناعي، ويتحول الاهتمام إلى تقنيات أكثر غرابة أو التطورات الحديثة مثل التعلم الآلي والتعلم العميق. الخوارزمية في جوهرها عبارة عن مجموعة منهجية من العمليات، يتم تنفيذها عادةً بواسطة جهاز حاسب، مما يضمن حلًا نهائيًا لمشكلة ما خلال إطار زمني محدد أو يشير إلى عدم قابليتها للحل. بينما ظل البشر يحلون الخوارزميات يدويًا لعدة قرون، فإن تعقيد المشكلات الحديثة يستلزم المساعدة الحسابية. تم تصميم الخوارزميات لتبسيط هذه العملية، بهدف إيجاد حلول دقيقة وسريعة وفعالة أيضًا. تمثل هذه الخوارزميات إنجازات فكرية متأصلة في العقل البشري، وتعكس الآلات التي تعمل بالخوارزميات بشكل أساسي الذكاء المتضمن في هذه الإجراءات. إن فهم التأثير العميق للخوارزميات على الذكاء الاصطناعي أمر بالغ الأهمية، لأنه يذكرنا بأن الجوهر الحقيقي للذكاء الاصطناعي يكمن في التصميم الدقيق لهذه العمليات الأساسية وتنفيذها. النمط Pattern يشير مصطلح النمط إلى تمثيل أو تجميع البيانات بطريقة تسمح بالكشف عن تكرار السلوك أو الخصائص المشتركة. ويمكن استخدام هذا التمثيل لفهم البيانات واستخراج المعلومات والقواعد منها. على سبيل المثال، يمكن لنموذج تعلم الآلة أن يتعرف على نماذج معينة في الصور، مثل التعرف على وجوه الأشخاص لأن لها شكل وسمات مميزة. مثال آخر، يمكن لنظام يعمل بالذكاء الصناعي تصنيف رسائل البريد الإلكتروني إلى "رسائل هامة" و "رسائل غير هامة" عن طريق اكتشاف أنماط مثل الكلمات المفتاحية وهياكل الرسائل. الميزات أو السمات Features السمات عبارة عن أجزاء محددة من المعلومات ضمن البيانات التي تستخدمها الخوارزمية لإجراء تنبؤات أو قرارات. في التعرف على الصور، يمكن أن تتضمن السمات الحواف Edges أو الألوان أو الأنسجة أو قيم البكسلات أما في التنبؤ بأسعار المنازل، فيمكن أن تكون السمات هي حجم ومساحة المنزل وعدد الغرف والموقع. يعد اختيار السمات ذات الصلة أمرًا بالغ الأهمية لدقة نظام الذكاء الاصطناعي. اقرأ مقالة خوارزميات الذكاء الاصطناعي للحصول على فهم شامل. النموذج Model لحل أي مشكلة أو مهمة باستخدام الذكاء الاصطناعي، فإن الهدف الأساسي هو إنشاء نموذج باستخدام خوارزمية واحدة أو أكثر. نطوّر أداء هذا النموذج من خلال التدريب الدقيق للخوارزميات المُستخدمة. يتمثل الطموح النهائي في أن يقدم النموذج قدرات تنبؤية استثنائية، بحيث نضمن فعاليته في سيناريوهات العالم الحقيقي. على سبيل المثال، يمكن للنموذج الذي تم تدريبه على آلاف صور القطط التعرف على القطط في الصور الجديدة. النماذج هي جوهر أنظمة الذكاء الصناعي. من ناحية أخرى، يتضمن تدريب نموذج الذكاء الاصطناعي بشكل رئيسي تعريّضه لكميات كبيرة من البيانات بهدف فهم وتعلّم هذه البيانات. يتم تحقيق ذلك من خلال الخوارزميات التي يمكنها التعلّم من البيانات. تُعتبر مرحلة تدريب النموذج خطوة هامة في إنشاء أنظمة الذكاء الاصطناعي القادرة على اتخاذ القرارات الذكية الفعّالة. الأجهزة والعتاد Hardware تحدثنا في فقرة سابقة عن النكسات التاريخية التي واجهتها مساعي الذكاء الاصطناعي المبكرة بسبب عدم كفاية قدرات الأجهزة أو الموارد الحوسبية عمومًا. كان شح الموارد الحسابية كبيرًا للغاية ولم تكن قادرة على تنفيذ المهام بسرعة كافية، مما أعاق حتى العمليات البسيطة، فما بالك بالمهام المعقدة المتمثلة في تكرار الفكر البشري. تم تصوير هذا التحدي بوضوح في الفيلم الرائع "The Imitation Game"، حيث نجح آلان تورينج ببراعة في فك شفرة إنجما من خلال تحديد عبارة معينة في كل رسالة. كان نجاح تورينج يعتمد على استغلال ثغرة في طريقة مشغلي آلة إنجما، والتي لولاها لكان نجاح جهاز الحاسب الخاص به أمرًا مستحيلًا لأنه كان بطيئًا جدًا للقيام بالمهمة. يؤكد الواقع التاريخي الأكثر تعقيدًا عما صوّره الفيلم، عمق التحديات التي يواجهها تورينج. حل ما لا يمكن حله: كيف تمكن آلان تورينج وفريقه من فك شفرة اللغز؟. لحسن الحظ تم حل مشكلة الموارد الحوسبية في وقتنا هذا إلى حد جيد، مما أتاح لنا رؤية الذكاء الاصطناعي في حياتنا اليومية. عمومًا، وحتى مع وجود أجهزة متخصصة مصممة لإجراء عمليات حسابية سريعة اليوم مثل وحدات المعالجة المركزية CPUs ووحدات معالجة الرسومات GPUs ومسرعات الذكاء الاصطناعي المتخصصة مثل TPUs، فإن الآلة المصمّمة لمحاكاة الفكر البشري تواجه قيودًا تُمليها مدخلاتها ومخرجاتها، بالتالي لازالت مكونات الحواسيب لا تساعد في الارتقاء إلى مستوى تكرار تعقيد الدماغ البشري. لغات برمجة الذكاء الاصطناعي لغات برمجة الذكاء الاصطناعي هي مفتاح أساسي لتطوير تطبيقات ذكية ونظم قادرة على التعلم واتخاذ القرارات. هذه اللغات تمثل الواجهة التي تسمح للمطورين بالتفاعل مع الأنظمة الذكية وتحديد سلوكها. من بين أهم هذه اللغات تأتي لغة بايثون Python التي تُعَدُّ لغة برمجة مفتوحة المصدر وسهلة التعلم، وهي مثالية لبناء نماذج الذكاء الاصطناعي. إلى جانب بايثون تأتي لغة R التي تستخدم أساسًا في التحليل الإحصائي والتعلم الآلي. مكتبات وأطر التعلم الآلي مكتبات وأطر التعلم الآلي هي أدوات برمجية ومكتبات تسهل تطوير نماذج الذكاء الاصطناعي وتدريبها. تشمل أطر التعلم الآلي الشائعة تنسرفلو TensorFlow وباي تورش PyTorch وسايكيت-ليرن scikit-learn. واجهة المستخدم UI توفر واجهة المستخدم وسيلة للبشر للتفاعل مع أنظمة الذكاء الاصطناعي. يمكن أن تكون بسيطة مثل واجهة الدردشة النصية أو معقدة مثل روبوت يشبه الإنسان. تعد معرفة هذه العناصر والمفاهيم الأساسية أمرًا ضروريًا لفهم كيفية عمل أنظمة الذكاء الاصطناعي وكيفية تطبيقها في سيناريوهات العالم الحقيقي المختلفة. دورة الذكاء الاصطناعي احترف برمجة الذكاء الاصطناعي AI وتحليل البيانات وتعلم كافة المعلومات التي تحتاجها لبناء نماذج ذكاء اصطناعي متخصصة. اشترك الآن كيف يعمل الذكاء الاصطناعي يشمل الذكاء الاصطناعي مجموعة واسعة من التقنيات والأساليب التي تهدف إلى محاكاة الذكاء البشري في الآلات. في حين أن الذكاء الاصطناعي قد يبدو صعبًا، فإن فهم مفاهيمه الأساسية يعد خطوة أولى حاسمة في إزالة الغموض عن هذا المجال الرائع. يعمل الذكاء الاصطناعي في جوهره من خلال معالجة وتحليل كميات هائلة من البيانات لاكتشاف الأنماط وفهم العلاقات بينها. فيما يلي المفاهيم الأساسية وراء كيفية عمل الذكاء الاصطناعي: إدخال البيانات: تبدأ أنظمة الذكاء الاصطناعي بالبيانات. يمكن أن تأتي هذه البيانات في أشكال مختلفة، مثل النصوص أو الصور أو قراءات أجهزة الاستشعار. إنه بمثابة المادة الخام التي تعمل بها خوارزميات الذكاء الاصطناعي. المعالجة المسبقة للبيانات: قبل أن تتمكن خوارزميات الذكاء الاصطناعي من فهم البيانات، غالبًا ما تحتاج إلى التنظيف والإعداد. يتضمن ذلك مهام مثل إزالة الضوضاء ومعالجة القيم المفقودة وتوحيد التنسيق. الخوارزميات: خوارزميات الذكاء الاصطناعي هي التعليمات الرياضية التي تقود عمليات صنع القرار. يمكن أن تتراوح من الأنظمة البسيطة القائمة على القواعد إلى نماذج التعلم الآلي الأكثر تقدمًا. يعتمد اختيار الخوارزمية على المهمة المحددة التي يحتاج الذكاء الاصطناعي إلى تنفيذها. التدريب: في حالة التعلم الآلي، تخضع أنظمة الذكاء الاصطناعي للتدريب حيث تتعلم كيفية التعرّف على الأنماط في البيانات. الاستدلال: بعد انتهاء التدريب، تستخدم أنظمة الذكاء الاصطناعي المعرفة المكتسبة لإجراء تنبؤات أو حل المشكلات أو تقديم التوصيات. يُعرف هذا بالاستدلال وهو الطريقة الأساسية التي يتفاعل بها الذكاء الاصطناعي مع العالم الحقيقي. حلقة التقييم: في سياق الذكاء الاصطناعي، يشير "التقييم" إلى العملية التي من خلالها تتعلم أنظمة الذكاء الاصطناعي وتحسن أدائها بمرور الوقت. بعد نشر نموذج الذكاء الاصطناعي والتفاعل مع بيانات العالم الحقيقي أو المستخدمين، فإنه يتلقى تعليقات بناءً على نتائج تنبؤاته أو قراراته. تعتبر حلقة التغذية الراجعة هذه ضرورية لتحسين النموذج وتعزيز دقته وفعاليته. المخرجات: المخرجات النهائية لنظام الذكاء الاصطناعي هي النتائج أو الاستجابات التي تولدها أنظمة الذكاء الاصطناعي بعد معالجة بيانات الإدخال باستخدام الخوارزميات. وهو يمثل نتيجة التحليل أو اتخاذ القرار أو التوصية أو التنبؤ الذي يقوم به نظام الذكاء الاصطناعي بناءً على الأنماط والمعلومات التي تعلمها أثناء التدريب أو من خلال قواعده المبرمجة. يعد فهم هذه المفاهيم الأساسية أمرًا ضروريًا لأي شخص يتطلع إلى فهم طريقة عمل الذكاء الاصطناعي. هذه المبادئ الأساسية تكون بمثابة أساس متين لاستكشاف موضوعات الذكاء الاصطناعي الأكثر تقدمًا. تعلم الآلة: تقنية الذكاء الاصطناعي الأساسية يعد تعلم الآلة تقنية أساسية في مجال الذكاء الاصطناعي، حيث تُمكّن أجهزة الحاسب من التعلم من البيانات وتحسين أدائها بمرور الوقت دون أن تتم برمجتها بشكل صريح. في برمجة الحاسوب التقليدية، يكتب البشر تعليمات واضحة لجهاز الحاسب لأداء مهمة محددة. أما في التعلم الآلي يتم تصميم الخوارزميات بطريقة يمكنها التعرف على الأنماط من خلال تعريضها لكميات هائلة من البيانات، مما يسمح للنظام بالتنبؤ أو اتخاذ القرارات في الحياة الواقعية بناءً على فهمه السابق للبيانات. تتضمن عملية التعلم الآلي ثلاثة مكونات رئيسية: البيانات: يتم جمع مجموعات كبيرة من البيانات المتوافرة عبر الإنترنت بهدف تدريب النموذج عليها. الخوارزميات: تحلل خوارزميات التعلم الآلي البيانات لتحديد الأنماط والعلاقات داخلها. تم تصميم هذه الخوارزميات للتعلم من البيانات وتحسين أدائها بمرور الوقت. النماذج: بمجرد أن تتعلم الخوارزمية من البيانات، فإنها يصبح لدينا نموذج. يمكن استخدام هذا النموذج لإجراء تنبؤات أو قرارات. إن تعلم الآلة متعدد الاستخدامات بشكل لا يصدق ويستخدم في مجموعة واسعة من التطبيقات، مثل معالجة اللغة الطبيعية والتعرف على الصور وأنظمة التوصية والمركبات المستقلة، لذا فهو يلعب دورًا حاسمًا في تمكين أنظمة الذكاء الاصطناعي من التكيف وتحسين أدائها عندما تواجه بيانات جديدة، مما يجعلها التقنية الأساسية في تطوير الآلات الذكية. التطبيقات الشائعة للذكاء الاصطناعي في الحياة اليومية لقد اندمج الذكاء الاصطناعي بسلاسة في حياتنا اليومية والعملية، مما أدى إلى إحداث ثورة في مختلف الجوانب وتعزيز الكفاءة والراحة. لقد أصبحنا اليوم نرى تطبيقات لاحصر لها للذكاء الاصطناعي، وفي هذا القسم نلقي الضوء على بعض تطبيقات الذكاء الاصطناعي الشائعة. المساعدون الافتراضيون Virtual assistants يعمل المساعدون الافتراضيون المدعومين بالذكاء الاصطناعي مثل سيري Siri وأليكسا Alexa ومساعد جوجل Google Assistant على تبسيط المهام والإجابة على الأسئلة والتحكم في الأجهزة الذكية من خلال معالجة اللغة الطبيعية. ميزات الهاتف الذكي يعمل الذكاء الاصطناعي على تحسين الهواتف الذكية من خلال التعرف على الوجه للأمان وتحسين إعدادات الكاميرا للحصول على صور أفضل وتحسين تجربة المستخدم بشكل عام. تخصيص وسائل التواصل الاجتماعي تعمل خوارزميات الذكاء الاصطناعي على تحليل سلوك المستخدم وعرض محتوى مخصص وإعلانات مستهدفة. التسوق المحسّن عبر الإنترنت تستخدم منصات التجارة الإلكترونية الذكاء الاصطناعي لتوصيات المنتجات وتقديم اقتراحات مخصصة بناءً على سجل التصفح وتعزيز تجربة التسوق. خدمة العملاء المعتمدة على الذكاء الاصطناعي توفر روبوتات الدردشة Chatbots المدعومة بالذكاء الاصطناعي استجابات فورية لاستفسارات العملاء وتحسين خدمات الدعم على مواقع الويب والتطبيقات ووسائل التواصل الاجتماعي. الذكاء الاصطناعي في الرعاية الصحية من تحليل الصور الطبية إلى التحليلات التنبؤية، يساعد الذكاء الاصطناعي في تشخيص الأمراض والتنبؤ بالنتائج وتحسين خطط العلاج. أنظمة الملاحة الذكية يقوم الذكاء الاصطناعي بتحليل بيانات حركة المرور في الوقت الفعلي، وتقديم اقتراحات دقيقة للمسار، وأوقات الوصول المقدرة، والمسارات المثالية للسائقين. خدمات الترجمة اللغوية تتيح أدوات الترجمة المعتمدة على الذكاء الاصطناعي مثل Azure AI Translator على ترجمة النصوص والكلام في الوقت الفعلي، وكسر حواجز اللغة وتسهيل التواصل العالمي. الذكاء الاصطناعي في الخدمات المالية يقوم الذكاء الاصطناعي بتحليل البيانات المالية واكتشاف الاحتيال والتنبؤ باتجاهات السوق وتقديم المشورة المالية الشخصية للمستخدمين. أتمتة المنزل الذكي يعمل الذكاء الاصطناعي على تشغيل أجهزة مثل أجهزة تنظيم الحرارة وكاميرات الأمان، مما يسمح للمستخدمين بالتحكم في المنازل وأتمتتها لتحقيق كفاءة الطاقة والأمن. التعليم المخصص توفر المنصات التعليمية التي تعتمد على على الذكاء الاصطناعي تجارب تعليمية مخصصة، وتكيف الدروس بناءً على أداء الطالب الفردي وأساليب التعلم. تسلط هذه التطبيقات الضوء على التأثير المتنوع والواسع النطاق للذكاء الاصطناعي في حياتنا اليومية، مما أحدث ثورة في كيفية تفاعلنا مع التكنولوجيا وتعزيز الجوانب المختلفة لأعمالنا الروتينية. نوصي أيضًا بالاطلاع على مقالة تطبيقات الذكاء الاصطناعي، حيث نلقي نظرة شاملة على التطبيقات المختلفة للذكاء الاصطناعي. الاعتبارات الأخلاقية والتحديات في الذكاء الاصطناعي تحتل الاعتبارات والتحديات الأخلاقية في مجال الذكاء الاصطناعي صدارة المناقشات مع استمرار تقدم هذه التكنولوجيا. إن قدرة الذكاء الاصطناعي على معالجة كميات هائلة من البيانات واتخاذ القرارات تثير أسئلة أخلاقية مهمة حول الخصوصية والتحيز والمساءلة والعديد من القضايا التي نتناولها بإيجاز في هذا القسم. خصوصية البيانات وأمنها وحقوق الملكية الفكرية يعتمد الذكاء الاصطناعي بشكل كبير على البيانات. يعد ضمان خصوصية وأمن بيانات المستخدم أمرًا بالغ الأهمية لمنع سوء الاستخدام والوصول غير المصرح به. كما أن هذه الأنظمة الذكية يمكنها إعادة صياغة الأفكار والمقالات أو تقليد أساليب الكتابة ..إلخ، وهذا ما يمس مبدأ حقوق الملكية الفكرية والنشر. على سبيل المثال، بعض الكتاب بدأو بتحريك دعوات قضائية ضد شركتي "ميتا META" و "أوبن آي إيه OpenAI" بسبب أنظمة الذكاء الاصطناعي التي يطورونها. التحيز والإنصاف يمكن لخوارزميات الذكاء الاصطناعي أن تؤدي عن غير قصد إلى تبني التحيزات الموجودة في بيانات التدريب، مما يؤدي إلى نتائج غير عادلة. يعد التصدي للتحيز وضمان العدالة في أنظمة الذكاء الاصطناعي أمرًا ضروريًا لاتخاذ القرار العادل. في مقالة نشرتها صحيفة إندبندنت independent الأمريكية، بعنوان "يقول إيلون ماسك Elon Musk إنه سينشئ "TruthGPT" لمواجهة "تحيّز" الذكاء الاصطناعي"، ونقلًا عن رجل الأعمال الشهير إيلون ماسك -قالت أن إيلون ماسك يتهم ChatGPT بما اسماه "التحيز ليبرالي". الشفافية والمساءلة غالبًا ما يُنظر إلى عمليات الذكاء الاصطناعي على أنها "صناديق سوداء"، مما يجعل من الصعب فهم كيفية اتخاذ القرارات. يعد ضمان الشفافية في الخوارزميات ومحاسبة المطورين عن سلوك أنظمتهم أمرًا بالغ الأهمية. التأثير على التوظيف إحدى المشكلات الأساسية هي أتمتة المهام الروتينية، حيث يمكن لأنظمة الذكاء الاصطناعي المجهزة بخوارزميات التعلم الآلي، التعامل بكفاءة مع الوظائف المتكررة والعادية التي كان يؤديها البشر في السابق. يشكل هذا التحول تحديًا كبيرًا، وخاصة في صناعات محددة حيث أصبحت الأتمتة سائدة. لقد شهدت صناعات مثل التصنيع والنقل وخدمة العملاء الاستبدال التدريجي للعمال البشريين بآلات ذكية، وهذا ما سيؤدي إلى فقدان محتمل لبعض الوظائف. مثال على ذلك، هو أننا بدأنا نرى استبدال لخدمة العملاء ببرامج الدردشة الآلية. عمومًا، ووسط هذا التحدي، هناك فرصة لاكتساب مهارات جديدة وللتحوّل الوظيفي. على سبيل المثال لا الحصر، هناك حاجة متزايدة لقوى عاملة ماهرة في إدارة وتطوير أنظمة الذكاء الاصطناعي. كما يمكن لمبادرات برامج إعادة بناء المهارات والمسارات التعليمية أن تزوّد العمال بالخبرة اللازمة للتكيف مع سوق العمل المتطور. من الممكن أن تخفف هذه التحوّلات من الآثار السلبية للأتمتة من خلال مواءمة القوى العاملة مع متطلبات الاقتصاد والصناعة المدعومة بالذكاء الاصطناعي، وهذا يضمن قدرة الأفراد على إيجاد سبل جديدة للتوظيف في ظل هذا التقدم التكنولوجي. الاستخدام الأخلاقي في الحرب والمراقبة يتم استخدام تقنيات الذكاء الاصطناعي بشكل متزايد في التطبيقات العسكرية والمراقبة. يعد وضع مبادئ توجيهية أخلاقية لمنع إساءة الاستخدام وإلحاق الضرر بالمدنيين وانتهاكات حقوق الخصوصية مصدر قلق ملح. التعاون بين الإنسان والذكاء الاصطناعي يعد تحقيق التوازن بين أدوار البشر والذكاء الاصطناعي في عمليات صنع القرار أمرًا بالغ الأهمية. وينبغي للأطر الأخلاقية أن تعمل على تعزيز التعاون حيث يعمل الذكاء الاصطناعي على تعزيز القدرات البشرية بدلا من استبدال الحكم البشري بالكامل. العواقب طويلة المدى إن توقع وتخفيف العواقب المحتملة طويلة المدى للذكاء الاصطناعي، مثل تأثيره على المجتمع والبيئة والعلاقات الإنسانية، يتطلب دراسة أخلاقية متأنية لضمان مستقبل إيجابي للبشرية. المعايير واللوائح العالمية إن تطوير المعايير واللوائح الدولية أمر ضروري لضمان تطوير تكنولوجيات الذكاء الاصطناعي ونشرها واستخدامها بشكل أخلاقي عبر الحدود، وتعزيز الابتكار المسؤول وتقليل المخاطر. خاتمة وفي ختام مقالة أساسيات الذكاء الاصطناعي، فإن الخوض في عالم الذكاء الاصطناعي قد سمح لنا بإزالة الغموض عن التكنولوجيا التي تشكل حاضرنا ومستقبلنا. خلال هذا الاستكشاف، تعلمنا المفاهيم الأساسية وراء الذكاء الاصطناعي، وتتبعنا أصوله وفهمنا أنواعه المختلفة. لقد كشفنا عن الأعمال الداخلية لأنظمة الذكاء الاصطناعي، وكشفنا عن مفاهيم التعلم الآلي والخوارزميات التي كانت تعتبر في السابق ألغازًا غامضة. ومن خلال استكشاف تطبيقات الحياة الواقعية، شهدنا كيف يتم دمج الذكاء الاصطناعي بسلاسة في حياتنا اليومية، مما يؤدي إلى إحداث ثورة في الصناعات وتعزيز تجاربنا. ومع ذلك، سلطت هذه الرحلة الضوء أيضًا على الاعتبارات الأخلاقية المحيطة بالذكاء الاصطناعي، وحثّتنا على التفكير في التطوير المسؤول لهذه الأداة. تطرّقنا أيضًا إلى التحديات والقيود التي يواجهها الذكاء الاصطناعي، مع التأكيد على أهمية البحث والابتكار المستمر للتغلب على هذه العقبات. في هذا العصر الذي يتسم بالتقدم التقني السريع، فإن تبني أساسيات الذكاء الاصطناعي يمكّننا من المشاركة في المناقشات واتخاذ قرارات مستنيرة والاستفادة من فوائد هذه التكنولوجيا التحويلية. ومن خلال تعزيز فهم أعمق للذكاء الاصطناعي بين الأفراد الذين ليس لديهم معرفة مسبقة، فإننا نمهد الطريق لمجتمع أكثر شمولاً واستنارة. اقرأ أيضًا تعلم الذكاء الاصطناعي خوارزميات الذكاء الاصطناعي أهمية الذكاء الاصطناعي برمجة الذكاء الاصطناعي مقدمة إلى الذكاء الاصطناعي التوليدي دليل استخدام ChatGPT API لتحسين خدماتك عبر الإنترنت بناء مساعد شخصي من خلال ربط واجهة OpenAI API مع Node.js
  9. لقد انتقل الذكاء الاصطناعي التوليدي Generative AI من مجرد كلمة طنانة في الصناعة إلى واقع ملموس يسود كافة مجالات الحياة ويتقدّم بوتيرة سريعة. يُقدم هذا المقال نظرة عامة على الذكاء الاصطناعي التوليدي الذي يشكل مفهومًا أساسيًا لفهم التقنيات الحديثة وتطبيقاتها، ويستكشف التطور الحاصل للذكاء الاصطناعي وقدراته الحالية والاعتبارات الأخلاقية المرتبطة به. ويُختتم المقال بتقديم رؤى حول مستقبل الذكاء الاصطناعي التوليدي ومناقشة تأثيره المحتمل على حياتنا اليومية. التطوّر التاريخي للذكاء الاصطناعي تساعدك معرفة التطور التاريخي للذكاء الاصطناعي على فهم الذكاء الاصطناعي التوليدي بصورة أفضل ومعرفه دوره ومساهمته في مسيرة تطور علم الذكاء الاصطناعي بشكل عام. تعود أصول الذكاء الاصطناعي Artificial Intelligence أو اختصارًا AI إلى الفلاسفة والرياضيين القدامى الذين سعوا إلى تنفيذ الاستدلال أو المنطق من خلال الآلة. لكن وضع أساسيات ومبادئ الذكاء الاصطناعي الحديث بدأ في القرنين التاسع عشر والعشرين، وتجسد في الجبر البولي لجورج بول George Boole ومفهوم آلات التفكير Thinking machines التي وضعها آلان تورينج Alan Turing. وفي عام 1943، قدم وارن ماكولوخ Warren McCullouch ووالتر بيتس Walter Pitts أول خلية عصبية اصطناعية AI neuron، التي تعد تمثيلًا رياضيًا للخلية الحية. وقد كان هذا بدايةً لمفهوم الشبكات العصبية Neural networks، التي أصبحت اليوم جوهر الذكاء الاصطناعي الحديث. في عام 1950، قدم آلان تورنج ورقة بحثية بعنوان "آلات الحوسبة والذكاء Computing Machinery and Intelligence"، حيث اقترح اختبارًا لذكاء الآلة. وهذا الاختبار لتورنج لا يزال يُستخدم اليوم كوسيلة للتفكير في تقييم أنظمة الذكاء الاصطناعي. وقد ظهر مصطلح الذكاء الاصطناعي لأول مرة في عام 1956 خلال مشروع بحثي في دارتموث حول الذكاء الاصطناعي Dartmouth Summer Research Project، ومن هنا كانت بداية أبحاث الذكاء الاصطناعي، فقد شهدت هذه الفترة الزمنية العديد من الاكتشافات التي أدت إلى انتعاش في مجال الذكاء الاصطناعي في الستينيات، ودعمتها تمويلات من وزارة الدفاع الأمريكية من أجل استثمارها في التطبيقات العسكرية. وتوقعت شخصيات بارزة حينها مثل هربرت سايمون Herbert Simon ومارفن مينسكي Marvin Minsky أن الآلات الذكية ستتمكن من الوصول لمستوى الذكاء البشري في غضون جيل واحد. ومع ذلك، ثبت أن تعقيدات الذكاء الاصطناعي كانت أكبر من المتوقع، مما أدى إلى تقليل التمويل والبحث، مما أدى إلى ما يعرف باسم "شتاء الذكاء الاصطناعي AI winter". لاحقًا شهدت فترة الثمانينات عودة الاهتمام بأبحاث الذكاء الاصطناعي بفضل النجاح التجاري الذي حققته الأنظمة الخبيرة Expert Systems أحد أشهر تطبيقات الذكاء الاصطناعي والتي تعد عبارة عن أنظمة مبنية على قواعد تُقلد الاستدلال البشري. ووجدت هذه الأنظمة النور في قطاعات متنوعة، بما في ذلك الرعاية الصحية والتمويل. ومع ذلك كانت هذه النهضة مؤقتة، حيث بدأ "شتاء الذكاء الاصطناعي" الآخر عام 1987. خلال التسعينيات والعقد الأول من القرن الحادي والعشرين، أصبحت تقنيات تعلم الآلة Machine Learning سائدة في مجال الذكاء الاصطناعي. فقد أتيحت كمية كبيرة من البيانات الضخمة وشكلت شُعلةً لبداية ازدهار التعلم الآلي. فبخلاف الأنظمة التقليدية المبنية على قواعد محددة لتعمل، تُميّز خوارزميات التعلم الآلي الأنماط التي تتضمّنها البيانات من تلقاء نفسها، وهذا ما أدى إلى تطوير مجموعة من التطبيقات مثل مُرشحات البريد الإلكتروني المزعج Email spam filters وأنظمة التوصية المستخدمة في مواقع مشهورة مثل Netflix وأنظمة التنبؤ المالي وتوقع أسواق الأسهم. وقد حوّلت تقنية التعلم الآلي تركيز الذكاء الاصطناعي من الأنظمة المبنية على القواعد إلى الأنظمة المبنية على البيانات. مع حلول عام 2012 طرأ تحول كبير على الذكاء الاصطناعي فقد تحسنت قدرات الحواسيب وتطورت المعالجات CPU ووحدات معالجة الرسومات GPUs من جهة، كما توفرت البيانات وتطورت خوارزميات الشبكات العصبية، وهذه العوامل مجتمعة أدت إلى ظهور التعلم العميق Deep Learning كفرع من التعلم الآلي. وقد تفوق التعلم العميق بسرعة على تقنيات التعلم الآلي الأخرى، مما أدى إلى نهضة في أبحاث الذكاء الاصطناعي وتطبيقاته وزيادة في التمويل الممنوح له فمع حلول عام 2022، وقد بلغت الاستثمارات العالمية في الذكاء الاصطناعي حوالي 91 مليار دولار، وزاد الطلب على مختصي الذكاء الاصطناعي في سوق العمل. وقد بلغت تطبيقات الذكاء الاصطناعي المعتمدة على التعلم الآلي ذروتها في الوقت الحالي وطبقت في مختلف المهام بدءًا من المهام البسيطة كترشيح الرسائل المزعجة إلى المهام المعقدة كالمركبات ذاتية القيادة وأدوات التشخيص الطبي، كما ظهر تخصص فرعي للتعلم الآلي عرف باسم الذكاء الاصطناعي التوليدي Generative AI وأثبت كفاءته في العديد من التطبيقات وأبرزها كتابة المحتوى النصي وتوليد الصور ومقاطع الفيديو والأصوات، وفي فقراتنا التالية سنشرح المزيد حول هذا الفرع الحديث وآلية عمله. ما هو الذكاء الاصطناعي التوليدي؟ يستخدم المُهندسون أدوات وتقنيات متنوعة لتدريب خوارزميات التعلّم الآلة والذكاء الاصطناعي على البيانات للحصول على نماذج Models يمكنها إجراء توقعات أو تصنيفات. على سبيل المثال، يمكن لنموذج مُدرّب على مجموعة بيانات تحتوي على صور للقطط والكلاب التفريق بينهما بناءً على أنماط تعلمتها الخوارزمية خلال التدريب. تَخدم نماذج التعلم الآلي تطبيقات متنوعة بما في ذلك أنظمة الأمان التي تراقب تسجيلات الفيديو التي تسجلها الكاميرات وتكتشف وجود محاولات اقتحام محتملة من خلال تحليل بيانات الفيديو، كما يُمكن للمساعدين الصوتيين مثل سيري Siri وأليكسا Alexa معالجة الكلام والرد على استفسارات المستخدمين، وتعمل المركبات ذاتية القيادة على تتبع واكتشاف الكائنات واتخاذ القرارات أثناء القيادة، وفي قطاع الرعاية الصحية تُستخدم نماذج الذكاء الاصطناعي لاكتشاف التشوهات في الصور الطبية، بالإضافة إلى العديد من الاستخدامات الأخرى. دعونا نُطلق على هذا المجال اسم "الذكاء الاصطناعي التقليدي" أو "تعلم الآلة التقليدي" وذلك نظرًا لاستخدامه الشائع فهذا النوع من الذكاء الاصطناعي يمكنه تصنيف أو توقّع المحتوى، حيث يأخذ دخلًا وينتج خرجًا. مثلًا يمكنه تحديد ما إذا كانت الصورة (دخل) تحتوي على قطة أو كلب (أي قرار يُمثل الخرج) أو تحديد أفضل مسار إلى الوجهة (كما في تطبيقات الخرائط) أو تقدير احتمالية وجود ورم في صورة الأشعة السينية. أما الذكاء الاصطناعي التوليدي -والذي يُعتبر فرع من فروع تعلّم الآلة- فهو يستخدم الشبكات العصبية لإنشاء محتوى جديد كليًا. إذ تُدرّب هذه النماذج على مجموعات بيانات كبيرة مثل الصور ومقاطع الفيديو والصوت أو النصوص، وتكتشف الأنماط التي تُشكّل هذه الأشياء وتتعلّم منها، بالتالي يمكن لهذه النماذج بعد التدريب إنشاء محتوى جديد استنادًا إلى ما تعلمته خلال مرحلة التدريب. وتفيد نماذج الذكاء الاصطناعي التوليدي في مجالات عديدة مثل نماذج إنشاء الصور مثل DALL-E التابعة لشركة OpenAI والذي تعتمد على مجموعات ضخمة من الصور لتوليد صور جديدة، ونماذج إنشاء النصوص مثل ChatGPT التابعة لنفس الشركة الذي يولد نصوصًا جديدة بالاعتماد على مجموعات هائلة من النصوص التي دُرّب عليها. يمكن للذكاء الاصطناعي التوليدي صياغة مجموعة واسعة من أنواع المحتوى، حيث يمكن لنماذج توليد الصور إنشاء صور متنوعة أو حتى إنشاء لوحات فنية تُحاكي لوحات فنانين أو حركات فنية محددة، وبالمثل يمكن لنماذج النصوص تقليد كُتّاب معينين أو أنواع معينة من الكتابة، وهذا ما ينتج عنه نصوص فنيّة وإبداعيّة أو حتى إنشاء شيفرات برمجية في لغات برمجة متنوعة كما يفعل نموذج CodeLlama. قد يبدو الاطلاع على نماذج الذكاء الاصطناعي التوليدي لأول مرة أمرًا ساحرًا، إذ يبدو وكأنها تُخرج المحتوى المطلوب من اللاشيء. يمكن أن تطلب من نموذج توليد النصوص كتابة قصيدة أو قصة، أو من نموذج توليد الصور إنشاء لوحة فنية أو صورة، تجربة خيالية أليس كذلك؟ خذ الأمثلة التالية: أُنشئِت الصور التالية باستخدام DALL-E، وهو نموذج لتوليد الصور. طُلب من النموذج "إنشاء صورة لمدينة مزدحمة من عصر النهضة تسكنها حيوانات مجسمة". لاحظ التفاصيل المعقدة مثل الهندسة المعمارية المزخرفة والأنواع المختلفة من الحيوانات وفي أقل من دقيقة، أنتج النموذج صورتين فريدتين، لكل منهما أسلوب مميز. إن نماذج إنشاء النص لا تقل دهشة عن نماذج إنشاء الصور فقد أُنشئ النص التالي بواسطة بوت المحادثة ChatGPT، وهو نموذج لتوليد النص. طُلب من النموذج "إنشاء فقرة واحدة عن قطة منزلية تعيش في عالم ما بعد نهاية العالم" وهذا ما أنتجته: كيف تعمل نماذج الذكاء الاصطناعي التوليدي؟ ظن البشر لفترة طويلة أنهم هم وحدهم مصدر الإبداع الفني وأن كتابة القصص أو تأليف الموسيقى أو غيرها من الأعمال الإبداعية سمات إنسانيّة فريدة يصعب تكرارها أو محاكاتها. لكن أظهر الذكاء الاصطناعي التوليدي في يومنا هذا خلاف ذلك فقد تمكنت هذه النماذج من إنشاء محتوى لا يُمكن تمييزه عن المحتوى في كثير من الأحيان عن المحتوى الذي ينتجه الإنسان، وهذا يطرح سؤالًا: كيف تعمل هذه النماذج؟ إليك شرحًا مفصلًا يوضح لك آلية عملها: تُدرّب نماذج الذكاء الاصطناعي التوليدي على مجموعات بيانات كبيرة وتتعلم منها الأنماط والعلاقات بين البيانات، مثلًا يتعلم نموذج توليد النصوص العلاقات السياقية والدلالية للكلمات، بينما يتعلم نموذج توليد الصور العلاقات البصرية بين البكسلات وبعدها تستخدم النماذج هذه الأنماط التي تعلمتها لإنشاء محتوى جديد. إليك تشبيهًا مبسطًا يوضح كيف ينتقل نموذج توليد النصوص من المرحلة التدريبية إلى مرحلة الإنتاج. تخيل متدربًا يتعلم الطهي عن طريق دراسة وصفات الطهي، ولذلك يُطلب منه دراسة كتاب طهي يحتوي على مجموعة متنوعة من الوصفات التي تتدرّج في المستوى من البسيطة إلى الصعبة. يتعلّم المُتدرّب أثناء دراسته للوصفات العلاقات بين المكونات وتعليمات الطهي وكلما درس المزيد من الوصفات زادت الأنماط التي تعلّمها. وهنا يبدأ في بناء نموذج عقلي لعملية الطهي. لقد لاحظ أن ذكر "الشوكولاتة" و"السكر" غالبًا ما يتبعه عملية الخبز. ولاحظ أيضًا أن مصطلحات مثل "الغليان" غالبًا ما يتبعها مكونات مثل "الماء" أو "المعكرونة". كما يساعده معلمه أيضًا على التعلّم من خلال مطالبته بتوقّع ما سيأتي بعد ذلك في الوصفة ويتحقق المُعلّم من توقعاته ويعاقبه على الخطأ في الوصفات ويكافئه على جودتها. تعمل هذه العملية التكرارية للتوقّع والتصحيح ولعدد كبير جدًا من الوصفات، على تحسين فهمه وزيادة دقة تنبؤاته. وأخيرًا وبعد كل هذا التدريب يطرح المُعلّم تحدّيًا أخيرًا: "اصنع وصفة جديدة لكعكة الشوكولاتة". يستلهم المُتدرب من جميع الوصفات التي درسها، ومن فهمه الدقيق لعملية الطهي، لإنشاء وصفة جديدة. قد تكون الوصفة الناتجة حديثًا مستوحاة من الوصفات السابقة، ولكنها تمثل إبداعًا فريدًا من نوعه. تُدرّب نماذج الذكاء الاصطناعي التوليدية بطريقة مماثلة، وتُمنح إمكانية الوصول إلى مجموعات بيانات كبيرة مثل الصور أو مقاطع الفيديو أو الصوت أو النص، وتتعلّم الأنماط والعلاقات بين البيانات وتستخدم هذه المعرفة لإنشاء محتوى جديد. هذا بالطبع شرح مبسط بمفردات مفهومة ليساعدك على فهم كيفية عمل نماذج الذكاء الاصطناعي التوليدية، لكن العملية الفعلية أكثر تعقيدًا بالطبع وتتضمن حسابات وخوارزميات رياضيّة معقدة. ومع ذلك، يظل المبدأ الأساسي كما هو: تتعلم هذه النماذج أنماطًا من البيانات، وتستخدم هذه المعرفة لإنشاء محتوى جديد. دورة الذكاء الاصطناعي احترف برمجة الذكاء الاصطناعي AI وتحليل البيانات وتعلم كافة المعلومات التي تحتاجها لبناء نماذج ذكاء اصطناعي متخصصة. اشترك الآن المفاهيم الخاطئة حول الذكاء الاصطناعي التوليدي تظهر قدرات النماذج التوليديّة للذكاء الاصطناعي بوضوح في المحتوى الذي تنتجه، وهذا ما يؤدي في كثير من الأحيان إلى تصوّرات خاطئة حول تصميمها وقدراتها. دعونا نتناول بعض هذه الافتراضات الخاطئة. هل أصبحت نماذج الذكاء الاصطناعي التوليدية مُدركة أو واعية؟ بالطبع لا. هذه النماذج لا تُفكر ولا تشعُر وتفتقر إلى فهم للمحتوى الذي تُنتجه أو البيئة المحيط بها. إنها ليست مُدركة لذاتها أو لبيئتها المحيطة فهي تشبه آلة إنتاج الخبز ولاتتضمّن أي ترتيب هندسي أو معماري مُشابه للوعي الذاتي. وبالرغم من أن هذه النماذج تُنتج محتوى يبدو ناتجًا عن إدراك للذات إذا توفّرت له الإرشادات والمعايير الصحيحة، ولكن هذا مجرد انعكاس للبيانات التي تدرّبت عليها -بعبارة أخرى، إنها تحاكي ما تتعلمه لكنها لا تعيه ولا تدركه. هل نماذج الذكاء الاصطناعي التوليديّة خالية من التحيّز؟ لا قطعًا. تُدرّب هذه النماذج على كميات هائلة من البيانات التي أنشأها البشر. يأتي الكثير من هذه البيانات من الإنترنت، وكما نعلم فشبكة الإنترنت تتضمّن الكثير المعلومات الخاطئة والسلبية. وبما أن هذه النماذج تُدرّب على هذه البيانات، فإنها بلاشك تعكس محتواها سواءًا أكان إيجابيًّا أم سلبيًّا. وبالتالي فإن الاعتماد الكامل على هذه النماذج يشبه الثقة بالأشخاص المجهولين على الإنترنت. قد تكون المعلومات التي يقدمونها صحيحة، ولكن من الحكمة أن تكون متحفظًا على ما تستقيه منهم من معلومات. ملاحظة: يشير مصطلح التحيّز Bias في سياق الحديث عن المعلومات التي تولّدها أو تنتجها نماذج الذكاء الاصطناعي إلى فكرة ميل إجاباتها لأفكار أو منطق أو اتجاهات معينة. مثلًا، قد تُنتج النماذج المُدرّبة على نصوص تتضمّن تحيزات عنصرية أو عرقية (مثل التمييز بين البيض والسود)، إجابات عنصرية. هل نماذج الذكاء الاصطناعي التوليديّة دقيقة؟ الإجابة هي نعم ولا، ففي بعض الحالات قد تكون إجاباتها دقيقة ومحكمة، وفي بعض الأحيان قد تكون إجاباتها مُضللة وخاطئة. في الواقع هذه النماذج مُصابة بما يُسميّه الباحثون "الهلوسة"، أي أن هذه النماذج من الممكن أن تُعطيك إجابات من وحي الخيال وربما خاطئة تمامًا، لكن تُقدّمها بطريقة مُقنعة للغاية. هذا درس تعلمه بعض الأشخاص بطريقة صعبة. وبالنتيجة يجب عليك دائمًا أن تتحقق من المعلومات التي تنتجها نماذج الذكاء الاططناعي التوليدية من مصادر موثوقة. هل سيحل الذكاء الاصطناعي مكان عملي؟ في الواقع الجواب قد يكون نعم للأسف! فهذا يعتمد على مهنتك ودورك في العمل. فكما فقد الأشخاص الذين كانوا يعملون في إضاءة الشوارع وإشعال الفوانيس عند الغسق وإطفائها عند الفجر في القرن السادس عشر عملهم عند اختراع الكهرباء والمصباح الكهربائي، وكما ألغت السيارات وجود العربات التي تجرُّها الخيول، قد تلغي نماذج الذكاء الاصطناعي بعد فترة وجيزة كثيرًا من المهن الحالية لكنها بكل تأكيد ستحمل معها وظائف جديدة في ذات الوقت. قد يُقلل الذكاء الاصطناعي من حجم العمالة في بعض المهام، مما يسمح لعدد أقل من الأشخاص بإنجاز المزيد. إذًا ومما لا شك فيه سينتج الذكاء الصناعي أيضًا وظائف جديدة تتطلب مهارات جديدة، مثل مجال هندسة النصوص المُدخَلة Prompt engineering الناشئ. فقد أظهر التاريخ دائمًا أن التغيير هو الثابت الوحيد والتكيّف مع هذا التغيير المستمر هو المفتاح لمواصلة النجاح. لقد تناول هذا القسم مجرد معلومات سطحيّة فيما يتعلق بالاعتقادات الخاطئة السائدة حول الذكاء الاصطناعي التوليديّ، وسنناقش في الفقرات التالية أبرز المخاوف والتحديات المرتبطة بهذا العلم الحديث. ما هي المخاوف الأخلاقية المرتبطة بالذكاء الاصطناعي التوليدي؟ تُضخّم تقنية الذكاء الاصطناعي التوليدي لإنتاج المحتوى المعضلات الأخلاقية الحالية وتُدخل أخرى جديدة. دعونا نستكشف بعض هذه المخاوف بشكل أكبر. الأمن السيبراني يمكن استخدام نماذج الذكاء الاصطناعي التوليدي لتجاوز الاختبارات الآلية CAPTCHA وتدابير الأمان الأخرى، مما قد يؤدي في نهاية المطاف إلى زيادة في هجمات الأمن السيبراني. كما يمكن أيضًا استخدامها لإنشاء مابُسمّى بالتزييف العميق deepfakes، وهي وسائط اصطناعية (صور، فيديوهات، أصوات) تظهر وكأنها حقيقية وغير مُزيّفة، وذلك باستخدام تقنيات التعلم العميّق. قد يؤدي ذلك إلى نشر المعلومات الخاطئة وقد تُستخدم قدرة هذه النماذج على إنشاء شيفرات برمجية في مجموعة واسعة من لغات البرمجة لأداء عمليات الاختراق تلقائيًا بمساعدة الذكاء الاصطناعي. التحيّز والتمييّز ستؤدي نماذج الذكاء الاصطناعي التوليدي إلى تبني التحيزات الاجتماعية الموجودة في بيانات التدريب، مما يؤدي إلى إنتاج نتائج غير عادلة أو تمييزية، بالتالي سيؤدي إلى حلقة راجعة إن لم يُعالج، حيث تُستخدم النتائج المُتحيّزة كبيانات تدريب للنماذج المستقبلية مما يُعزز التحيّز أكثر. وبالتالي فإن استخدام الذكاء الاصطناعي التوليدي في اتخاذ قرارات تؤثر في حياة الناس، مثل السياسات العامة والتوظيف أو العدالة الجنائية، يمكن أن يؤدي إلى نتائج غير عادلة أو تمييّزية. المعلومات الخاطئة والأخبار الزائفة يمكن استخدام نماذج الذكاء الاصطناعي التوليدي لإنشاء أخبار زائفة أو دعاية أو أشكال أخرى من المعلومات على نطاق غير مسبوق. يمكن استخدام ذلك للتأثير على الرأي العام أو لتوجيه الانتخابات أو حتى لحث العنف. تتزايد هذه المخاوف عند الجمع بينها وبين التزييف العميق الذي تحدثنا عنه منذ قليل. الخصوصية يمكن تدريب النماذج المُقدمة من البائع على أي من البيانات العامة أو الخاصة أو كليهما، وقد يؤدي استخدام البيانات الخاصة في تدريب النماذج -مثل السجلات الطبية- إلى كشف هذه النماذج عن غير قصد عن معلومات حسّاسة. من ناحية أخرى يمكن استخدام البيانات العامة -مثل منشورات وسائل التواصل الاجتماعي- لاستنتاج معلومات خاصة عن الأفراد، وبالتالي انتهاك الخصوصية أو حتى الابتزاز. الملكية الفكرية يتجاوز الذكاء الاصطناعي التوليدي النظام القانوني ولايتقيّد به، وهذا ما يُثير تساؤلات حول الملكية الفكرية وحقوق المحتوى الذي يُنتجه الذكاء الاصطناعي. يُشكل ذلك قلقًا خاصًا بالنسبة لمُنشئي المحتوى الإبداعي، مثل الفنانين، والموسيقيين، والكتّاب الذين يعتمدون على إبداعاتهم الفريدة. فقدان الطابع الإنساني يُستخدم الذكاء الاصطناعي التوليدي في وقتنا الحالي بكثرة، ويستمر في استبدال بعض الوظائف التي تتضمّن تفاعلات بشرية. تحل روبوتات الدردشة الآلية حاليًا محل مُمثلي خدمة العملاء، ومن المرجح أن تستمر هذه الاتجاهات في مجالات أخرى. يمكن أن يؤدي ذلك إلى فقدان الطابع الإنساني في التفاعلات البشرية، مما قد يؤدي إلى تقليل التعاطف والشفقة. السلامة والموثوقية يثير استخدام الذكاء الاصطناعي التوليدي في التطبيقات الحيوية، مثل المركبات ذاتية القيادة وتشخيص الأمراض الطبية والتطبيقات العسكرية مخاوف حول السلامة والموثوقية. إذ يمكن أن تؤدي النتائج غير المتوقعة إلى وقوع حوادث أو إصابات أو حتى خسائر في الأرواح. لهذا يتطلب استخدام الذكاء الاصطناعي التوليدي في التطبيقات المهمة دراسة متأنية واختبارات مكثّفة لضمان سلامة مستخدميه. الشفافيّة والمساءلة غالبًا ما تكون نماذج الذكاء الاصطناعي صناديق سوداء، مما يصعب فهم كيفية عملها أو لماذا تتخذ قرارات معينة. كما تنتج هذه النماذج نتائج مختلفة لنفس المُدخلات، مما يجعل من الصعب توقّع سلوكها. وهذا يطرح تساؤلًا مهمًا من المسؤول عندما يرتكب نموذج الذكاء الاصطناعي التوليدي خطأً ما؟ وكيف يمكننا ضمان استخدام هذه النماذج بطريقة مسؤولة؟ هذه أسئلة تحتاج إلى دراسة وبحث متأنيين. هذا القسم يُلامس فقط بعض الأمور السطحية المتعلّقة بالمخاوف الأخلاقية المحيطة بالذكاء الاصطناعي التوليدي. وإدراك هذه المخاوف أمر بالغ الأهمية لفهم تأثير التقنية على حياتنا. حالات استخدام الذكاء الاصطناعي التوليدي لنستكشف بعض الصناعات وحالات الاستخدام المختلفة حيث يُطبّق الذكاء الاصطناعي التوليدي أو لديه الإمكانية للتطبيق. القطاع التقني أصبح الذكاء الاصطناعي التوليدي إضافة لا غنى عنها لسير العمل في تطوير البرمجيات. يُستخدم في إنشاء وشرح الشيفرات البرمجية والاختبارات التلقائية وتوثيقات الشيفرات البرمجية وتحديث الأنظمة القديمة. كما يُستخدم أيضًا في مجموعة من تطبيقات الأمان السيبراني مثل القرصنة الآلية واكتشاف البرامج الضارة واكتشاف التسلل واكتشاف الثغرات الأمنية. القطاع المالي يكتسب الذكاء الاصطناعي التوليدي قوته من كميات كبيرة من البيانات، مما يجعل القطاع المالي الغني بالبيانات مكانًا طبيعيًا لتطبيق الذكاء الاصطناعي التوليدي. يمكن استخدامه لأتمتة التحليل المالي وتقليل المخاطر وتحسين العمليات. يمكن أيضًا استخدامه لإنشاء محتوى مثل الملخصات وتحويل النص إلى رسوم بيانية. التجارة الإلكترونية يُستخدم الذكاء الاصطناعي التوليدي لتحسين مشاركة العملاء وكفاءة العمليات في مجال التجارة الإلكترونية. حيث يمكنه توفير معلومات فورية وحديثة حول سلوك العملاء وتفضيلاتهم، ثم استخدام هذه المعلومات لتعزيز وتحسين تجربة التسوّق الشاملة للعملاء. كذلك نستطيع استخدام تقنيات الذكاء الاصطناعي التوليدي لإنشاء روبوتات الدردشة والوكلاء الافتراضيين الذين يعملون كمُمثلين لخدمة العملاء يعملون على مدار الساعة طوال أيام الأسبوع. يمكن للذكاء الصناعي أيضًا إنتاج وتحديث أوصاف المنتج والمحتوى التسويقي. الرعاية الصحية يُطبق الذكاء الاصطناعي التوليدي على مجموعة واسعة من حالات استخدام الرعاية الصحية، بما في ذلك التصوير الطبي واكتشاف الأدوية ورعاية المرضى والتنبؤ بتطور الأمراض. يُستخدم لتحليل الصور الطبية واكتشاف الشذوذ وتوقّع تقدم الأمراض. يُستخدم أيضًا لاكتشاف أدوية وعلاجات جديدة وتحسين رعاية المرضى. التعليم يُستخدم الذكاء الاصطناعي التوليدي لأتمتة عمليات اكتشاف الانتحال وإنشاء مشاكل للتدرّب على حلّها وتقديم تغذية راجعة أو تعليقات لهم. يمكن أن يُستخدم أيضًا لإنشاء تجارب تعليميّة مُخصصة مثل المعلمين الافتراضيين وإنشاء محتوى تعليمي. هذا يؤدي إلى إمكانية حدوث تحوّل كبير في الطريقة التي يتعلم بها الطلاب ويتفاعلون مع المحتوى. صناعة السيارات يُستخدم الذكاء الاصطناعي التوليدي لتحسين تصميم المركبات وعمليات الهندسة والتصنيع وتطوير المركبات ذاتية القيادة. تستفيد شركات مثل تويوتا ومرسيدس وبي إم دبليو من الذكاء الاصطناعي لتسريع سير العمل وتحسين الإنتاجية ودفع الابتكار. الترفيه ووسائل الإعلام يمكن للذكاء الاصطناعي التوليدي أن يُحدث تحوّلًا جذريًا في صناعة الترفيه إذ يمكن استخدامه في إنشاء المحتوى النصي والموسيقى والأفلام وألعاب الفيديو. يمكن أن يعزز أشكال الترفيه الحالية ويخلق أشكالًا جديدة. إلا أنّه يثير أيضًا مخاوف أخلاقية كبيرة، مثل سوء الاستخدام وقضايا حقوق الملكية الفكرية. المجال القانوني وجد الذكاء الاصطناعي التوليدي مجموعة من التطبيقات في القطاع القانوني كمراجعة العقود والبحث القانوني. يُمكن أن يُمكّن المحترفين القانونيين من التركيّز على المهام عالية المستوى، من خلال أتمتة المهام المستهلكة للوقت والمتكررة. بالرغم من أن الذكاء الاصطناعي التوليدي يوفّر الوقت والجهد إلا أن إمكانيته لإنتاج معلومات مزيفة تتطلب اعتبارًا ورصدًا دقيقين. التخطيط العمراني للذكاء الاصطناعي التوليدي مجموعة واسعة من حالات الاستخدام في التخطيط الحضري، بما في ذلك تحسين تدفق حركة المرور وتحسين الاستعداد للكوارث وتحسين النمو المستدام وتعزيز إمكانية الوصول والسلامة في الأماكن الحضرية. واليوم تقدم شركات مثل Digital Blue Foam أدوات تعتمد على الذكاء الاصطناعي للتخطيط الحضري. الزراعة يُظهر الذكاء الاصطناعي التوليدي الكثير من الأمل في العمل الزراعي وقطاع الزراعة. يمكن استخدامه لتحسين إنتاجية المحاصيل وتقليل استخدام المبيدات الحشرية ومنع الخسائر في المحاصيل وحتى تصميم البروتينات النباتية. هذا يمكن أن يساعد في ضمان الأمن الغذائي والحد من الأثر البيئي. العلوم البيئية يُستخدم الذكاء الاصطناعي التوليدي في العلوم البيئية كنمذجة تغيّر المناخ ومكافحة التلوّث. كما يُستخدم أيضًا لتحليل البيانات البيئية والتنبؤ بالتغيرات البيئية والمساهمة بالمعلومات التي يمكن استخدامها في صياغة وتطوير السياسات البيئية. من جانب آخر هناك جانب مظلم يُسقطه الذكاء الاصطناعي التوليدي خاصة عند تدريب النماذج الكبيرة، مثل النفايات الإلكترونية واستهلاك طاقة والموارد (إذا لم تكن هذه الطاقة مأخوذة من مصادر مستدامة، قد يكون لها تأثير سلبي على البيئة) ويعتبر ذلك مصدر قلق متزايد يجب معالجته من أجل ضمان أن يكون الذكاء الاصطناعي جزءًا من مستقبل مستدام. كيف يبدو مستقبل الذكاء الاصطناعي التوليدي؟ رغم أن مستقبل التقنية المتعلق بتطوّر الذكاء الاصطناعي التوليدي غير واضح تمامًا حتى اليوم، إلا أن تقدمه الملحوظ والمستمر بما يُقدّمه من نماذج وتطبيقات جديدة يشير إلى أهمية هذا المجال وديمومته. إذ يُظهر الواقع الراهن في هذا المجال أنه يمكننا استنتاج بعض الأفكار حول اتجاهات الذكاء الاصطناعي التوليدي في المستقبل. لذلك يمكننا أن نتأمل ونبني توقعاتنا حول المسار المستقبلي المحتمل للذكاء الاصطناعي التوليدي، ونتوقع أنه سيشهد المزيد من التحسينات على النتائج التي سيولدها، فقد تمكنت نماذج النصوص والصور والصوت والفيديو بالفعل من إنتاج محتوى يشبه إلى حد كبير ما يُنتجه الإبداع البشري ويتوقع أن تزداد جودة ودقة المحتوى في المستقبل القريب. لكن هناك بعض المعوقات التي تقف في وجه هذا التطوّر، مثل التكاليف المرتفعة والمتطلبات العتاديّة التي تحد من توفر هذه النماذج وتطورها. طبعًا يمكننا استخدام نماذج أصغر وأقل قوة يمكنها أن تعمل على أجهزة محمولة معينة إلا أن ذلك يؤثر على أداءها وقوتها وتبنيها، لذا يمكننا أن نتوقع وجود توجهات تعالج هذه القضايا كتصميم الأجهزة والنماذج بطريقة تجعل الوصول أكثر انتشارًا وأوسع نطاقًا. من ناحية أخرى يُرجّح أن يصبح الذكاء الاصطناعي التوليدي أكثر تفاعلية، وذلك من خلال استجابة النماذج لتعليقات المستخدمين والتكيّف مع تفضيلاتهم وحتى التعلّم من تفاعلاتهم معها. يتيح ذلك تجارب أكثر تخصيصًا ونماذج تلبي التفضيلات الشخصيّة كما يساهم في تغيير الطريقة التي نتفاعل بها مع البرامج والخدمات، حيث تصبح التفاعلات اللغوية هي القاعدة. تُنشأ حاليًا أنواع مختلفة من المحتوى (كالفيديو والنص) وكل منها تُنتج بسرعات متفاوتة، حيث يظهر النص كأحد أسرع الأشكال، بينما يتطلب الفيديو وقتًا أطول. وهذا يُشير إلى احتمالية ظهور تطبيقات جديدة وفريدة لاسيما عندما تكون نماذج الذكاء الاصطناعي التوليدي قادرة على إنشاء محتوى في الزمن الحقيقي. يُعتبر دمج الذكاء الاصطناعي التوليدي من ناحية أخرى في محركات ألعاب الفيديو فكرة رائعة لتعزيز الانغماس والتفاعل مع الألعاب. يمكن لبيئات اللعبة أن تولّد عوالمًا كاملةً ديناميكيًا مع إمكانيّة تعديلها بناءً على سلوك اللاعب، كما يمكن لشخصيات الألعاب الافتراضية التي تشبه الأفراد الحقيقيين لحدٍ ما، أن تُقدم للاعبين تفاعلات فريدة من نوعها وهذا الخروج عن الأساليب التقليديّة يمكن أن يحوّل صناعة الألعاب ويجعلها أكثر تميّزًا. ومن ناحية التعليّم والتعلّم، قد تظهر أساليب جديدة ومبتكرة تتضمن تفاعل الطلاب مع معلمين رقمييّن يلبون أساليب التعلّم المُخصصة والقابلة للتكيّف مع حاجات الطلاب. هناك إمكانية للطلاب لإجراء محادثات مع تجسيدات افتراضية لشخصيات تاريخية، كما ستتاح إمكانية توفير المعلومات بتنسيقات متنوعة، مثل النص أو الصوت أو الفيديو، وذلك اعتمادًا على التفضيلات الشخصية وبهذا يمكن للطالب اختيار طريقة التعلم التي تناسبه. ستوفر التطورات في الواقع المعزز Augmented reality والواقع الافتراضي Virtual reality طرقًا جديدة للذكاء الاصطناعي التوليدي، حيث من المرجّح أن يصبح كل من الواقع المعزز والواقع الافتراضي أكثر واقعية وانتشارًا، وذلك بفضل قدرات نماذج الذكاء الاصطناعي التوليدي على إنشاء محتوى في الزمن الحقيقي ويؤدي إلى توفير أشكال جديدة من الترفيه والفنون، مثل الأفلام التفاعلية. وفي الختام يمكننا القول أن جودة بيانات التدريب قد تكون العامل المُميّز بين النماذج. قد يتحوّل دور مُنشئي المحتوى التقليدي نحو إنشاء بيانات التدريب وتنظيمها والحفاظ عليها فالحفاظ على بيانات التدريب حديثة وذات صلة بالمجال المُحدّد يمكن أن يضمن بقاء المحتوى المُنشأ ديناميكيًا ودقيقًا وحديثًا. ومن المرجح أن تصبح بيانات التدريب عالية الجودة هي الميزة التنافسية بين نماذج الذكاء الاصطناعي التوليدي المختلفة. خاتمة تعرفت في مقال اليوم على الذكاء الاصطناعي التوليدي أحد أكثر مجالات الذكاء الاصطناعي السائدة في يومنا هذا والآخذة بالتطور المستمر، والتي بدأت تغير الطريقة التي نتفاعل بها مع البرامج والخدمات. بالطبع لا تزال هذه التقنية في مهدها ولازال هناك العديد من الأسئلة والمخاوف الأخلاقية المرتبطة بها والتي لم توضع لها أجوبة نهائية، لكن هناك شيء واحد يمكننا القول أنه مؤكد وهو أن الذكاء الاصطناعي موجود ليبقى ويُرجّح أن يصبح أكثر انتشارًا في حياتنا اليومية ومن الواجب علينا أن نعي تأثيرات صعود الذكاء الاصطناعي التوليدي ونفهم هذه التقنية وقدراتها ونستغلها بطريقة نافعة. ترجمة -وبتصرُّف- للمقال Introduction to Generative AI لصاحبه Ben Lambert. اقرأ أيضًا أنواع الذكاء الاصطناعي دليل استخدام ChatGPT API لتحسين خدماتك عبر الإنترنت دليلك لربط واجهة OpenAI API مع Node.js برمجة الذكاء الاصطناعي: بناء مستقبل الآلات الذكية
  10. إذا كنا بحاجة إلى عرض البيانات بتنسيقات منظمة مثل التقارير النصية أو صفحات HTML، فإن قوالب لغة جو توفر حلًا فعالًا. تتضمن مكتبة لغة جو القياسية حزمتين تسمحان لأي برنامج مكتوب في هذه اللغة بتقديم البيانات بطريقة منسقة بدقة، وهما text/template و html/template. يمكننا باستخدام هذه الحزم إنشاء قوالب نصية وتمرير البيانات فيها لتصيير render مستندات مصممة خصيصًا لمتطلباتنا. توفر القوالب المرونة في التكرار على البيانات باستخدام الحلقات وتطبيق المنطق الشرطي لتحديد محتوى ومظهر كل عنصر. نستكشف في هذا المقال كيفية استخدام كلتا حزم القوالب. نستخدم في البداية حزمة text/template لإنشاء تقرير نصي عادي من خلال الاستفادة من الحلقات والعبارات الشرطية والدوال المخصصة. نستخدم بعد ذلك html/template لتصيير مستند HTML مع ضمان الحماية ضد الثغرات الأمنية في إدخال التعليمات البرمجية. المتطلبات الأولية 1لمتابعة هذا المقال التعليمي، سنحتاج إلى: إصدار مُثبّت من جو 1.16 أو أعلى، ويمكنك الاستعانة بمقال تثبيت لغة جو Go وإعداد بيئة برمجة محلية على أبونتو Ubuntu لإعداده. تثبيت لغة جو وإعداد بيئة برمجة محلية على نظام ماك macOS. تثبيت لغة جو وإعداد بيئة برمجة محلية على ويندوز. إنشاء البنى Structs في لغة جو وتعريف التوابع في لغة جو. الخطوة 1- استيراد حزمة text/template لنفترض أننا نريد إنشاء تقرير بسيط عن بيانات الكلاب التي لدينا. تنسيق التقرير المطلوب هو كما يلي: --- Name: Jujube Sex: Female (spayed) Age: 10 months Breed: German Shepherd/Pitbull --- Name: Zephyr Sex: Male (intact) Age: 13 years, 3 months Breed: German Shepherd/Border Collie نحتاج لإنشاء هذا التقرير باستخدام حزمة text/template إلى استيراد الحزمة اللازمة وإعداد المشروع. يتألف التقرير من نص ثابت في القالب (العناصر على اليسار) وبيانات ديناميكية نمررها إلى القالب لتقديمها (على اليمين). يمكن تخزين القوالب مثل متغيرات من النوع string ضمن الشيفرة أو ملفات منفصلة خارج الشيفرة. تحتوي القوالب على نص ثابت معياري متداخل مع عبارات شرطية else و if وعبارات التحكم في التدفق (الحلقات) واستدعاءات الدوال، وكلها محاطة داخل أقواس معقوصة {{. . .}}. أخيرًا يمكننا إنشاء المستند النهائي من خلال توفير البيانات للقالب كما في المثال أعلاه. ننتقل الآن إلى مساحة العمل الخاصة بنا "go env GOPATH" وننشئ مجلدًا جديدًا لهذا المشروع، ثم ننتقل إليه. يمكن إجراء ذلك من خلال التعليمات التالية بالترتيب: $ cd `go env GOPATH` $ mkdir pets $ cd pets باستخدام محرر نانو nano أو أي محرر آخر تريده، نفتح ملفًا جديدًا يسمى "pets.go": $ nano pets.go ونضع فيه التعليمات التالية: package main import ( "os" "text/template" ) func main() { } تنتمي الشيفرة السابقة إلى الحزمة main، وتتضمّن الدالة main التي تسمح بتنفيذ الشيفرة باستخدام الأمر go run. تستورد الشيفرة حزمتين، هما: text/template من مكتبة جو القياسية، والتي نستخدمها لكتابة القالب وعرضه، وحزمة os للتفاعل مع نظام التشغيل من خلال الدوال التي توفرها. بذلك تكون الأمور جاهزة لبدء كتابة المنطق اللازم لإنشاء التقرير المطلوب باستخدام حزمة text/template. الخطوة 2- إنشاء بيانات القالب بدايةً يجب أن يكون لدينا بعض البيانات لتمريرها إلى القالب، لذا سنعرّف بنيةً تسمى Pet تمثل خصائص حيوان أليف. تحتفظ هذه البنية ببيانات كل كلب في التقرير. . . . type Pet struct { Name string Sex string Intact bool Age string Breed string } نُنشئ أيضًا شريحةً من Pet لتخزين معلومات كلبين: func main() { dogs := []Pet{ { Name: "Jujube", Sex: "Female", Intact: false, Age: "10 months", Breed: "German Shepherd/Pitbull", }, { Name: "Zephyr", Sex: "Male", Intact: true, Age: "13 years, 3 months", Breed: "German Shepherd/Border Collie", }, } } // end main عرّفنا البنية Pet بحقول تمثل الخصائص المختلفة للحيوان الأليف. نستخدم هذه البنى للاحتفاظ ببيانات كل كلب في التقرير. تتضمن الحقول: اسم الحيوان الأليف Name وجنس الحيوان الأليف Sex وقيمة منطقية تشير إلى ما إذا كان الحيوان الأليف سليم Intact وعمر الحيوان الأليف Age والسلالة Breed. أنشأنا داخل الدالة main شريحة Pet باسم dogs وملأناها بنموذجين من كلاب مختلفة. الكلب الأول يُسمّى Jujube والكلب الثاني Zephyr. من المهم ملاحظة أنه في سيناريو العالم الحقيقي يمكن جلب بيانات القالب من قاعدة بيانات أو الحصول عليها من واجهة برمجة تطبيقات خارجية أو توفيرها من خلال إدخال المستخدم، لكن هنا أدخلنا البيانات يدويًا. يمكننا الآن المتابعة إلى الخطوة التالية لكتابة القالب وعرضه. الخطوة 3- تنفيذ وعرض بيانات القالب حان الوقت الآن لاستكشاف كيفية استخدام حزمة text/template لتوليد مستند من قالب، ولكي نتأكد من أن الأمور تعمل بنجاح، سننشئ ملف قالب فارغ ثم نمرّر البيانات إلى القالب لتنفيذه. على الرغم من أن النموذج الأولي لن يعرض سوى النص "Nothing here yet"، إلا أنه سيكون بمثابة نقطة بداية لتوضيح دوال الحزمة text/template. ننشئ ملف باسم pets.tmpl بالمحتوى التالي: Nothing here yet. نحفظ القالب ونخرج من المحرر. في حالة المحرر نانو nano، نضغط على المفتاحين "CTRL + X" ثم المفتاح "Y" و "ENTER" لتأكيد التغييرات. نضيف الآن مقتطف الشفرة التالي داخل main: . . . var tmplFile = “pets.tmpl” tmpl, err := template.New(tmplFile).ParseFiles(tmplFile) if err != nil { panic(err) } err = tmpl.Execute(os.Stdout, dogs) if err != nil { panic(err) } } // main نهاية الدالة صرّحنا ضمن الدالة main عن المتغير tmplFile وأسندنا له القيمة “pets.tmpl”، والتي تمثل اسم ملف القالب. استخدمنا بعد ذلك الدالة template.New لإنشاء قالب من template، مع تمرير tmplFile اسمًا للقالب. استدعينا بعد ذلك ParseFiles في القالب الذي أنشأناه حديثًا، مع تمرير tmplFile مثل ملف لتحليله. تربط هذه الخطوة ملف القالب بالقالب. تحققنا بعد ذلك من أية أخطاء حدثت أثناء تحليل القالب. نلتقط الخطأ في حالة حدوثه، وتنتج لدينا حالة هلع panic في البرنامج. الآن لتنفيذ القالب نستدعي التابع Execute، ونمرر له وسيط أول os.Stdout ليكون وجهة الخرج ووسيط ثان dogs ليمثّل البيانات الممررة إلى القالب. يمثل os.Stdout (أو أي شيء آخر يحقق الواجهة io.Writer، أي ملف مثلًا) الخرج القياسي الذي سيطبع في هذه الحالة التقرير المُنشأ على الطرفية. سيؤدي تنفيذ القالب في هذه المرحلة إلى عرض النص المذكور أنفًا، وذلك لأننا لا نستخدم بيانات ديناميكية في القالب. ستكون الشيفرة كاملة كما يلي: package main import ( "os" "text/template" ) type Pet struct { Name string Sex string Intact bool Age string Breed string } func main() { dogs := []Pet{ { Name: "Jujube", Sex: "Female", Intact: false, Age: "10 months", Breed: "German Shepherd/Pitbull", }, { Name: "Zephyr", Sex: "Male", Intact: true, Age: "13 years, 3 months", Breed: "German Shepherd/Border Collie", }, } var tmplFile = “pets.tmpl” tmpl, err := template.New(tmplFile).ParseFiles(tmplFile) if err != nil { panic(err) } err = tmpl.Execute(os.Stdout, dogs) if err != nil { panic(err) } } // main نهاية الدالة لنُشغّل ملف البرنامج "pets.go" من خلال الأمر go run: $ go run pets.go سيكون الخرج على النحو التالي: Nothing here yet. لا يطبع البرنامج البيانات حتى الآن، ولكن على الأقل يعمل بطريقة صحيحة. لنكتب الآن قالبًا. الخطوة 3- كتابة قالب القالب هو أكثر من مجرد نص عادي بترميز UTF-8، إذ يحتوي القالب على نص ثابت إضافةً إلى الإجراءات التي توجّه محرك القالب حول كيفية معالجة البيانات وإنشاء المخرجات. تُغلّف الإجراءات بأقواس معقوصة {{ <action> }}، وتعمل على البيانات باستخدام تدوين النقطة (.). من الشائع استخدام بُنى البيانات القابلة للتكرار عند تمرير البيانات إلى قالب، مثل الشرائح أو المصفوفات أو الروابط maps. سنستكشف في هذه الخطوة كيفية التكرار على شريحة في القالب باستخدام range. التكرار على شريحة يمكننا استخدام الكلمة المفتاحية range في لغة جو داخل حلقة for للتكرار على شريحة، وكذلك هو الحال في القوالب؛ إذ يمكننا استخدام الإجراء range لتحقيق نفس النتيجة، ولكن بصيغة مختلفة قليلًا؛ فبدلًا من استخدام كلمة مفتاحية for، يمكن ببساطة استخدام range متبوعًا بالبيانات القابلة للتكرار، وتغلق الحلقة بالتعليمة {{ end }}. لنعدّل ملف "pets.tmpl" عن طريق استبدال محتوياته بما يلي: {{ range . }} --- (Pet will appear here...) {{ end }} يتخذ الإجراء range النقطة (.) وسيطًا له، والذي يمثل كامل شريحة dogs، ثم نُغلق الحلقة باستخدام {{ end }}. نضع ضمن الحلقة نصًا ثابتًا سيُعرض لكل حيوان أليف. في هذه المرحلة عمومًا، لن تُعرض أية معلومات عن الكلاب في الخرج. احفظ الملف "pets.tmpl" وشغّل ملف البرنامج "pets.go" من خلال الأمر go run: $ go run pets.go سيكون الخرج على النحو التالي: --- (Pet will appear here...) --- (Pet will appear here...) يُطبع النص الثابت مرتين نظرًا لوجود كلبين في الشريحة. دعونا الآن نستبدل هذا ببعض النصوص الثابتة المفيدة، إلى جانب بيانات الكلاب. عرض حقل عند استخدام range مع النقطة . في القالب السابق، إذ تشير النقطة إلى العنصر الحالي في الشريحة أثناء كل تكرار للحلقة وعندما يكون هناك عنصر واحد في الشريحة فهو يشير إلى كامل الشريحة. يتيح ذلك الوصول إلى الحقول المُصدّرة لكل حيوان أليف مباشرةً باستخدام تدوين النقطة دون الحاجة إلى الإشارة إلى فهارس الشريحة. بالتالي لكي نعرض حقل، يمكن ببساطة تغليفه بأقواس معقوصة وإسباقه بنقطة. لنحّدث ملف "pets.tmpl" بالشيفرة التالية: {{ range . }} --- Name: {{ .Name }} Sex: {{ .Sex }} Age: {{ .Age }} Breed: {{ .Breed }} {{ end }} بذلك سيتضمن الخرج أربعة حقول لكل كلب: الاسم والجنس والعمر والسلالة. تمثل النقطة . الحيوان الأليف الحالي الذي يجري تكراره في الحلقة. إذًا يمكننا الوصول إلى الحقل المقابل لكل حيوان أليف وعرض قيمته جنبًا إلى جنب مع التسميات المناسبة باستخدام تدوين النقطة. لنُشغّل ملف البرنامج "pets.go" من خلال الأمر go run: $ go run pets.go سيكون الخرج على النحو التالي: --- Name: Jujube Sex: Female Age: 10 months Breed: German Shepherd/Pitbull --- Name: Zephyr Sex: Male Age: 13 years, 3 months Breed: German Shepherd/Border Collie يبدو الأمر جيدًا. الآن دعونا نرى كيفية استخدام المنطق الشرطي لعرض الحقل الخامس. استخدام الشروط تجاهلنا الحقل Intact في القالب السابق؛ لإبقاء التقرير أكثر سهولة للقراءة، فبدلًا من عرض القيمة المنطقية مباشرةً true أو false، يمكننا استخدام إجراء if-else لتخصيص الخرج بناءً على قيمة الحقل، وتقديم معلومات أوضح وأكثر سهولة للفهم من مجرد وضع true أو false. نفتح ملف "pets.tmpl" مجددًا ونعدّل القالب على النحو التالي: {{ range . }} --- Name: {{ .Name }} Sex: {{ .Sex }} ({{ if .Intact }}intact{{ else }}fixed{{ end }}) Age: {{ .Age }} Breed: {{ .Breed }} {{ end }} يشتمل القالب الآن على عبارة if-else للتحقق من قيمة الحقل Intact. إذا كان الحقل true، فإنه يطبع (intact)، وإلا فإنه يطبع (fixed). يمكننا أيضًا تحسين الخرج أكثر؛ من خلال عرض المصطلحات الخاصة بالجنس لكلب حالته fixed، مثل spayed أو neutered، بدلًا من استخدام المصطلح العام fixed. لتحقيق ذلك يمكننا إضافة عبارة if متداخلة داخل كتلةelse: {{ range . }} --- Name: {{ .Name }} Sex: {{ .Sex }} ({{ if .Intact }}intact{{ else }}{{ if (eq .Sex "Female") }}spayed{{ else }}neutered{{ end }}{{ end }}) Age: {{ .Age }} Breed: {{ .Breed }} {{ end }} مع هذا التعديل؛ يتحقق القالب أولًا مما إذا كان الحيوان الأليف سليمًا. إذا لم يكن الأمر كذلك، فإنه يتحقق أيضًا مما إذا كان الحيوان الأليف أنثى Female. يسمح هذا بمعلومات أكثر دقة في التقرير. احفظ ملف القالب وشغّل ملف البرنامج "pets.go" من خلال الأمر go run: $ go run pets.go سيكون الخرج: --- Name: Jujube Sex: Female (spayed) Age: 10 months Breed: German Shepherd/Pitbull --- Name: Zephyr Sex: Male (intact) Age: 13 years, 3 months Breed: German Shepherd/Border Collie لدينا كلبان وثلاث حالات محتملة لعرض Intact. دعونا نضيف كلبًا آخر إلى الشريحة في pets.go لتغطية الحالات الثلاث: . . . func main() { dogs := []Pet{ { Name: "Jujube", Sex: "Female", Intact: false, Age: "10 months", Breed: "German Shepherd/Pitbull", }, { Name: "Zephyr", Sex: "Male", Intact: true, Age: "13 years, 3 months", Breed: "German Shepherd/Border Collie", }, { Name: "Bruce Wayne", Sex: "Male", Intact: false, Age: "3 years, 8 months", Breed: "Chihuahua", }, } . . . لنُشغّل ملف البرنامج "pets.go" من خلال الأمر go run: $ go run pets.go ليكون الخرج: --- Name: Jujube Sex: Female (spayed) Age: 10 months Breed: German Shepherd/Pitbull --- Name: Zephyr Sex: Male (intact) Age: 13 years, 3 months Breed: German Shepherd/Border Collie --- Name: Bruce Wayne Sex: Male (neutered) Age: 3 years, 8 months Breed: Chihuahua رائع، يبدو كما هو متوقع. الآن دعونا نناقش دوال القالب، مثل الدالة eq التي استخدمناها للتو. استخدام دوال القالب توفر الحزمة text/template -إضافةً إلى الدالة eq التي استخدمناها سابقًا- العديد من الدوال الأخرى لمقارنة قيم الحقول وإرجاع النتائج المنطقية، مثل gt (أكبر من) و ne (عدم تساوي) و le (أقل من أو يساوي) والمزيد. يمكن استدعاء هذه الدوال بطريقتين مختلفتين: كتابة اسم الدالة متبوعة بمعامل واحد أو أكثر ومفصولة بمسافات. هذه هي الطريقة التي استخدمنا بها الدالة eq في هذا المقال:"eq .Sex "Female. كتابة معامل واحد متبوع برمز الأنبوب |، ثم اسم الدالة والمعلمات الإضافية إذا لزم الأمر. يسمح هذا بربط استدعاءات عدة دوال معًا، مع جعل خرج كل دالة مدخلًا للتالية. هذا مشابه لكيفية عمل أنابيب الأوامر في سطر أوامر Unix. مثلًا يمكن كتابة عملية المقارنة السابقة باستخدام الدالة eq في القالب بالشكل: "Sex | eq "Female.، وهذا يُكافئ التعبير "eq .Sex "Female. دعونا الآن نستخدم الدالة len لعرض عدد الكلاب في الجزء العلوي من التقرير. نفتح ملف "pets.tmpl" ونضيف الشيفرة التالية في البداية: Number of dogs: {{ . | len -}} {{ range . }} . . . يمكنك أيضًا كتابتها بالشكل {{ - . len }}. تحسب هذه الدالة طول البيانات المُمررة في .، وهي في هذه الحالة شريحة الكلاب. بالتالي سنتمكن من عرض عدد الكلاب في أعلى التقرير من خلال تضمين هذه الدالة في القالب. لاحظ الشَرطة - بجانب الأقواس المزدوجة المعقوصة، وتمنع هذه الشرطة طباعة الأسطر الجديدة n\ بعد الإجراء. يمكن أيضًا استخدامها لمنع طباعة السطر الجديد قبل الإجراء من خلال وضعها قبل الإجراء، أي في البداية {{ - . len - }}. لنُشغّل ملف البرنامج "pets.go" من خلال الأمر go run: $ go run pets.go سيكون الخرج على النحو التالي: Number of dogs: 3 --- Name: Jujube Sex: Female (spayed) Age: 10 months Breed: German Shepherd & Pitbull --- Name: Zephyr Sex: Male (intact) Age: 13 years, 3 months Breed: German Shepherd & Border Collie --- Name: Bruce Wayne Sex: Male (neutered) Age: 3 years, 8 months Breed: Chihuahua باستخدام الشرطة في {{- len | .}}، لا توجد أسطر فارغة بين جملة Number of dogs وتفاصيل الكلب الأول. تجدر الإشارة إلى أن الدوال المضمنة في حزمة text/template محدودة، ومع ذلك يمكن استخدام أي دالة في لغة جو مع القوالب طالما أنها تُرجع قيمة واحدة أو قيمتين، إذ تكون الثانية قيمةً من نوع خطأ error. يسمح هذا بتوسيع دوال القوالب التي يمكن استخدامها من خلال دمج دوال خاصة. استخدام دوال لغة جو مع القوالب لنفترض أننا نريد كتابة قالب يأخذ شريحة من الكلاب ويعرض فقط الكلب الأخير. يمكننا في قوالب لغة جو استخراج مجموعة فرعية من شريحة باستخدام الدالة المبنية مسبقًا slice، والتي تعمل بطريقة تشبه [mySlice [x:y في لغة جو. إذا كنا نريد مثلًا استرداد العنصر الأخير من شريحة مكونة من ثلاثة عناصر، فيمكن استخدام {{ slice . 2 }}. من المهم ملاحظة أن slice تُرجع شريحةً أخرى، وليس عنصرًا فرديًا. لذا، {{slice. 2}} تكافئ [:slice [2، وليس [slice [2. يمكن أيضًا أن تقبل الدالة slice عدة فهارس، مثل{{ slice. 0 2 }}لاسترداد الشريحة [slice [0: 2، لكننا لن نستخدم ذلك في هذا السيناريو. يظهر التحدي عندما نريد الإشارة إلى الفهرس الأخير للشريحة داخل القالب الخاص بنا. على الرغم من أن الدالة len متاحة، إلا أن العنصر الأخير في الشريحة موجود في الفهرس len - 1، وللأسف، لا تدعم القوالب العمليات الحسابية. هنا يمكننا إنشاء دالة مخصصة للتغلب على هذا القيد، وذلك من خلال كتابة دالة تُنقص قيمة عدد صحيح مُمرر لها. بدايةً ننشئ ملف قالب جديد. نفتح ملفًا جديدًا يسمى "lastPet.tmpl" ونضع المحتوى التالي: {{- range (len . | dec | slice . ) }} --- Name: {{ .Name }} Sex: {{ .Sex }} ({{ if .Intact }}intact{{ else }}{{ if ("Female" | eq .Sex) }}spayed{{ else }}neutered{{ end }}{{ end }}) Age: {{ .Age }} Breed: {{ .Breed }} {{ end -}} يستخدم هذا القالب إجراء range مع الشريحة المُعدّلة للتكرار على آخر كلب في الشريحة المحددة. نُطبّق الدالة المخصصة dec الموجودة في السطر الأول لتقليل طول الشريحة، مما يسمح لنا بالوصول إلى الفهرس الأخير. يعرض القالب بعد ذلك المعلومات ذات الصلة بالكلب الأخير، بما في ذلك الاسم والجنس والعمر والسلالة. لتعريف الدالة dec المخصصة وتمريرها إلى القالب، نُجري التغييرات التالية داخل الدالة main في الملف "pets.go" -بعد شريحة الكلاب وقبل استدعاء ()tmpl.Execute- كما هو موضح أدناه: . . . funcMap := template.FuncMap{ "dec": func(i int) int { return i - 1 }, } var tmplFile = “lastPet.tmpl” tmpl, err := template.New(tmplFile).Funcs(funcMap).ParseFiles(tmplFile) if err != nil { panic(err) } . . . صرّحنا عن FuncMap على أنها رابط map للدوال، إذ تمثِّل أزواج (المفتاح، القيمة) أسماء الدوال والتطبيق المقابلة لها. نُعرّف في هذه الحالة الدالة dec على أنها دالة مجهولة تطرح 1 من عدد صحيح وتعيد النتيجة. نُغيّر بعد ذلك اسم ملف القالب إلى "lastPet.tmpl". أخيرًا نستدعي التابع Funcs من القالب، قبل استدعاء ParseFiles، ونمرر له funcMap لإتاحة الدالة dec داخل القالب. من المهم ملاحظة أنه يجب استدعاء Funcs قبل ParseFiles لتسجيل الدالة المخصصة بطريقة صحيحة مع القالب. دعونا نفهم بدايةً ما يحدث في الإجراء range: {{- range (len . | dec | slice . ) }} يجري في هذا السطر الحصول على طول شريحة الكلاب باستخدام . len، ثم تمرير النتيجة إلى الدالة dec المخصصة لطرح قيمة 1 من المتغير المُمرر لها len . | dec، ثم تمرير النتيجة مثل معاملٍ ثانٍ إلى الدالة slice. لذلك، بعبارات أبسط، بالنسبة لشريحة مكونة من ثلاثة كلاب، فإن range تعادل: {{- range (slice . 2) }} لنُشغّل ملف البرنامج "pets.go" بعد حفظه من خلال الأمر go run: $ go run pets.go سيكون الخرج على النحو التالي: --- Name: Bruce Wayne Sex: Male (neutered) Age: 3 years, 8 months Breed: Chihuahua يبدو هذا جيدًا. ماذا لو أردنا إظهار آخر كلبين بدلًا من آخر كلب فقط؟ نُحرّر الملف "lastPet.tmpl" ونضيف استدعاءً آخرًا للدالة dec: {{- range (len . | dec | dec | slice . ) }} . . . لنُشغّل ملف البرنامج "pets.go" بعد حفظه من خلال الأمر go run: $ go run pets.go ليكون الخرج على النحو التالي: --- Name: Zephyr Sex: Male (intact) Age: 13 years, 3 months Breed: German Shepherd/Border Collie --- Name: Bruce Wayne Sex: Male (neutered) Age: 3 years, 8 months Breed: Chihuahua يمكن تحسين الدالة dec من خلال جعلها تأخذ معاملًا واحدًا ونغيّر اسمها بحيث نكتب minus 2 بدلًا من dec | dec. لنفرض أننا أردنا عرض الكلاب الهجينة مثل "Zephyr" بطريقة مختلفة، وذلك باستبدال الشرطة المائلة بعلامة العطف &. لحسن الحظ لن نضطر لكتابة دالة خاصة لذلك، إذ يمكننا الاستفادة من دالة موجودة في الحزمة strings، لكن نحتاج إلى إجراء بعض التغييرات على ملف "pets.go" قبل ذلك. نستورد أولًا الحزمة strings مع الحزم الأخرى في أعلى الملف. نُحدِّث بعد ذلك المتغير funcMap داخل الدالة main لتضمين دالة ReplaceAll من حزمة strings: package main import ( "os" "strings" "text/template" ) . . . func main() { . . . funcMap := template.FuncMap{ "dec": func(i int) int { return i - 1 }, "replace": strings.ReplaceAll, } . . . } // main نهاية الدالة من خلال إضافة strings.ReplaceAll إلى funcMap، نكون قد جعلناها متاحةً في القالب تحت الاسم replace. نفتح ملف "lastPet.tmpl" ونعدّله لاستخدام replace: {{- range (len . | dec | dec | slice . ) }} --- Name: {{ .Name }} Sex: {{ .Sex }} ({{ if .Intact }}intact{{ else }}{{ if ("Female" | eq .Sex) }}spayed{{ else }}neutered{{ end }}{{ end }}) Age: {{ .Age }} Breed: {{ replace .Breed “/” “ & ” }} {{ end -}} لنُشغّل ملف البرنامج "pets.go" بعد حفظه من خلال الأمر go run: $ go run pets.go سيكون الخرج على النحو التالي: --- Name: Zephyr Sex: Male (intact) Age: 13 years, 3 months Breed: German Shepherd & Border Collie --- Name: Bruce Wayne Sex: Male (neutered) Age: 3 years, 8 months Breed: Chihuahua تحتوي سلالة Zephyr الآن على علامة عطف بدلًا من شرطة مائلة. أجرينا هذا التعديل على حقل Breed داخل القالب بدلًا من تعديل البيانات في pets.go. يتبع هذا المبدأ القائل بأن عرض البيانات هو مسؤولية القوالب وليس الشيفرة. تجدر الإشارة إلى أن بعض بيانات الكلاب، مثل حقل Breed، تحتوي على بعض المظاهر التي قد لا تكون طريقة عرض المعلومات فيها مثالية في هندسة البرمجيات عمومًا، إذ يمكن أن يؤدي التنسيق الحالي لتخزين سلالات متعددة في سلسلة واحدة مفصولة بشرطة مائلة / إلى اختلافات في عملية إدخال البيانات، مما يؤدي إلى تنسيقات غير متسقة في قاعدة البيانات (على سبيل المثال، Labrador/Poodle و Labrador & Poodleو Labrador, Poodleو Labrador-Poodle mix، إلخ). لمعالجة هذه المشكلة وتحسين المرونة في البحث حسب السلالة وتقديم البيانات، قد يكون من الأفضل تخزين حقل Breed مثل شريحة من السلاسل (string[]) بدلًا من سلسلة واحدة. سيؤدي هذا التغيير إلى إزالة الغموض في التنسيق ويسمح بمعالجة أسهل في القوالب. يمكن بعد ذلك استخدام دالة strings.Join ضمن القالب لربط جميع السلالات، جنبًا إلى جنب مع ملاحظة إضافية من خلال الحقل Breed. بحيث تشير إلى ما إذا كان الكلب سلالة أصيلة (purebred) أو سلالة هجينة (mixed breed). دعونا في الختام نعرض نفس البيانات في مستند HTML ونرى لماذا يجب علينا دائمًا استخدام حزمة html/template عندما يكون ناتج القالب الخاصة بنا بتنسيق HTML. الخطوة 5- كتابة قالب HTML في حين أن الحزمة text/template مناسبة لطباعة الخرج بدقة (سواءً من سطر الأوامر أو مكان آخر) وإنشاء ملفات منظّمة من البرامج الدفعية Batch program (برامج تعالج سلسلة من المهام أو الأوامر دفعة واحدة أو بطريقة غير تفاعلية)، إلا أنه من الشائع استخدام قوالب لغة جو لتصيير صفحات HTML في تطبيقات الويب. على سبيل المثال، يعتمد مُنشئ الموقع الثابت (أداة تساعد في إنشاء ملفات HTML ثابتة بناءً على القوالب والمحتوى. يبسط عملية إنشاء مواقع الويب وإدارتها عن طريق تحويل القوالب والمحتوى والموارد الأخرى إلى موقع ويب ثابت جاهز للنشر) هوغو Hugo على كل من text/template و html/template مثل أساس لنظام القوالب الخاص به. تتيح هذه الحزم للمستخدمين تحديد القوالب ذات معاملات النوع وإدراج البيانات ديناميكيًا فيها، مما يتيح إنشاء صفحات HTML لمواقع الويب. تقدم لغة HTML ميزات فريدة لا نراها مع النص العادي، إذ تستخدم أقواس الزاوية لتغليف العناصر (<td>) وعلامات العطف لتمييز الكيانات (;nbsp&) وعلامات الاقتباس لتغليف قيم أو سمات الوسوم (<"/a href="https://www.digitalocean.com>). عند إدخال البيانات التي تحتوي على هذه الأحرف باستخدام حزمة text/template، يمكن أن ينتج عن ذلك HTML تالف أو حتى حقن شيفرة Code injection (ثغرة أمنية يتمكن منها المهاجم من إدخال التعليمات البرمجية الضارة وتنفيذها داخل تطبيق أو نظام). تعالج حزمة html/template هذه التحديات، بحيث تهرب تلقائيًا من المحارف التي قد تخلق إشكالية، وتستبدلها بكيانات HTML الآمنة. تُصبح علامة العطف في البيانات (;amp&) وقوس الزاوية اليسرى (;It&) وهكذا. دعونا نواصل استخدام نفس بيانات الكلاب، لإثبات خطورة استخدام text/template مع HTML. نفتح "pets.go" ونعدّل حقل Name على النحو التالي: . . . dogs := []Pet{ { Name: "<script>alert(\"Gotcha!\");</script>Jujube", Sex: "Female", Intact: false, Age: "10 months", Breed: "German Shepherd/Pit Bull", }, { Name: "Zephyr", Sex: "Male", Intact: true, Age: "13 years, 3 months", Breed: "German Shepherd/Border Collie", }, { Name: "Bruce Wayne", Sex: "Male", Intact: false, Age: "3 years, 8 months", Breed: "Chihuahua", }, } . . . نُنشئ الآن قالب HTML في ملف جديد يسمى "petsHtml.tmpl": <p><strong>Pets:</strong> {{ . | len }}</p> {{ range . }} <hr /> <dl> <dt>Name</dt> <dd>{{ .Name }}</dd> <dt>Sex</dt> <dd>{{ .Sex }} ({{ if .Intact }}intact{{ else }}{{ if (eq .Sex "Female") }}spayed{{ else }}neutered{{ end }}{{ end }})</dd> <dt>Age</dt> <dd>{{ .Age }}</dd> <dt>Breed</dt> <dd>{{ replace .Breed “/” “ & ” }}</dd> </dl> {{ end }} نحفظ قالب HTML. نحتاج إلى تعديل المتغير tmpFile قبل تشغيل "pets.go"، ولكن دعونا أيضًا نُعدّل البرنامج لإخراج القالب إلى ملف بدلًا من الطرفية. نفتح الملف "pets.go" ونضيف الشيفرة التالية داخل الدالة main: . . . funcMap := template.FuncMap{ "dec": func(i int) int { return i - 1 }, "replace": strings.ReplaceAll, } var tmplFile = "petsHtml.tmpl" tmpl, err := template.New(tmplFile).Funcs(funcMap).ParseFiles(tmplFile) if err != nil { panic(err) } var f *os.File f, err = os.Create("pets.html") if err != nil { panic(err) } err = tmpl.Execute(f, dogs) if err != nil { panic(err) } err = f.Close() if err != nil { panic(err) } } // end main نفتح ملف File جديد يسمى "pets.html" ونمرّره (بدلًا من os.Stdout) إلى tmpl.Execute، ثم نغلق الملف عند الانتهاء. لنُشغّل ملف البرنامج "pets.go" من خلال الأمر go run لإنشاء ملف HTML. نفتح بعد ذلك صفحة الويب المحلية هذه في المتصفح: $ go run pets.go شغّل المتصفح البرنامج النصي المحقون، وهذا هو السبب في أنه لا يجب أبدًا استخدام حزمة text/template لإنشاء HTML، خاصةً عندما لا يمكن الوثوق تمامًا بمصدر بيانات القالب. بصرف النظر عن محارف الهروب في HTML، تعمل حزمة html /template تمامًا مثل text/template ولها نفس الاسم الأساسي ("قالب" template)، مما يعني أن كل ما علينا فعله لجعل القالب آمنًا هو استبدال استيراد text/template مع html /template. لنعدّل ملف "pets.go" وفقًا لذلك الآن: package main import ( "os" "strings" "html/template" ) . . . نحفظ الملف لتعديل بيانات "pets.html" ونشغّله مرةً أخيرة. ثم نعيد تحميل ملف HTML في المتصفح: صيّرت حزمة html/template النص المُدخل على أنه نص فقط في صفحة الويب. نفتح الملف "pets.html" في محرر النصوص (أو نعرض مصدر الصفحة في المتصفح) وننظر إلى أول كلب Jujube: . . . <dl> <dt>Name</dt> <dd>&lt;script&gt;alert(&#34;Gotcha!&#34;);&lt;/script&gt;Jujube</dd> <dt>Sex</dt> <dd>Female (spayed)</dd> <dt>Age</dt> <dd>10 months</dd> <dt>Breed</dt> <dd>German Shepherd &amp; Pit Bull</dd> </dl> . . . استبدلت حزمة html أقواس الزاوية ومحارف الاقتباس في اسم Jujube، وكذلك علامة العطف في السلالة. الخاتمة توفر قوالب لغة جو حلًا متعدد القدرات لدمج البيانات في تنسيقات نصية متنوعة. يمكن استخدام القوالب في أدوات سطر الأوامر لتنسيق الخرج، وكذلك في تطبيقات الويب لإنشاء صفحات HTML. تعلمنا خلال هذا المقال كيفية الاستفادة من حزم القوالب المضمنة في لغة جو لإنتاج نص جيد التنظيم وعرض HTML باستخدام نفس البيانات. ترجمة -وبتصرف- للمقال How To Use Templates in Go لصاحبه Kristin Davidson. اقرأ أيضًا المقال السابق كيفية استخدام الأنواع المعممة Generics في لغة جو Go. كيفية استخدام القوالب في تطبيقات فلاسك Flask. مكونات الويب: عناصر HTML المخصصة وقوالبها.
  11. قدمت لغة جو في الإصدار 1.18 ميزةً جديدة تُعرف باسم الأنواع المُعمّمة generic types -أو اختصارًا generics- والتي كانت في قائمة أمنيات مُطوّري هذه اللغة لبعض الوقت. والنوع المُعمّم في لغات البرمجة، هو نوع يمكن استخدامه مع أنواع متعددة أخرى. سابقًا، عندما كنا نرغب في استخدام نوعين مختلفين لنفس المتغير في لغة جو، كنا نحتاج إما لاستخدام واجهة مُعرّفة بأسلوب معين مثل io.Reader، أو استخدام {}interface الذي يسمح باستخدام أي قيمة. المشكلة في أن استخدام {}interface يجعل التعامل مع تلك الأنواع صعبًا، لأنه يجب التحويل بين العديد من الأنواع الأخرى المحتملة للتفاعل معها (عليك تحويل القيم بين أنواع مختلفة من البيانات للتعامل معها). على سبيل المثال، إذا كان لدينا قيمة من {}interface تمثل عددًا، يجب علينا تحويلها إلى int قبل أن تتمكن من إجراء العمليات الحسابية عليها. هذه العملية قد تكون معقدة وتستلزم كتابة الكثير من التعليمات الإضافية للتعامل مع تحويل الأنواع المختلفة، أما الآن وبفضل الأنواع المُعمّمة، يمكننا التفاعل مباشرةً مع الأنواع المختلفة دون الحاجة للتحويل بينها، مما يؤدي إلى شيفرة نظيفة سهلة القراءة. نُنشئ في هذا المقال برنامجًا يتفاعل مع مجموعة من البطاقات، إذ سنبدأ بإنشائها بحيث تستخدم {}interface للتفاعل مع البطاقات الأخرى، ثم نُحدّثها من أجل استخدام الأنواع المُعمّمة، ثم سنضيف نوعًا ثانيًا من البطاقات باستخدام الأنواع المُعمّمة، ثم سنُحدّثها لتقييد نوعها المُعمّم، بحيث تدعم أنواع البطاقات فقط. نُنشئ أخيرًا دالةً تستخدم البطاقات الخاصة بنا وتدعم الأنواع المُعمّمة. المتطلبات الأولية لمتابعة هذا المقال التعليمي، سنحتاج إلى: إصدار مُثبّت من جو 1.16 أو أعلى، ويمكنك الاستعانة بمقال تثبيت لغة جو Go وإعداد بيئة برمجة محلية على أبونتو Ubuntu لإعداده. تثبيت لغة جو وإعداد بيئة برمجة محلية على نظام ماك macOS. تثبيت لغة جو وإعداد بيئة برمجة محلية على ويندوز. فهم قوي لأساسيات لغة جو، مثل المتغيرات والدوال وأنواع البيانات والشرائح والحلقات ..إلخ. كل الأساسيات ومقالات أخرى متقدمة في هذه اللغة تجدها هنا. التجميعات Collections في لغة جو بدون استخدام الأنواع المعممة يشير مصطلح التجميعة Collection في هذا السياق إلى حاوية بيانات يمكن أن تحتوي على قيم أو عناصر متعددة، وهو مفهوم ذو مستوى أعلى يشمل أنواعًا مختلفة من هياكل البيانات، مثل المصفوفات والقوائم والمجموعات والروابط maps، مما يسمح بتخزين وتنظيم عدة قيم مرتبطة معًا. توفر التجميعات عمليات وتوابع للوصول إلى العناصر التي تحتويها ومعالجتها وتكرارها. نستخدم مصطلح "التجميعة" هنا لوصف الهيكل أو الحاوية المستخدمة لتخزين وإدارة أوراق اللعب في البرنامج. تتمتع لغة جو بميزة قوية تتمثّل في قدرتها على تمثيل العديد من الأنواع بطريقة مرنة باستخدام الواجهات interfaces. يمكن للكثير من الشيفرات المكتوبة بلغة جو أن تعمل جيدًا باستخدام دوال الواجهات المتاحة، وهذا هو أحد الأسباب التي جعلت اللغة موجودةً لفترة طويلة دون دعم الأنواع المُعمّمة. نُنشئ في هذا المقال برنامجًا يحاكي عملية الحصول على بطاقة لعب عشوائية PlayingCard من مجموعة بطاقات Deck. سنستخدم في هذا القسم {}interface للسماح للدستة Deck بالتفاعل مع أي نوع من البطاقات. نُعدّل لاحقًا البرنامج لاستخدام الأنواع المُعمّمة لنتمكن من فهم الفروق بينهما والتعرف على الحالات التي تكون فيها الأنواع المُعمّمة خيارًا أفضل من غيرها. يمكن تصنيف أنظمة النوع type systems في لغات البرمجة عمومًا إلى فئتين مختلفتين: النوع typing والتحقق من النوع type checking. تُشير الفئة الأولى إلى كيفية التعامل مع الأنواع في اللغة وكيفية تعريف وتمثيل أنواع البيانات المختلفة. هناك نوعان رئيسيان من أنظمة "النوع": النوع القوي strong والنوع الضعيف weak وكذلك الثابت static والديناميكي dynamic. تُطبّق قواعد صارمة في النوع القوي تضمن توافق الأنواع وعدم وجود تضارب بينها. على سبيل المثال، لا يمكن تخزين قيمة من نوع بيانات ما في متغير من نوع بيانات آخر (لا يمكن تخزين قيمة int في متغير string) إلا إذا كانت هناك توافقية بينهما. أما في النوع الضعيف، فقد تسمح اللغة بتحويل تلقائي للأنواع من خلال عمليات تحويل ضمنية. أما بالنسبة "للتحقق من النوع"، فهذا يشير إلى كيفية التحقق من صحة استخدام الأنواع في البرنامج. هناك نوعان رئيسيان من أنظمة التحقق من النوع: التحقق الثابت statically-checked والتحقق الديناميكي dynamically-checked. تُفحص الأنواع في التحقق الثابت خلال عملية التصريف ويُفحص توافقها وصحتها والتأكد من عدم وجود أخطاء في النوع قبل تنفيذ البرنامج. أما في التحقق الدينامكي، فإن الفحص يجري أثناء تنفيذ البرنامج نفسه، ويمكن للأنواع أن تتغير وتحول ديناميكيًا خلال تنفيذ البرنامج. تنتمي لغة جو عمومًا إلى فئة اللغات ذات النوع القوي والتحقق الثابت، إذ تستخدم قواعد صارمة للتحقق من توافق الأنواع وتتحقق من صحتها خلال عملية التصريف، مما يساعد في تجنب الأخطاء الناتجة عن تعامل غير صحيح مع الأنواع في البرنامج، لكن يترتب على ذلك بعض القيود على البرامج، لأنه يجب أن تُعرّف الأنواع التي تنوي استخدامها قبل تصريف البرنامج. يمكن التعامل مع هذا من خلال استخدام النوع {}interface، إذ يعمل نوع {}interface مع أي قيمة. لبدء إنشاء البرنامج باستخدام {}interface لتمثيل البطاقات، نحتاج إلى مجلد للاحتفاظ بمجلد البرنامج فيه، وليكن باسم "projects". أنشئ المجلد وانتقل إليه: $ mkdir projects $ cd projects نُنشئ الآن مجلدًا للمشروع، وليكن باسم "generics" ثم ننتقل إليه: $ mkdir generics $ cd generics من داخل هذا المجلد، نفتح الملف "main.go" باستخدام محرر نانو nano أو أي محرر آخر تريده: $ nano main.go نضع مقتطف الشيفرة التالي داخل هذا الملف، إذ نُصرّح عن الحزمة package لإخبار المُصرّف أن يحوّل البرنامج إلى ملف ثنائي لتتمكّن من تشغيله مباشرةً. نستورد أيضًا الحزم اللازمة عن طريق تعليمة import: package main import ( "fmt" "math/rand" "os" "time" ) نُعرّف النوع PlayingCard والدوال والتوابع المرتبطة به: ... type PlayingCard struct { Suit string Rank string } func NewPlayingCard(suit string, card string) *PlayingCard { return &PlayingCard{Suit: suit, Rank: card} } func (pc *PlayingCard) String() string { return fmt.Sprintf("%s of %s", pc.Rank, pc.Suit) } عرّفنا بنية بيانات تُسمى PlayingCard مع الخصائص نوع البطاقة Suit وترتيبها Rank لتمثيل مجموعة ورق اللعب Deck المكونة من 52 بطاقة. يكون Suit أحد القيم التالية: Diamonds أو Hearts أو Clubs أو Spades. أما Rank يكون أما A أو 2 أو 3، وهكذا حتى K. عرّفنا أيضًا دالة NewPlayingCard لتكون باني constructor لبنية البيانات PlayingCard، وتابع String ليُرجع ترتيب ونوع البطاقة باستخدام fmt.Sprintf. ننشئ الآن النوع Deck بالدوال AddCard و RandomCard، إضافةً إلى الدالة NewPlayingCardDeck لإنشاء Deck* مملوء بجميع بطاقات اللعبة التي عددها 52. ... type Deck struct { cards []interface{} } func NewPlayingCardDeck() *Deck { suits := []string{"Diamonds", "Hearts", "Clubs", "Spades"} ranks := []string{"A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K"} deck := &Deck{} for _, suit := range suits { for _, rank := range ranks { deck.AddCard(NewPlayingCard(suit, rank)) } } return deck } func (d *Deck) AddCard(card interface{}) { d.cards = append(d.cards, card) } func (d *Deck) RandomCard() interface{} { r := rand.New(rand.NewSource(time.Now().UnixNano())) cardIdx := r.Intn(len(d.cards)) return d.cards[cardIdx] } في البنية Deck المُعرّفة أعلاه، أنشأنا حقلًا يُسمّى cards لاحتواء مجموعة ورق اللعب. نظرًا لأننا نرغب في أن تكون مجموعة البطاقات قادرة على استيعاب أنواع متعددة مختلفة من البطاقات، فلا يمكن تعريفها فقط على أنها PlayingCard*. لذا عرّفناها على أنها {}interface[] حتى نتمكن من استيعاب أي نوع من البطاقات التي قد نُنشئها مستقبلًا. بالإضافة إلى الحقل {}interface[] في Deck، أنشأنا التابع AddCard الذي يقبل قيمة من نفس نوع interface{} لإضافة بطاقة إلى حقل البطاقات في Deck. أنشأنا أيضًا التابع RandomCard الذي يُرجع بطاقةً عشوائية من cards في Deck. يستخدم هذا التابع حزمة math/rand لإنشاء رقم عشوائي بين 0 وعدد البطاقات في المجموعة. يُنشئ سطر rand.New مولد رقم عشوائي جديد باستخدام الوقت الحالي مثل مصدر للعشوائية، وإلا فإن الرقم العشوائي يمكن أن يكون نفسه في كل مرة. يستخدم السطر ((r.Intn(len(d.cards مولد الأرقام العشوائية لإنشاء قيمة صحيحة بين 0 والعدد المُقدم. نظرًا لأن التابع Intn لا يشمل قيمة المعامل ضمن نطاق الأرقام الممكنة، فلا حاجة لطرح 1 من الطول لمعالجة فكرة البدء من 0 (إذا كان لدينا مجموعة ورق لعب تحتوي على 10 عناصر، فالأرقام الممكنة ستكون من 0 إلى 9). لدينا أيضًا RandomCard تُرجع قيمة البطاقة في الفهرس المحدد بالرقم العشوائي. تحذير: كن حذرًا في اختيار مولد الأرقام العشوائية الذي تستخدمه في برامجك. ليست حزمة math/rand آمنة من الناحية التشفيرية ولا ينبغي استخدامها في البرامج التي تتعلق بالأمان. توفر حزمة crypto/rand مولد أرقام عشوائية يمكن استخدامه لهذه الأغراض. بالنسبة للدالة NewPlayingCardDeck فهي تُرجع القيمة Deck* مملوءة بجميع البطاقات الموجودة في دستة بطاقات اللعب. نستخدم شريحتين، واحدة تحتوي على جميع الأشكال المتاحة وأخرى تحتوي على جميع الرتب المتاحة، ثم نكرر فوق كل قيمة لإنشاء PlayingCard* جديدة لكل تركيبة قبل إضافتها إلى المجموعة باستخدام AddCard. تُرجع القيمة بمجرد إنشاء بطاقات مجموعة ورق اللعب. بعد إعداد Deck و PlayingCard، يمكننا إنشاء الدالة main لاستخدامها لسحب البطاقات: ... func main() { deck := NewPlayingCardDeck() fmt.Printf("--- drawing playing card ---\n") card := deck.RandomCard() fmt.Printf("drew card: %s\n", card) playingCard, ok := card.(*PlayingCard) if !ok { fmt.Printf("card received wasn't a playing card!") os.Exit(1) } fmt.Printf("card suit: %s\n", playingCard.Suit) fmt.Printf("card rank: %s\n", playingCard.Rank) } أنشأنا بدايةً مجموعةً جديدة من البطاقات باستخدام الدالة NewPlayingCardDeck ووضعناها في المتغير deck، ثم استخدمنا fmt.Printf لطباعة جملة تدل على أننا نسحب بطاقة. نستخدم التابع RandomCard من deck للحصول على بطاقة عشوائية من المجموعة. نستخدم بعد ذلك fmt.Printf مرةً أخرى لطباعة البطاقة التي سحبناها من المجموعة. نظرًا لأن نوع المتغير card هو {}interface، سنحتاج إلى استخدام توكيد النوع type assertion للحصول على مرجع إلى البطاقة بنوعها الأصلي PlayingCard*. إذا كان النوع في المتغير card ليس نوع PlayingCard*، الذي يجب أن يكون عليه وفقًا لطريقة كتابة البرنامج الحالية، ستكون قيمة ok تساوي false وسيطبع البرنامج رسالة خطأ باستخدام fmt.Printf ويخرج مع شيفرة خطأ 1 باستخدام os.Exit. إذا كان النوع PlayingCard* سيطبع البرنامج قيمتي Suit و Rank من playingCard باستخدام fmt.Printf. لنُشغّل ملف البرنامج "main.go" بعد حفظه من خلال الأمر go run: $ go run main.go يُفترض أن نرى في الخرج بطاقة مُختارة عشوائيًا من المجموعة، إضافةً إلى نوع البطاقة وترتيبها: --- drawing playing card --- drew card: Q of Diamonds card suit: Diamonds card rank: Q قد تكون النتيجة المطبوعة مختلفةً عن النتيجة المعروضة أعلاه، لأن البطاقة مسحوبة عشوائيًا من المجموعة، لكن يجب أن تكون مشابهة. يُطبع السطر الأول قبل سحب البطاقة العشوائية من المجموعة، ثم يُطبع السطر الثاني بمجرد سحب البطاقة. يمكن رؤية خرج البطاقة باستخدام القيمة المُرجعة من التابع String من PlayingCard. يمكننا أخيرًا رؤية سطري النوع والرتبة المطبوعة بعد تحويل قيمة {}interface المستلمة إلى قيمة PlayingCard* مع الوصول إلى حقول Suit و Rank. أنشأنا في هذا القسم مجموعة Deck تستخدم قيم {}interface لتخزين والتفاعل مع أي قيمة، وأيضًا نوع PlayingCard ليكون بمثابة البطاقات داخل هذه الدستة Deck، ثم استخدمنا Deck و PlayingCard لاختيار بطاقة عشوائية من المجموعة وطباعة معلومات حول تلك البطاقة. للوصول إلى معلومات محددة تتعلق بقيمة PlayingCard* التي سُحبت، كُنّا بحاجة لعمل إضافي يتجلى بتحويل النوع {}interface إلى النوع PlayingCard* مع الوصول إلى حقول Suit و Rank. ستعمل الأمور في المجموعة Deck جيدًا بهذه الطريقة، ولكن قد ينتج أخطاء إذا أُضيفت قيمة أخرى غير PlayingCard* إلى المجموعة Deck. سنُعدّل في القسم التالي المجموعة Deck، بحيث يمكننا الاستفادة من خصائص الأنواع القوية والتحقق الثابت للأنواع في لغة جو، مع الاحتفاظ بالمرونة في قبول قيم {}interface، وذلك من خلال استخدام الأنواع المعممة. التجميعات في لغة جو مع استخدام الأنواع المعممة أنشأنا في القسم السابق تجميعةً باستخدام شريحة من أنواع {}interface، ولكن كان علينا إتمام عمل إضافي لاستخدام تلك القيم يتجلى بتحويل القيم من نوع {}interface إلى النوع الفعلي لتلك القيم. يمكننا باستخدام الأنواع المعممة إنشاء معامل واحد أو أكثر للأنواع، والتي تعمل تقريبًا مثل معاملات الدالة، ولكن يمكن أن تحتوي على أنواع بدلًا من بيانات. توفر الأنواع المعممة بهذا الأسلوب طريقةً لاستبدال نوع مختلف من معاملات الأنواع في كل مرة يجري فيها استخدام النوع المعمم. هنا يأتي اسم الأنواع المعممة؛ فبما أن النوع المعمم يمكن استخدامه مع أنواع متعددة، وليس فقط نوع محدد مثل io.Reader أو {}interface، يكون عامًا بما يكفي ليناسب عدة حالات استخدام. نُعدّل في هذا القسم مجموعة ورق اللعب Deck لتكون من نوع معمم يمكنه استخدام أي نوع محدد للبطاقة عند إنشاء نسخة من Deck بدلًا من استخدام {}interface. نفتح ملف "main.go" ونحذف استيراد حزمة os: package main import ( "fmt" "math/rand" // "os" حذف استيراد "time" ) لن نحتاج بعد الآن إلى استخدام دالة os.Exit، لذا فمن الآمن حذف هذا الاستيراد. نُعدّل البنية Deck الآن لتكون نوعًا معممًا: ... type Deck[C any] struct { cards []C } يقدم هذا التحديث الصيغة الجديدة لتعريف البنية Deck، بحيث نجعلها تحقق مفهوم النوع المُعمم، وذلك من خلال استخدام مفهوم "الموضع المؤقت Placeholder" أو "معاملات النوع Type parameters". يمكننا التفكير في هذه المعاملات بطريقة مشابهة للمعاملات التي نُضمّنُها في دالة؛ فعند استدعاء دالة، تُقدم قيمًا لكل معامل في الدالة. وهنا أيضًا، عند إنشاء قيمة من النوع المعمم، نُقدّم أنواعًا لمعاملات الأنواع. نلاحظ بعد اسم البنية Deck أننا أضفنا عبارة داخل قوسين مربعين ([])، إذ تسمح هذه الأقواس المعقوفة لنا بتحديد معامل أو أكثر من هذه المعاملات للبنية. نحتاج في حالتنا للنوع Deck إلى معامل نوع واحد فقط، يُطلق عليه اسم C، لتمثيل نوع البطاقات في المجموعة deck. من خلال كتابتنا C any نكون قد صرّحنا عن معامل نوع باسم C يمكننا استخدامه في البنية struct ليُمثّل أي نوع (any). ينوب any عن الأنواع المختلفة، أي يُعرف نوعه في الزمن الحقيقي، أي وقت تمرير قيمته، فإذا كانت القيمة المُمررة سلسلة يكون سلسلة، وإذا كانت عددًا صحيحًا يكون صحيحًا. يُعد النوع any في حقيقة الأمر اسمًا بديلًا للنوع {}interface، وهذا ما يجعل الأنواع المعممة أكثر سهولة للقراءة، فنحن بغنى عن كتابة {}C interface ونكتب C any فقط. يحتاج deck الخاص بنا إلى نوع معمم واحد فقط لتمثيل البطاقات، ولكن إذا كنا نحتاج إلى أنواع معممة إضافية، فيمكن إضافتها من خلال فصلها بفواصل، مثل F any, C any. يمكن أن يكون اسم المعامل المُستخدم لمعاملات النوع أي شيء نرغب فيه إذا لم يكن محجوزًا، ولكنها عادةً قصيرة وتبدأ بحرف كبير. عدّلنا أيضًا نوع الشريحة cards في البنية Deck ليكون من النوع C. عند استخدام مفهوم الأنواع المعممة، يمكننا استخدام معاملات النوع لتنوب عن أي نوع، أي يمكننا وضعها في نفس الأماكن التي نضع فيها أنواع البيانات عادةً. في حالتنا، نريد أن يمثل المعامل C كل بطاقة في الشريحة، لذا نضع اسم الشريحة ثم [] ثم المعامل C الذي سينوب عن النوع. نُعدّل الآن التابع AddCard لاستخدام النوع المعمّم الذي عرّفناه، لكن سنتخطى تعديل دالة NewPlayingCardDeck حاليًا: ... func (d *Deck[C]) AddCard(card C) { d.cards = append(d.cards, card) } تضمّن التحديث الخاص بالتابع AddCard في Deck، إضافة المعامل المُعمّم [C] إلى المستقبل الخاص بالتابع. يُخبر هذا لغة جو باسم المعامل المُعمّم الذي سنستخدمه في أماكن أخرى في تصريح التابع، ويتيح لنا ذلك معرفة النوع المُمرّر مثل قيمة إلى معامل النوع C عند استخدام التابع. يتبع هذا النوع من الكتابة بأقواس معقوفة نفس طريقة التصريح عن بنية struct، إذ يُعرّف معامل النوع داخل الأقواس المعقوفة بعد اسم الدالة والمستقبل. لا نحتاج في حالتنا إلى تحديد أي قيود لأنها مُحددة فعلًا في تصريح Deck. عدّلنا أيضًا معامل الدالة card لاستخدام معامل النوع C بدلًا من النوع الأصلي {}interface، وهذا يتيح للدالة استخدام النوع C الذي سيُعرف ما هو لاحقًا. بعد تعديل التابع AddCard، نُعدّل التابع RandomCard لاستخدام النوع المعمم C أيضًا: ... func (d *Deck[C]) RandomCard() C { r := rand.New(rand.NewSource(time.Now().UnixNano())) cardIdx := r.Intn(len(d.cards)) return d.cards[cardIdx] } بدلًا من استخدام نوع معمّم C مثل معامل دالة، عدّلنا التابع ()RandomCard ليعيد قيمةً من النوع المُعمّم C بدلًا من معامل الدالة {}interface. هذا يعني أنه عند استدعاء هذا التابع، سنحصل على قيمة من النوع C المحدد، بدلًا من الحاجة إلى تحويل القيمة إلى نوع محدد بواسطة "تأكيدات النوع type assertion". عدّلنا أيضًا المستقبل الخاص بالدالة ليتضمن [C]، وهو ما يخبر لغة جو بأن الدالة هي جزء من Deck التي تستخدم النوع المعمم C مثل معامل. لا نحتاج إلى أي تحديثات أخرى في الدالة. حقل cards في Deck مُعدّل فعلًا في تصريح البنية struct ليكون من النوع C[]، مما يعني أنه عندما يعيد التابع قيمة من cards، فإنه يعيد قيمةً من النوع C المحدد، وبالتالي لا حاجة لتحويل النوع بعد ذلك. بعد تعديلنا للنوع Deck لاستخدام الأنواع المعممة، نعود للدالة NewPlayingCardDeck لجعلها تستخدم النوع المعمم Deck مع الأنواع PlayingCard*: ... func NewPlayingCardDeck() *Deck[*PlayingCard] { suits := []string{"Diamonds", "Hearts", "Clubs", "Spades"} ranks := []string{"A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K"} deck := &Deck[*PlayingCard]{} for _, suit := range suits { for _, rank := range ranks { deck.AddCard(NewPlayingCard(suit, rank)) } } return deck } ... تبقى معظم الشيفرة في NewPlayingCardDeck كما هي، باستثناء بعض التغييرات البسيطة، ولكن الآن نستخدم إصدارًا مُعمّمًا من Deck، ويجب علينا تحديد النوع الذي نرغب في استخدامه مع C عند استخدام Deck. نفعل ذلك عن طريق الإشارة إلى نوع Deck بالطريقة المعتادة، سواء كان Deck نفسه أو مرجعًا مثل Deck*، ثم تقديم النوع الذي يجب استبدال C به باستخدام نفس الأقواس المعقوفة التي استخدمناها عند التصريح عن معاملات النوع في البداية. بمعنى آخر، عندما نُعرّف متغير من نوع Deck المعمّم، يجب علينا تحديد النوع الفعلي للمعامل C؛ فإذا كانت Deck من النوع Deck*، يجب أن نُوفّر النوع الفعلي الذي يجب استبدال C به في الأقواس المعقوفة مثل [PlayingCard*]. هكذا نحصل على نسخة محددة من Deck لنوع البطاقات التي ترغب في استخدامها، والتي في هذه الحالة هي PlayingCard*. يمكن تشبيه هذه العملية لإرسال قيمة لمعامل دالة عند استدعاء الدالة؛ فعند استخدامنا لنوع Deck المعمّم، فإننا نمرر النوع الذي نرغب في استخدامه مثل معامل لهذا النوع. بالنسبة لنوع القيمة المُعادة في NewPlayingCardDeck، نستمر في استخدام Deck* كما فعلت من قبل، لكن هذه المرة، يجب أن تتضمن الأقواس المعقوفة و PlayingCard* أيضًا؛ فمن خلال تقديم [PlayingCard*] لمعامل النوع، فأنت تقول أنك ترغب في استخدام النوع PlayingCard* في تصريح Deck والتوابع الخاصة به ليحل محل قيمة C، وهذا يعني أن نوع حقل cards في Deck يتغير فعليًا من C[] إلى PlayingCard*[]. بمعنى آخر، عندما ننشئ نسخةً جديدةً من Deck باستخدام NewPlayingCardDeck، فإننا نُنشئ Deck يمكنه تخزين أي نوع محدد من PlayingCard*. هذا يتيح لنا استخدام مجموعة متنوعة من أنواع البطاقات في Deck بدلًا من الاعتماد على {}interface. بالتالي يمكننا الآن التعامل مباشرةً مع البطاقات بنوعها الفعلي بدلًا من الحاجة إلى استبدال الأنواع والتحويلات التي كنا نستخدمها مع {}interface في الإصدار السابق من Deck. بالمثل، عند إنشاء نسخة جديدة من Deck، يجب علينا أيضًا توفير النوع الذي يحل محل C. نستخدم عادةً {}Deck& لإنشاء مرجع جديد إلى Deck، ولكن بدلًا من ذلك يجب علينا تضمين النوع داخل الأقواس المعقوفة للحصول على {}[PlayingCard*]&. الآن بعد أن عدّلنا الأنواع الخاصة بنا لاستخدام الأنواع المُعمّمة، نُعدّل الدالة main للاستفادة منها: ... func main() { deck := NewPlayingCardDeck() fmt.Printf("--- drawing playing card ---\n") playingCard := deck.RandomCard() fmt.Printf("drew card: %s\n", playingCard) // Code removed fmt.Printf("card suit: %s\n", playingCard.Suit) fmt.Printf("card rank: %s\n", playingCard.Rank) } أزلنا هذه المرة بعض التعليمات، لأنه لم تعد هناك حاجة لتأكيد قيمة {}interface على أنها PlayingCard*. عندما عدّلنا التابع RandomCard في Deck لإعادة النوع C و NewPlayingCardDeck لإعادة [Deck[*PlayingCard*، نكون قد عدّلنا التابع RandomCard بجعله يعيد PlayingCard* بدلًا من {}interface. هذا يعني أن نوع playingCard هو PlayingCard* أيضًا بدلًا من interface{} ويمكننا الوصول إلى حقول Suit و Rank مباشرةً. لنُشغّل ملف البرنامج "main.go" بعد حفظه من خلال الأمر go run: $ go run main.go سنشاهد خرجًا مشابهًا لما سبق، ولكن البطاقة المسحوبة قد تكون مختلفة: --- drawing playing card --- drew card: 8 of Hearts card suit: Hearts card rank: 8 على الرغم من أن الخرج هو نفسه مثل الإصدار السابق من البرنامج عندما استخدمنا {}interface، إلا أن الشيفرة أنظف وتتجنب الأخطاء المحتملة. لم يعد هناك حاجةٌ لتأكيد النوع PlayingCard*، مما يُجنّبنا التعامل مع الأخطاء الإضافية، كما أنّه من خلال تحديد أن نسخة Deck يمكن أن تحتوي فقط على PlayingCard*، لن يكون هناك أي احتمال لوجود قيمة أخرى غير PlayingCard* تُضاف إلى مجموعة البطاقات. عدّلنا في هذا القسم البنية Deck لتكون نوعًا مُعمّمًا، مما يوفر مزيدًا من السيطرة على أنواع البطاقات التي يمكن أن تحتويها كل نسخة من مجموعة أوراق اللعب. عدّلنا أيضًا التابعين AddCard و RandomCard إما لقبول وسيط مُعمم أو لإرجاع قيمة مُعممة. عدّلنا أيضًا NewPlayingCardDeck لإرجاع Deck* يحتوي على بطاقات PlayingCard*. أخيرًا أزلنا التعامل مع الأخطاء في الدالة main لأنه لم يعد هناك حاجة لذلك. الآن بعد تعديل Deck ليكون مُعممًا، يمكننا استخدامه لاحتواء أي نوع من البطاقات التي نرغب. سنستفيد في القسم التالي من هذه المرونة من خلال إضافة نوع جديد من البطاقات إلى البرنامج. استخدام أنواع مختلفة مع الأنواع المعممة يمكننا -بإنشاء نوع مُعمّم مثل النوع Deck- استخدامه مع أي نوع آخر؛ فعندما نُنشئ نسخةً من Deck، ونرغب باستخدامها مع أنواع PlayingCard*، فإن الشيء الوحيد الذي نحتاجه هو تحديد هذا النوع عند إنشاء القيمة، وإذا أردنا استخدام نوع آخر، نُبدّل نوع PlayingCard* بالنوع الجديد الذي نرغب باستخدامه. نُنشئ في هذا القسم بنية بيانات جديدة تُسمى TradingCard لتمثيل نوع مختلف من البطاقات، ثم نُعدّل البرنامج لإنشاء Deck يحتوي على عدة TradingCard*. لإنشاء النوع TradingCard، نفتح ملف "main.go" مرةً أخرى ونضيف التعريف التالي: ... import ( ... ) type TradingCard struct { CollectableName string } func NewTradingCard(collectableName string) *TradingCard { return &TradingCard{CollectableName: collectableName} } func (tc *TradingCard) String() string { return tc.CollectableName } النوع TradingCard مشابه لنوع PlayingCard، ولكن بدلًا من وجود حقول Suit و Rank، يحتوي على حقل CollectableName لتتبع اسم بطاقة التداول، كما يتضمن دالة الباني NewTradingCard والتابع String، أي بطريقة مشابهة للنوع PlayingCard. نُنشئ الآن الدالة البانية NewTradingCardDeck لإنشاء Deck مُعبأة بقيم TradingCards*: ... func NewPlayingCardDeck() *Deck[*PlayingCard] { ... } func NewTradingCardDeck() *Deck[*TradingCard] { collectables := []string{"Sammy", "Droplets", "Spaces", "App Platform"} deck := &Deck[*TradingCard]{} for _, collectable := range collectables { deck.AddCard(NewTradingCard(collectable)) } return deck } عند إنشاء أو إرجاع Deck* في هذه الحالة، فإننا نُبدّل قيم PlayingCard* بقيم TradingCard*، وهذا هو الأمر الوحيد الذي نحتاج إلى تغييره في Deck. هناك مجموعة من البطاقات الخاصة، والتي يمكننا المرور عليها لإضافة كل TradingCard* إلى Deck. يعمل التابع AddCard في Deck بنفس الطريقة كما في السابق، لكن هذه المرة يقبل قيمة TradingCard* من NewTradingCard. إذا حاولنا تمرير قيمة من NewPlayingCard، سيُعطي المُصرّف خطأ لأنه يتوقع TradingCard* وليس PlayingCard*. نُعدّل الدالة main لإنشاء Deck جديدة من أجل عدة TradingCard*، وسحب بطاقة عشوائية باستخدام RandomCard، وطباعة معلومات البطاقة: ... func main() { playingDeck := NewPlayingCardDeck() tradingDeck := NewTradingCardDeck() fmt.Printf("--- drawing playing card ---\n") playingCard := playingDeck.RandomCard() ... fmt.Printf("card rank: %s\n", playingCard.Rank) fmt.Printf("--- drawing trading card ---\n") tradingCard := tradingDeck.RandomCard() fmt.Printf("drew card: %s\n", tradingCard) fmt.Printf("card collectable name: %s\n", tradingCard.CollectableName) } أنشأنا مجموعة جديدة من بطاقات التداول باستخدام NewTradingCardDeck وخزّناها في tradingDeck. نظرًا لأننا لا نزال نستخدم نفس النوع Deck كما كان من قبل، يمكننا استخدام RandomCard للحصول على بطاقة تداول عشوائية من Deck وطباعة البطاقة. يمكننا أيضًا الإشارة إلى وطباعة حقل CollectableName مباشرةً من tradingCard لأن Deck المُعمّم الذي نستخدمه قد عرّف C على أنه TradingCard*. يُظهر هذا التحديث أيضًا قيمة استخدام الأنواع المُعمّمة. لدعم نوع بطاقة جديد تمامًا، لم نحتاج إلى تغيير Deck إطلاقًا، فمن خلال معاملات النوع في Deck تمكّننا من تحديد نوع البطاقة الذي نريده، وبدءًا من هذه النقطة فصاعدًا، يُستخدم النوع TradingCard* بدلًا من النوع PlayingCard* في أية تفاعلات مع قيم Deck. لنُشغّل ملف البرنامج "main.go" بعد حفظه من خلال الأمر go run: $ go run main.go ليكون الخرج على النحو التالي: --- drawing playing card --- drew card: Q of Diamonds card suit: Diamonds card rank: Q --- drawing trading card --- drew card: App Platform card collectable name: App Platform يجب أن نرى نتيجةً مشابهة للنتيجة الموضحة أعلاه (مع بطاقات مختلفة بسبب العشوائية) بعد انتهاء تشغيل البرنامج. تظهر بطاقتين مسحوبتين: البطاقة الأصلية للّعب وبطاقة التداول الجديدة المُضافة. أضفنا في هذا القسم النوع TradingCard الجديد لتمثيل نوع بطاقة مختلف عن نوع البطاقة الأصلية PlayingCard. بمجرد إضافة نوع البطاقة TradingCard،أنشأنا الدالة NewTradingCardDeck لإنشاء وملء مجموعة ورق اللعب ببطاقات التداول. أخيرًا عدّلنا الدالة main لاستخدام مجموعة ورق التداول الجديدة وطباعة معلومات مُتعلقة بالبطاقة العشوائية المستخرجة. بصرف النظر عن إنشاء دالة جديدة NewTradingCardDeck لملء الدستة ببطاقات مختلفة، لم نحتاج إلى إجراء أي تحديثات أخرى على مجموعة ورق اللعب لدعم نوع بطاقة جديد تمامًا. هذه هي قوة الأنواع المُعمّمة؛ إذ يمكننا كتابة الشيفرة مرةً واحدةً فقط واستخدامها متى أردنا لأنواع مختلفة من البيانات المماثلة أيضًا. هناك مشكلة واحدة في Deck الحالي، وهي أنه يمكن استخدامه لأي نوع، بسبب التصريح C any الذي لدينا. قد يكون هذا هو ما نريده حتى نتمكن من إنشاء مجموعة ورق لعب للقيم int بكتابة {}[Deck[int&، ولكن إذا كنا نرغب في أن يحتوي Deck فقط على بطاقات، سنحتاج إلى طريقة لتقييد أنواع البيانات المسموح بها مع C. القيود على الأنواع المعممة غالبًا لا نرغب أو نحتاج إلى أي قيود على الأنواع المستخدمة في الأنواع المُعمّمة لأننا قد لا نهتم بنوع البيانات المُستخدمة، لكن قد نحتاج في أحيان أخرى إلى القدرة على تقييد الأنواع المستخدمة بواسطة الأنواع المُعمّمة. على سبيل المثال، إذا كُنّا نُنشئ نوعًا مُعمّمًا Sorter، قد نرغب في تقييد أنواعه المُعمّمة لتلك التي تحتوي على تابع Compare (أي تقييد الأنواع المستخدمة معه، بحيث تكون فقط الأنواع القابلة للمقارنة)، حتى يتمكن Sorter من مقارنة العناصر التي يحتوي عليها. بدون هذا القيد، فقد لا تحتوي القيم على التابع Compare، ولن يعرف Sorter كيفية مقارنتها. نُنشئ في هذا القسم واجهةً جديدة تسمى Card، ثم نُعدّل Deck للسماح فقط بإضافة أنواع Card. لتطبيق التحديثات، افتح ملف "main.go" وضِف الواجهة Card: ... import ( ... ) type Card interface { fmt.Stringer Name() string } واجهة Card مُعرّفة بنفس طريقة تعريف أي واجهة أخرى في لغة جو؛ وليس هناك متطلبات خاصة لاستخدامها مع الأنواع المُعمّمة. نحن نقول في واجهة Card هذه، أنه لكي نعد شيئًا ما بطاقة Card، يجب أن يُحقق النوع fmt.Stringer (يجب أن يحتوي على تابع String الذي تحتويه البطاقات فعلًا)، ويجب أيضًا أن يحتوي على تابع Name الذي يعيد قيمة من نوع string. نُعدّل الآن أنواع TradingCard و PlayingCard لإضافة التابع Name الجديد، إضافةً إلى التابع String الموجود فعلًا، لكي نستوفي شروط تحقيق الواجهة Card: ... ... type TradingCard struct { ... } ... func (tc *TradingCard) Name() string { return tc.String() } ... type PlayingCard struct { ... } ... func (pc *PlayingCard) Name() string { return pc.String() } يحتوي كُلًا من TradingCard و PlayingCard فعليًا على تابع String الذي يُحقق الواجهة fmt.Stringer. لذا، لتحقيق الواجهة Card، نحتاج فقط إلى إضافة التابع الجديد Name. بما أن fmt.Stringer يمكنها إرجاع أسماء البطاقات، يمكننا ببساطة إرجاع نتيجة التابع String في التابع Name. نُعدّل الآن Deck، بحيث يُسمح فقط باستخدام أنواع Card مع C: ... type Deck[C Card] struct { cards []C } كان لدينا C any قبل هذا التحديث مثل قيد نوع type constraint والمعروف أيضًا بتقييد النوع type restriction، وهو ليس قيدًا صارمًا. بما أن any هو اسم {}interface البديل (له نفس المعنى)، فقد سمح باستخدام أي نوع في جو بمثابة قيمة للمعامل C. الآن بعدما عوّضنا any بالواجهة Card الجديدة، سيتحقق المُصرّف في جو أن أي نوع مستخدم مع C يُحقق واجهة Card عند تصريف البرنامج. يمكننا الآن بعد وضع هذا القيد، استخدام أي توابع تقدمها الواجهة Card داخل توابع النوع Deck. إذا أردنا من RandomCard طباعة اسم البطاقة المسحوبة، ستتمكن من الوصول إلى التابع Name لأنها جزء من الواجهة Card. سنرى ذلك في الجزء التالي. هذه التحديثات القليلة هي التحديثات الوحيدة التي نحتاج إليها لتقييد النوع Deck، بحيث يستخدم فقط لقيم Card. لنُشغّل ملف البرنامج "main.go" بعد حفظه من خلال الأمر go run: $ go run main.go سيكون الخرج: --- drawing playing card --- drew card: 5 of Clubs card suit: Clubs card rank: 5 --- drawing trading card --- drew card: Droplets card collectable name: Droplets نلاحظ أن الناتج لم يتغير من حيث الشكل العام، فنحن لم نُعدّل بالوظيفة الرئيسية للبرنامج؛ وضعنا فقط قيودًا على الأنواع، إذ أضفنا في هذا الجزء الواجهة Card الجديدة وعدّلنا كلًا من TradingCard و PlayingCard لتحقيق هذه الواجهة. عدّلنا أيضًا Deck بهدف تقييد الأنواع التي يتعامل معها، بحيث يكون النوع المستخدم يُحقق الواجهة Card. كل ما فعلناه إلى الآن هو إنشاء نوع بيانات جديد من خلال مفهوم البنى struct وجعله مُعمّمًا، لكن لغة جو تسمح أيضًا بإنشاء دوال مُعممة، إضافةً إلى إنشاء أنواع مُعممة. إنشاء دوال معممة يتبع إنشاء دوال مُعممة في لغة جو طريقة تصريح مشابه جدًا للأنواع المُعممة في لغة جو. يتطلب إنشاء دوال مُعممة إضافة مجموعة ثانية من المعاملات إلى تلك الدوال كما هو الحال في الأنواع المُعممة التي تحتوي على معاملات نوع. نُنشئ في هذا الجزء دالة مُعممة جديدة باسم printCard، ونستخدم تلك الدالة لطباعة اسم البطاقة المقدمة. نفتح ملف "main.go" ونجري التحديثات التالية: ... func printCard[C any](card C) { fmt.Println("card name:", card.Name()) } func main() { ... fmt.Printf("card collectable name: %s\n", tradingCard.CollectableName) fmt.Printf("--- printing cards ---\n") printCard[*PlayingCard](playingCard) printCard(tradingCard) } نلاحظ أن التصريح عن الدالة printCard مألوف من ناحية تعريف معاملات النوع (أقواس معقوفة تحتوي على معاملات النوع المعممة. تحدد هذه المعاملات النوع الذي سيجري استخدامه في الدالة)، تليها المعاملات العادية للدالة داخل قوسين، ثم في دالة main، تستخدم الدالة printCard لطباعة كل من PlayingCard* و TradingCard*. قد نلاحظ أن أحد استدعاءات printCard يتضمن معامل النوع [PlayingCard*]، بينما الاستدعاء الثاني لا يحتوي على نفس معامل النوع [TradingCard*]. يمكن لمُصرّف لغة جو أن يستدل على معامل النوع المقصود من القيمة التي نمررها إلى المعاملات، لذلك في حالات مثل هذه، تكون معاملات النوع اختيارية. يمكننا أيضًا إزالة معامل النوع [PlayingCard*]. لنُشغّل ملف البرنامج "main.go" بعد حفظه من خلال الأمر go run: $ go run main.go سيظهر هذه المرة خطأ في التصريف: # وسطاء سطر الأوامر ./main.go:87:33: card.Name undefined (type C has no field or method Name) عند استخدامنا للشيفرة المذكورة أعلاه وتصريفها تظهر رسالة الخطأ "card.Name undefined"، وتعني أن Name غير معرفة في النوع C. هذا يحدث لأننا استخدمنا any قيدًا للنوع في معامل النوع C في دالة printCard. تعني any أن أي نوع يمكن استخدامه مع C، ولكنه لا يعرف عنه أي شيء محدد مثل وجود التابع Name في النوع. لحل هذه المشكلة والسماح بالوصول إلى Name، يمكننا استخدام الواجهة Card قيدًا للنوع في C. تحتوي الواجهة Card على التابع Name ونُطبقها في النوعين TradingCard و PlayingCard. وبذلك تضمن لنا لغة جو أن يكون لدى C التابع Name وبالتالي يتيح لنا استخدامها في الدالة printCard دون وجود أخطاء في وقت التصريف. نُعدّل ملف "main.go" لآخر مرة لاستبدال القيد any بالقيد Card: ... func printCard[C Card](card C) { fmt.Println("card name:", card.Name()) } لنُشغّل ملف البرنامج "main.go" بعد حفظه من خلال الأمر go run: $ go run main.go سيكون الخرج: --- drawing playing card --- drew card: 6 of Hearts card suit: Hearts card rank: 6 --- drawing trading card --- drew card: App Platform card collectable name: App Platform --- printing cards --- card name: 6 of Hearts card name: App Platform نلاحظ سحب البطاقتين كما اعتدنا سابقًا، ولكن تطبع الآن الدالة printCard البطاقات وتستخدم التابع Name للحصول على الاسم لتجري طباعته. أنشأنا في هذا القسم دالةً جديدة مُعممة printCard قادرة على أخذ أي قيمة Card وطباعة الاسم. رأينا أيضًا أن استخدام قيد النوع any بدلًا من Card أو قيمةً أخرى محددة يؤثر على التوابع المتاحة. الخاتمة أنشأنا في هذا المقال برنامجًا جديدًا يحتوي على بنية اسمها Deck تُمثّل نوعًا يمكن أن يُعيد بطاقة عشوائية من مجموعة ورق اللعب بصيغة {}interface، وأنشأنا النوع PlayingCard لتمثيل بطاقة اللعب في المجموعة، ثم عدّلنا النوع Deck لدعم مفهوم النوع المُعمّم وتمكنّا من إزالة بعض عمليات التحقق من الأخطاء لأن النوع المُعمّم يضمن عدم ظهور ذلك النوع من الأخطاء. أنشأنا بعد ذلك نوعًا جديدًا يسمى TradingCard لتمثيل نوع مختلف من البطاقات التي يمكن أن تدعمها Deck، وكذلك أنشأنا مجموعة ورق لعب لكل نوع من أنواع البطاقات وأرجعنا بطاقةً عشوائيةً من كل مجموعة. أضفنا أيضًا قيدًا للنوع إلى البنية Deck لضمان أنه يمكن إضافة أنواع تُحقق الواجهة Card فقط إلى مجموعة ورق اللعب. أنشأنا أخيرًا دالة مُعمّمة تسمى printCard يمكنها طباعة اسم أي قيمة من نوع Card باستخدام التابع Name. يمكن أن يُقلل استخدام الأنواع المُعمّمة في الشيفرة الخاصة بنا إلى حد كبير عدد الأسطر البرمجية اللازمة لدعم أنواع متعددة لنفس الشيفرة. يجب أن نُحقق التوازن بين الأداء وسهولة قراءة الشيفرة عند استخدام الأنواع المُعمّمة؛ إذ يؤثر استخدام الأنواع المُعمّمة على الأداء، لكنه يُسهّل القراءة. من جهة أخرى، استخدام الواجهات بدلًا من الأنواع المُعمّمة أفضل من ناحية الأداء، لكنه أسوأ في القراءة. بالتالي، إذا كان بإمكاننا استخدام الواجهة بدلًا من الأنواع المُعمّمة فمن الأفضل استخدام الواجهة. ترجمة -وبتصرف- للمقال How To Make HTTP Requests in Go لصاحبه Kristin Davidson. اقرأ أيضًا المقال السابق كيفية إنشاء طلبات HTTP في لغة جو Go. مقدمة إلى مفهوم الأنواع المعممة Generic Types في لغة Rust. مفهوم البرمجة المعممة Generic Programming
  12. يُعد بروتوكول HTTP الخيار الأفضل غالبًا عندما يحتاج المطورون إلى إنشاء اتصال بين البرامج. تقدم لغة جو (المعروفة بمكتبتها القياسية الشاملة) دعمًا قويًا لبروتوكول HTTP من خلال حزمة net/http الموجودة في المكتبة القياسية. لا تتيح هذه الحزمة إنشاء خوادم HTTP فحسب، بل تتيح أيضًا إجراء طلبات HTTP مثل عميل. سننشئ في هذه المقالة برنامجًا يتفاعل مع خادم HTTP من خلال إجراء أنواع مختلفة من الطلبات. سنبدأ بطلب GET باستخدام عميل HTTP الافتراضي في لغة جو، ثم سنعمل على تحسين البرنامج ليشمل طلب POST مع متن الطلب. أخيرًا نُخصص طلب POST من خلال دمج ترويسة HTTP وتحقيق آلية المهلة، للتعامل مع الحالات التي تتجاوز فيها مدة الطلب حدًا معينًا. المتطلبات الأولية لمتابعة هذا المقال التعليمي، سنحتاج إلى: إصدار مُثبّت من جو 1.16 أو أعلى، ويمكنك الاستعانة بمقال تثبيت لغة جو Go وإعداد بيئة برمجة محلية على أبونتو Ubuntu لإعداده. تثبيت لغة جو وإعداد بيئة برمجة محلية على نظام ماك macOS. تثبيت لغة جو وإعداد بيئة برمجة محلية على ويندوز. معرفة بكيفية إنشاء خادم HTTP في لغة جو. فهم لتنظيمات جو goroutines والقنوات channels. يمكنك الاطلاع على مقالة كيفية تشغيل عدة دوال على التساير في لغة جو Go. الإلمام بكيفية إنشاء طلبات HTTP وإرسالها (موصى به). تقديم طلب GET نناقش في هذه الفقرة كيفية تقديم طلب GET باستخدام حزمة net/http مثل عميل، إذ توفّر لنا هذه الحزمةطرقًا وخيارات متنوعة للتفاعل مع موارد HTTP. أحد الخيارات الشائعة هو استخدام عميل "HTTP عام" مع دوال مثل http.Get، التي تسمح بإنشاء طلب GET سريعًا باستخدام عنوان URL ومتن فقط. يمكننا أيضًا إنشاء http.Request للحصول على مزيد من التحكم وتخصيص جوانب معينة من الطلب. سنبدأ في هذا القسم بإنشاء برنامج أولي يستخدم http.Get لتقديم طلب HTTP، ونعدّله لاحقًا لاستخدام http.Request مع عميل HTTP الافتراضي. استخدام دالة http.Get لتقديم طلب نستخدم الدالة http.Get في الإصدار الأولي من البرنامج لإرسال طلب إلى خادم HTTP داخل البرنامج، إذ تُعد هذه الدالة مناسبة لأنها تتطلب الحد الأدنى من التهيئة، أي أنها لا تحتاج إلى الكثير من التفاصيل والإعدادات، وهذا ما يجعلها مثالية لتقديم طلب واحد مباشر. بالتالي، يكون استخدام الدالة http.Get هو الأسلوب الأنسب عندما نحتاج إلى تنفيذ طلب سريع لمرة واحدة فقط. كما هو معتاد، سنحتاج لبدء إنشاء برامجنا إلى إنشاء مجلد للعمل ووضع الملفات فيه، ويمكن وضع المجلد في أي مكان على الحاسب، إذ يكون للعديد من المبرمجين عادةً مجلدٌ يضعون داخله كافة مشاريعهم. سنستخدم في هذا المقال مجلدًا باسم "projects"، لذا فلننشئ هذا المجلد وننتقل إليه: $ mkdir projects $ cd projects ننشئ مجلدًا للمشروع وننتقل إليه. لنسميه مثلًا "httpclient": $ mkdir httpclient $ cd httpclient نستخدم الآن محرر نانو nano أو أي محرر آخر تريده لفتح ملف "main.go": $ nano main.go نضع بداخله الشيفرة التالية: package main import ( "errors" "fmt" "net/http" "os" "time" ) const serverPort = 3333 // ضبط منفذ الخادم على 3333 أضفنا في الشيفرة السابقة الحزمة main لضمان إمكانية تصريف البرنامج وتنفيذه. نستورد أيضًا عدة حزم لاستخدامها في البرنامج. نُعّرف ثابت const اسمه serverPort بقيمة 3333، سيجري استخدامه مثل منفذ لخادم HTTP والعميل. ننشئ دالة main ضمن الملف "main.go" ونبدأ بإعداد خادم HTTP مثل تنظيم goroutine: func main() { // مثل تنظيم جو HTTP نبدأ تشغيل خادم go func() { // نُنشئ جهاز توجيه جديد mux := http.NewServeMux() // معالجة مسار الجذر "/" وطباعة معلومات الطلب mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { fmt.Printf("Server: %s /\n", r.Method) }) // HTTP تهيئة خادم server := http.Server{ Addr: fmt.Sprintf(":%d", serverPort), Handler: mux, } // بدء تشغيل الخادم، ومعالجة الأخطاء المحتملة if err := server.ListenAndServe(); err != nil { if !errors.Is(err, http.ErrServerClosed) { fmt.Printf("Error running HTTP server: %s\n", err) } } }() // الانتظار لمدة قصيرة للسماح للخادم بالبدء time.Sleep(100 * time.Millisecond) } تعمل الدالة main بمثابة نقطة دخول للبرنامج، ونستخدم الكلمة المفتاحية go للإشارة إلى أن خادم HTTP سيجري تشغيله ضمن تنظيم جو goroutine. نعالج مسار الجذر /، ونطبع معلومات الطلب باستخدام fmt.Printf، ونُهيّئ خادم HTTP بالعنوان والمعالج المحددين. يبدأ الخادم بالاستماع إلى الطلبات باستخدام الدالة ListenAndServe، ويجري التعامل مع أية أخطاء محتملة؛ فإذا حدث خطأ ولم يكن هذا الخطأ هو http.ErrServerClosed (إغلاق طبيعي تحدثنا عنه في المقال السابق)، ستُطبع رسالة خطأ. نستخدم الدالة time.Sleep للسماح للخادم بوقت كافٍ لبدء التشغيل قبل تقديم الطلبات إليه. نجري الآن بعض التعديلات الإضافية على الدالة mian من خلال إعداد عنوان URL للطلب، وذلك بدمج اسم المضيف http://localhost مع قيمة serverPort باستخدام fmt.Sprintf. نستخدم بعد ذلك الدالة http.Get لتقديم طلب إلى عنوان URL هذا: ... requestURL := fmt.Sprintf("http://localhost:%d", serverPort) res, err := http.Get(requestURL) if err != nil { fmt.Printf("error making http request: %s\n", err) os.Exit(1) } fmt.Printf("client: got response!\n") fmt.Printf("client: status code: %d\n", res.StatusCode) } يرسل البرنامج طلب HTTP باستخدام عميل HTTP الافتراضي إلى عنوان URL المحدد عند استدعاء http.Get، ويعيد http.Response في حالة نجاح الطلب أو ظهور قيمة خطأ في حالة فشل الطلب. في حال حدوث خطأ، يطبع البرنامج رسالة الخطأ ويخرج من البرنامج باستخدام os.Exit مع شيفرة خطأ 1. إذا نجح الطلب، يطبع البرنامج رسالةً تشير إلى تلقي استجابة، جنبًا إلى جنب مع شيفرة حالة HTTP للاستجابة. لنُشغّل ملف البرنامج "main.go" بعد حفظه من خلال الأمر go run: $ go run main.go سيكون الخرج على النحو التالي: server: GET / client: got response! client: status code: 200 يشير السطر الأول من الخرج إلى أن الخادم تلقى طلب GET من العميل على المسار /. يشير السطران التاليان إلى أن العميل قد تلقى استجابة من الخادم بنجاح وأن شيفرة حالة الاستجابة كان 200. على الرغم من أن الدالة http.Get ملائمة لإجراء طلبات HTTP سريعة مثل تلك الموضحة في هذا القسم، لكن استخدامhttp.Request يوفر نطاقًا أوسع من الخيارات لتخصيص الطلب. استخدام دالة http.Request لتقديم طلب توفّر لنا http.Request مزيدًا من التحكم في الطلب أكثر من مجرد استخدام تابع HTTP (على سبيل المثال، GET و POST) وعنوان URL فقط. على الرغم من أننا لن نستخدم ميزات إضافية في الوقت الحالي، يسمح لنا استخدام http.Request بإضافة تخصيصات في أقسام لاحقة من هذا المقال. يتضمن التحديث الأولي تعديل معالج خادم HTTP لإرجاع استجابة تتضمّن بيانات جسون JSON وهمية باستخدام fmt.Fprintf. تُنشأ هذه البيانات ضمن خادم HTTP مكتمل باستخدام حزمة encoding/json. لمعرفة المزيد حول العمل مع بيانات جسون في لغة جو، يمكن الرجوع إلى المقال "كيفية استخدام جسون JSON في لغة جو". نحتاج أيضًا إلى استيراد io/ioutil لاستخدامه في تحديث لاحق. نفتح ملف "main.go" لتضمينhttp.Request كما هو موضح أدناه: package main import ( ... "io/ioutil" ... ) ... func main() { ... mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { fmt.Printf("server: %s /\n", r.Method) fmt.Fprintf(w, `{"message": "hello!"}`) }) ... هدفنا هو استبدال استخدام http.Get، من خلال استخدام كل من الدالة http.NewRequest من الحزمة net/http -لإنشاء http.Request جديد، والذي يمثل طلب HTTP يمكن إرساله إلى الخادم - و http.DefaultClient ليكون عميل HTTP افتراضي يوفر لنا استخدام التابع Do لإرسال http.Request إلى الخادم واسترداد http.Response المقابل. يسمح هذا التغيير بمزيد من التحكم في الطلب وإجراء تعديلات عليه قبل الإرسال إلى الخادم: ... requestURL := fmt.Sprintf("http://localhost:%d", serverPort) req, err := http.NewRequest(http.MethodGet, requestURL, nil) if err != nil { fmt.Printf("client: could not create request: %s\n", err) os.Exit(1) } res, err := http.DefaultClient.Do(req) if err != nil { fmt.Printf("client: error making http request: %s\n", err) os.Exit(1) } fmt.Printf("client: got response!\n") fmt.Printf("client: status code: %d\n", res.StatusCode) resBody, err := ioutil.ReadAll(res.Body) if err != nil { fmt.Printf("client: could not read response body: %s\n", err) os.Exit(1) } fmt.Printf("client: response body: %s\n", resBody) } نبدأ باستخدام الدالة http.NewRequest لإنشاء قيمة http.Request. نحدد تابع HTTP للطلب على أنه GET باستخدام http.MethodGet. نُنشئ أيضًا عنوان URL للطلب من خلال دمج اسم المضيف http://localhost مع قيمة serverPort باستخدام fmt.Sprintf. نفحص أيضًا متن الطلب إذا كان فارغًا (أي قيمة nil) لنتعامل أيضًا مع أي خطأ محتمل قد يحدث أثناء إنشاء الطلب. بخلاف http.Get الذي يرسل الطلب على الفور، فإنhttp.NewRequest يُجهّز الطلب فقط ولا يرسله مباشرةً، ويتيح لنا ذلك تخصيص الطلب كما نريد قبل إرساله فعليًا. بمجرد إعداد http.Request، نستخدم(http.DefaultClient.Do (req لإرسال الطلب إلى الخادم باستخدام عميل HTTP الافتراضي http.DefaultClient. يبدأ التابع Do الطلب ويعيد الاستجابة المستلمة من الخادم مع أي خطأ محتمل. بعد الحصول على الرد نطبع معلومات عنه، إذ نعرض أن العميل قد تلقى استجابة بنجاح، إلى جانب شيفرة حالة HTTP للاستجابة. نقرأ بعد ذلك متن استجابة HTTP باستخدام الدالة ioutil.ReadAll. يُمثّل متن الاستجابة على أنه io.ReadCloser، والذي يجمع بين io.Reader و io.Closer. نستخدم ioutil.ReadAll لقراءة جميع البيانات من متن الاستجابة حتى النهاية أو حدوث خطأ. تُعيد الدالة البيانات بقيمة byte[]، والتي نطبعها مع أي خطأ مصادف. لنُشغّل ملف البرنامج "main.go" بعد حفظه من خلال الأمر go run: $ go run main.go الخرج مشابه لما سبق مع إضافة بسيطة: server: GET / client: got response! client: status code: 200 client: response body: {"message": "hello!"} يُظهر السطر الأول أن الخادم لا يزال يتلقى طلب GET إلى المسار /. يتلقى العميل استجابة من الخادم برمز الحالة 200. يقرأ العميل متن استجابة الخادم ويطبعها أيضًا، والتي تكون في هذه الحالة {" message ":" hello! "}. يمكننا معالجة استجابة جسون هذه أيضًا باستخدام حزمة encoding/json، لكن لن ندخل في هذه التفاصيل الآن. طورّنا في هذا القسم برنامج باستخدام خادم HTTP وقدمنا طلبات HTTP إليه باستخدام طرق مختلفة. استخدمنا في البداية الدالة http.Get لتنفيذ طلب GET للخادم باستخدام عنوان URL الخاص بالخادم فقط. حدّثنا بعد ذلك البرنامج لاستخدام http.NewRequest لإنشاء قيمة http.Request، ثم استخدمنا التابع Do لعميل HTTP الافتراضيhttp.DefaultClient، لإرسال الطلب وطباعة متن الاستجابة. تُعد طلبات GET مفيدةً لاسترداد المعلومات من الخادم، لكن بروتوكول HTTP يوفر طرقًا أخرى متنوعة للاتصال بين البرامج. إحدى هذه الطرق هي طريقة POST، والتي تتيح لك إرسال معلومات من برنامجك إلى الخادم. إرسال طلب POST يُستخدم طلب GET في واجهة برمجة تطبيقات REST، فقط لاسترداد المعلومات من الخادم، لذلك لكي يُشارك البرنامج بالكامل في REST، يحتاج أيضًا إلى دعم إرسال طلبات POST، وهو عكس طلب GET تقريبًا، إذ يرسل العميل البيانات إلى الخادم داخل متن الطلب. سنُعدّل في هذا القسم البرنامج لإرسال طلب POST بدلُا من طلب GET، إذ سيتضمن طلب POST متنًا للطلب، وسنُعدّل الخادم لتوفير معلومات أكثر تفصيلًا حول الطلبات الواردة من العميل. نفتح ملف "main.go" ونُضمّن الحزم الإضافية التالية: ... import ( "bytes" "errors" "fmt" "io/ioutil" "net/http" "os" "strings" "time" ) ... ثم نُعدّل دالة المعالجة لطباعة معلومات متنوعة حول الطلب الوارد، مثل قيم سلسلة الاستعلام وقيم الترويسة ومتن الطلب: ... mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { fmt.Printf("server: %s /\n", r.Method) fmt.Printf("server: query id: %s\n", r.URL.Query().Get("id")) fmt.Printf("server: content-type: %s\n", r.Header.Get("content-type")) fmt.Printf("server: headers:\n") for headerName, headerValue := range r.Header { fmt.Printf("\t%s = %s\n", headerName, strings.Join(headerValue, ", ")) } reqBody, err := ioutil.ReadAll(r.Body) if err != nil { fmt.Printf("server: could not read request body: %s\n", err) } fmt.Printf("server: request body: %s\n", reqBody) fmt.Fprintf(w, `{"message": "hello!"}`) }) ... أضفنا في هذا التحديث لمعالج طلب HTTP الخاص بالخادم تعليمات fmt.Printf لتوفير مزيد من المعلومات حول الطلب الوارد. يُستخدم التابع r.URL.Query ().Get لاسترداد قيمة سلسلة الاستعلام المسماة id. يُستخدم التابع r.Header.Get للحصول على قيمة الترويسة content-type. تتكرر حلقة for معr.Header فوق كل ترويسة HTTP يستقبلها الخادم وتطبع اسمها وقيمتها. يمكن أن تكون هذه المعلومات ذات قيمة لأغراض استكشاف الأخطاء وإصلاحها في حالة ظهور أي مشكلات مع سلوك العميل أو الخادم. تُستخدم أيضًا الدالة ioutil.ReadAll لقراءة متن الطلب من r.Body. بعد تحديث دالة المعالجة للخادم، نُعدّل شيفرة الطلب في الدالة main، بحيث ترسل طلب POST مع متن الطلب: ... time.Sleep(100 * time.Millisecond) jsonBody := []byte(`{"client_message": "hello, server!"}`) bodyReader := bytes.NewReader(jsonBody) requestURL := fmt.Sprintf("http://localhost:%d?id=1234", serverPort) req, err := http.NewRequest(http.MethodPost, requestURL, bodyReader) ... في هذا التعديل قُدّم متغيران جديدان: يمثل الأول jsonBody بيانات جسون مثل قيم من النوع byte[] بدلًا من سلسلة string. يُستخدم هذا التمثيل لأنه عند ترميز بيانات جسون باستخدام حزمة encoding/json، فإنها تُرجع byte[] بدلًا من سلسلة. المتغير الثاني bodyReader، هو bytes.Reader مُغلّف ببيانات jsonBody. يتطلب http.Request أن يكون متن الطلب من النوع io.Reader. نظرًا لأن قيمة jsonBody هي byte[] ولا تُحقق io.Reader مباشرةً، يُستخدم bytes.Reader لتوفير واجهة io.Reader الضرورية، مما يسمح باستخدام قيمة jsonBody على أنها متن الطلب. نُعدّل أيضًا المتغير requestURL لتضمين قيمة سلسلة الاستعلام id = 1234، وذلك لتوضيح كيف يمكن تضمين سلسلة استعلام في عنوان URL للطلب إلى جانب مكونات عنوان URL القياسية الأخرى. أخيرًا نُعدّل استدعاء الدالة http.NewRequest لاستخدام التابع POST مع http.MethodPost، وضبط متن الطلب على bodyReader، وهو قارئ بيانات جسون. لنُشغّل ملف البرنامج "main.go" بعد حفظه من خلال الأمر go run: $ go run main.go سيكون الخرج بالتأكيد أطول من السابق، بسبب طباعة معلومات إضافية: server: POST / server: query id: 1234 server: content-type: server: headers: Accept-Encoding = gzip User-Agent = Go-http-client/1.1 Content-Length = 36 server: request body: {"client_message": "hello, server!"} client: got response! client: status code: 200 client: response body: {"message": "hello!"} يُظهر الخرج السلوك المُحدّث للخادم والعميل بعد إجراء التغييرات لإرسال طلب POST؛ إذ يشير السطر الأول إلى أن الخادم قد تلقى طلب POST للمسار /؛ ويعرض السطر الثاني قيمة سلسلة الاستعلام id، والتي تحمل القيمة 1234 في هذا الطلب؛ بينما يعرض السطر الثالث قيمة ترويسة Content-Type، وهي فارغة في هذه الحالة. قد يختلف ترتيب الترويسات المطبوعة من r.Headers أثناء التكرار عليها باستخدام range في كل مرة نُشغّل فيها البرنامج، فبدءًا من إصدار جو 1.12، لا يمكن ضمان أن يكون الترتيب الذي يجري فيه الوصول إلى العناصر هو نفس الترتيب الذي تُدرج في العناصر في الرابط map، وهذا لأن لغة جو تُحقق الروابط باستخدام بنية لا تحافظ على ترتيب الإدراج، لذلك قد يختلف ترتيب طباعة الترويسات عن الترتيب الذي جرى استلامها به فعليًا في طلب HTTP. الترويسات الموضحة في المثال هي Accept-Encoding و User-Agent و Content-Length. قد نرى أيضًا قيمة مختلفة للترويسة User-Agent عما رأيناه أعلاها اعتمادًا على إصدار جو المُستخدم. يعرض السطر التالي متن الطلب الذي استلمه الخادم، وهو بيانات جسون التي يرسلها العميل. يمكن للخادم بعد ذلك استخدام حزمة encoding/json لتحليل بيانات جسون هذه التي أرسلها العميل وصياغة استجابة. تمثل الأسطر التالية خرج العميل، مما يشير إلى أنه تلقى استجابةً مع رمز الحالة 200. يحتوي متن الاستجابة المعروض في السطر الأخير على بيانات جسون أيضًا. أجرينا في القسم السابق العديد من التحديثات لتحسين البرنامج. أولًا، استبدلنا طلب GET بطلب POST، مما يتيح للعميل إرسال البيانات إلى الخادم في متن الطلب، إذ أنجزنا هذا عن طريق تحديث شيفرة الطلب وتضمين متن الطلب باستخدام byte[]. عدّلنا أيضًا دالة معالجة الطلب الخاصة بالخادم لتوفير معلومات أكثر تفصيلًا حول الطلبات الواردة، مثل قيمة سلسلة الاستعلام id وترويسة Content-Type وجميع الترويسات المستلمة من العميل. تجدر الإشارة إلى أنه في الخرج المعروض، لم تكن ترويسة Content-Type موجودة في طلب HTTP، مما يشير عادةً إلى نوع المحتوى المُرسل في المتن. سنتعلم في القسم التالي المزيد عن كيفية تخصيص طلب HTTP، بما في ذلك ضبط ترويسة Content-Type لتحديد نوع البيانات المُرسلة. تخصيص طلب HTTP يسمح تخصيص طلب HTTP بنقل مجموعة واسعة من أنواع البيانات بين العملاء والخوادم. كان بإمكان عملاء HTTP سابقًا افتراض أن البيانات المستلمة من خادم HTTP هي HTML غالبًا، أما اليوم يمكن أن تشمل البيانات تنسيقات مختلفة، مثل جسون والموسيقى والفيديو وغيرهم. لنقل معلومات إضافية متعلقة بالبيانات المرسلة، يشتمل بروتوكول HTTP على العديد من الترويسات، أهمها الترويسة Content-Type، التي تُخبر الخادم (أو العميل، اعتمادًا على اتجاه البيانات) بكيفية تفسير البيانات التي يتلقاها. سنعمل في القسم التالي على تحسين البرنامج عن طريق ضبط الترويسة Content-Type في طلب HTTP للإشارة إلى أن الخادم يجب أن يتوقع بيانات جسون. ستتاح لنا الفرصة أيضًا لاستخدام عميل HTTP مخصص بدلًا من http.DefaultClient الافتراضي، مما يتيح لنا تخصيص إرسال الطلب وفقًا للمتطلبات المحددة. نفتح ملف "main.go" لإجراء التعديلات الجديدة على الدالة main: ... req, err := http.NewRequest(http.MethodPost, requestURL, bodyReader) if err != nil { fmt.Printf("client: could not create request: %s\n", err) os.Exit(1) } req.Header.Set("Content-Type", "application/json") client := http.Client{ Timeout: 30 * time.Second, } res, err := client.Do(req) if err != nil { fmt.Printf("client: error making http request: %s\n", err) os.Exit(1) } ... بدايةً، جرى النفاذ إلى ترويسات http.Request باستخدام req.Header وضبطنا ترويسة Content-Type على application/json، إذ يُمثّل ضبط هذه الترويسة إشارةً للخادم إلى أن البيانات الموجودة في متن الطلب مُنسّقة بتنسيق جسون، وهذا يساعد الخادم في تفسير متن الطلب ومعالجته بطريقة صحيحة. أنشأنا أيضًا نسخة خاصة من البنية http.Client في المتغير client. يتيح لنا هذا مزيدًا من التحكم في سلوك العميل. يمكننا في هذه الحالة ضبط قيمة المهلة Timeout للعميل على 30 ثانية؛ وهذا يعني أنه إذا لم يجري تلقي الرد في غضون 30 ثانية، سيستسلم العميل ويتوقف عن الانتظار. هذا مهم لمنع البرنامج من إهدار الموارد بإبقاء الاتصالات مفتوحة إلى أجل غير مسمى. من خلال إنشاء http.Client خاص بنا مع مهلة محددة، فإننا تتأكد من أن البرنامج لديه حد محدد للمدة التي سينتظرها الرد، ويختلف هذا عن استخدام http.DefaultClient الافتراضي، والذي لا يحدد مهلة وسينتظر إلى أجل غير مسمى. لذلك يعد تحديد المهلة أمرًا بالغ الأهمية لإدارة الموارد بفعالية وتجنب مشكلات الأداء المحتملة. أخيرًا عدّلنا الطلب لاستخدام التابع Do من المتغير client. هذا التغيير واضح ومباشر لأننا كنا نستخدم التابع Do في جميع أنحاء البرنامج، سواء مع العميل الافتراضي أو العميل المخصص. الاختلاف الوحيد الآن هو أننا أنشأنا صراحةً متغيرًا من http.Client. توفر هذه التحديثات مزيدًا من التحكم والمرونة لبرنامجنا، مما يسمح بضبط ترويسات محددة وإدارة المهلات الزمنية بالطريقة المناسبة. من خلال فهم وتخصيص هذه الجوانب لطلب HTTP، يمكننا ضمان الأداء الأمثل والتواصل الموثوق مع الخادم. لنُشغّل ملف البرنامج "main.go" بعد حفظه من خلال الأمر go run: $ go run main.go يجب أن تكون مخرجاتك مشابهة جدًا للإخراج السابق ولكن مع مزيد من المعلومات حول نوع المحتوى: server: POST / server: query id: 1234 server: content-type: application/json server: headers: Accept-Encoding = gzip User-Agent = Go-http-client/1.1 Content-Length = 36 Content-Type = application/json server: request body: {"client_message": "hello, server!"} client: got response! client: status code: 200 client: response body: {"message": "hello!"} يمكننا ملاحظة أن الخادم قد استجاب بقيمة الترويسة Content-Type المُرسلة من قِبل العميل وهي من النوع application/json، ونلاحظ القيمة content-type وهي أيضًا application/json. يسمح وجود Content-Type بالمرونة في التعامل مع أنواع مختلفة من واجهات برمجة التطبيقات في وقت واحد، إذ يمكّن الخادم من التمييز بين الطلبات التي تحتوي على بيانات جسون والطلبات التي تحتوي على بيانات XML. يصبح هذا التمييز مهمًا خاصةً عند التعامل مع واجهات برمجة التطبيقات التي تدعم تنسيقات بيانات مختلفة. لفهم كيفية عمل مهلة العميل Timeout، يمكننا تعديل الشيفرة لمحاكاة سيناريو يستغرق فيه الطلب وقتًا أطول من المهلة المحددة، وذلك لكي نتمكن من ملاحظة تأثير المهلة. يمكننا إضافة استدعاء دالة time.Sleep ضمن دالة المعالجة لخادم HTTP، إذ تؤدي هذه الدالة إلى تأخير مُصطنع في استجابة الخادم. للتأكد من أن التأخير يتجاوز قيمة المهلة التي حددناها، نجعل time.Sleep يستمر لمدة 35 ثانية، وبالتالي سيظهر الخادم وكأنه غير مستجيب، مما يتسبب في انتظار العميل للاستجابة: ... func main() { go func() { mux := http.NewServeMux() mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { ... fmt.Fprintf(w, `{"message": "hello!"}`) time.Sleep(35 * time.Second) }) ... }() ... } لنُشغّل ملف البرنامج "main.go" بعد حفظه من خلال الأمر go run: $ go run main.go عند تنفيذ البرنامج مع إضافة (time.Sleep (35 * time.Second في دالة المعالجة، سيتغير سلوك العميل والخادم، وذلك بسبب التأخير المصطنع الذي حدث في استجابة الخادم. نلاحظ أيضًا أن البرنامج يستغرق وقتًا أطول للخروج مقارنةً بالسابق، وذلك لأن البرنامج ينتظر انتهاء طلب HTTP قبل الإنهاء. الآن مع الانتظار الجديد الذي أضفناه، لن يكتمل طلب HTTP حتى تصل الاستجابة أو نتجاوز المهلة المحددة البالغة 30 ثانية، ونظرًا لأن الخادم متوقف مؤقتًا لمدة 35 ثانية، متجاوزًا مدة المهلة، فسيلغي العميل الطلب بعد 30 ثانية، وستجري عملية معالجة خطأ المهلة الزمنية: server: POST / server: query id: 1234 server: content-type: application/json server: headers: Content-Type = application/json Accept-Encoding = gzip User-Agent = Go-http-client/1.1 Content-Length = 36 server: request body: {"client_message": "hello, server!"} client: error making http request: Post "http://localhost:3333?id=1234": context deadline exceeded (Client.Timeout exceeded while awaiting headers) exit status 1 نلاحظ تلقي الخادم طلب POST بطريقة صحيحة إلى المسار /، والتقط أيضًا معامل الاستعلام id بقيمة1234. حدد الخادم نوع محتوى الطلب content-type على أنه application/json بناءً على ترويسة نوع المحتوى، كما عرض الخادم الترويسات التي تلقاها. طبع الخادم أيضًا متن الطلب. نلاحظ حدوث خطأ context deadline exceeded من جانب العميل أثناء طلب HTTP كما هو متوقع. تشير رسالة الخطأ إلى أن الطلب تجاوز المهلة الزمنية البالغة 30 ثانية (الخادم تلقى الطلب وعالجه، لكنه بسبب وجود تعليمة انتظار من خلال استدعاء دالة time.Sleep، سيبدو وكأنه لم ينتهي من معالجته). بالتالي سيفشل استدعاء التابع client.Do، ويخرج البرنامج مع رمز الحالة 1 باستخدام (os.Exit (1. يسلط هذا الضوء على أهمية ضبط قيم المهلة الزمنية بطريقة مناسبة لضمان عدم تعليق الطلبات إلى أجل غير مسمى. أجرينا خلال هذا القسم تعديلات على البرنامج لتخصيص طلب HTTP من خلال إضافة ترويسة Content-Type إليه. عدّلنا البرنامج أيضًا من خلال إنشاء http.Client جديد مع مهلة زمنية 30 ثانية، والتي استخدمناها لاحقًا لتنفيذ طلب HTTP. فحصنا أيضًا تأثير المهلة من خلال استخدام التعليمة time.Sleep داخل معالج طلب HTTP. سمح لنا هذا بملاحظة أن استخدام عميل http.Client مع المهلات الزمنية المضبوطة بدقة أمرٌ بالغ الأهمية لمنع الطلبات من الانتظار إلى أجل غير مسمى. بالتالي اكتسبنا رؤى حول أهمية ضبط المهلات المناسبة لضمان معالجة الطلب بكفاءة ومنع المشكلات المحتملة مع الطلبات التي قد تظل خاملة. الخاتمة اكتسبنا خلال هذا المقال فهمًا شاملًا للعمل مع طلبات HTTP في لغة جو باستخدام حزمة net/http؛ إذ بدأنا بتقديم طلب GET إلى خادم HTTP باستخدام كل من دالة http.Get و http.NewRequest مع عميل HTTP الافتراضي؛ ثم وسّعنا بعد ذلك معرفتنا عن طريق إجراء طلب POST مع متن طلب باستخدام bytes.NewReader. تعلمنا كيفية تخصيص الطلب عن طريق ضبط ترويسة Content-Type باستخدام التابع Set في حقل ترويسة http.Request.اكتشفنا أهمية ضبط المهلات للتحكم في مدة الطلبات من خلال إنشاء عملي http.Client. تجدر الإشارة إلى أن حزمة net/http توفر دوال أكثر مما غطيناه في هذا المقال، فعلى سبيل المثال يمكن استخدام دالة http.Post لتقديم طلبات POST والاستفادة من ميزات مثل إدارة ملفات تعريف الارتباط. من خلال إتقان هذه المفاهيم، نكون قد زودنا أنفسنا بالمهارات الأساسية للتفاعل مع خوادم HTTP وإنشاء أنواع مختلفة من الطلبات باستخدام لغة جو. ترجمة -وبتصرف- للمقال How To Make HTTP Requests in Go لصاحبه Kristin Davidson. اقرأ أيضًا المقال السابق كيفية إنشاء خادم HTTP في لغة Go إنشاء طلبات HTTP وإرسالها كيفية استخدام صيغة JSON في لغة Go
  13. يكرّس العديد من المطورين جزءًا من وقتهم لبناء خوادم تُسهّل توزيع المحتوى عبر الإنترنت. يُعد بروتوكول النقل التشعبي Hypertext Transfer Protocol-أو اختصارًا HTTP- من أهم الوسائل المستخدمة لتوزيع المحتوى مهما كان نوع البيانات عبر الإنترنت. تتضمّن مكتبة لغة جو القياسية وظائفًا مدمجة لإنشاء خادم HTTP لتخديّم محتوى الويب، أو إنشاء طلبات HTTP للتواصل مع هذه الخوادم. سنتعلم في هذا المقال كيفية إنشاء خادم HTTP باستخدام مكتبة لغة جو القياسية، وكيفية توسيع وظائف الخادم لاستخراج البيانات من أجزاء مختلفة من طلب HTTP، مثل سلسلة الاستعلام والمتن Body وبيانات النموذج في الطلب. سنتعلّم أيضًا كيفية تعديل استجابة الخادم عن طريق إضافة ترويسات HTTP ورموز حالة مخصصة status codes، وبالتالي السماح للمطورين بتخصيص سلوك الخادم الخاص بهم. توضيح بعض المصطلحات: في سياق إنشاء خادم HTTP باستخدام مكتبة جو القياسية، تشير المصطلحات "سلسلة استعلام الطلب" و"المتن" و"بيانات النموذج" إلى أجزاء مختلفة من طلب HTTP. سلسلة الاستعلام: هي جزء من عنوان URL الذي يأتي بعد رمز علامة الاستفهام (?). يحتوي عادةً على أزواج ذات قيمة مفتاح مفصولة بعلامات &. المتن أو النص الأساسي: يحتوي متن طلب HTTP على البيانات التي يرسلها العميل إلى الخادم، مثل JSON أو XML أو النص العادي. يُستخدم بكثرة في طلبات من نوع POSTو PUT و PATCH لإرسال البيانات إلى الخادم. بيانات النموذج: تُرسل عادةً بيانات النموذج مثل جزء من طلب POST مع ضبط ترويسة "نوع المحتوى" على "application/x-www-form-urlencoded" أو "multipart/form-data". وهو يتألف من أزواج مفتاح - قيمة على غرار سلسلة الاستعلام، ولكن يُرسل في متن الطلب. بروتوكول HTTP هو بروتوكول يعمل على مستوى التطبيقات، ويستخدم لنقل مستندات الوسائط التشعبية، مثل صفحات HTML عبر الإنترنت. يعمل HTTP بمثابة أساس لاتصالات البيانات على شبكة الويب العالمية. يتبع HTTP نموذج خادم-العميل، إذ يرسل العميل (عادةً متصفح ويب) طلبًا إلى الخادم، ويستجيب الخادم بالمعلومات المطلوبة. تُستخدم بعض الدوال، مثل GET و POSTو PUT و DELETE لتسهيل عملية الاتصال بين العميل والخادم. يمكنك الاطلاع على مقال مدخل إلى HTTP على أكاديمية حسوب لمزيدٍ من المعلومات حول بروتوكول HTTP. المتطلبات الأولية لمتابعة هذا المقال التعليمي، سنحتاج إلى: إصدار مُثبّت من جو 1.16 أو أعلى، ويمكنك الاستعانة بمقال تثبيت لغة جو Go وإعداد بيئة برمجة محلية على أبونتو Ubuntu لإعداده. تثبيت لغة جو وإعداد بيئة برمجة محلية على نظام ماك macOS. تثبيت لغة جو وإعداد بيئة برمجة محلية على ويندوز. القدرة على استخدام أداة curl لإجراء طلبات ويب. إلمام بكيفية استخدام جسون JSON في لغة جو. معرفة بكيفية استخدام السياقات Contexts في لغة جو Go. فهم لتنظيمات جو goroutines والقنوات channels. يمكنك الاطلاع على مقالة كيفية تشغيل عدة دوال على التساير في لغة جو Go. الإلمام بكيفية إنشاء طلبات HTTP وإرسالها (موصى به). إعداد المشروع تتوفّر معظم وظائف HTTP التي تسمح لنا بإجراء الطلبات من خلال حزمة net/http الموجودة في المكتبة القياسية في لغة جو، بينما تتولى حزمة net بقية عمليات الاتصال بالشبكة. توفّر حزمة net/http أيضًا خادم HTTP يمكن استخدامه لمعالجة تلك الطلبات. سننشئ في هذا القسم برنامجًا يستخدم الدالة http.ListenAndServe لتشغيل خادم HTTP يستجيب للطلبات ذات المسارات / و hello/، ثم سنوسّع البرنامج لتشغيل خوادم HTTP متعددة في نفس البرنامج. كما هو معتاد، سنحتاج لبدء إنشاء برامجنا إلى إنشاء مجلد للعمل ووضع الملفات فيه، ويمكن وضع المجلد في أي مكان على الحاسب، إذ يكون للعديد من المبرمجين عادةً مجلدٌ يضعون داخله كافة مشاريعهم. سنستخدم في هذا المقال مجلدًا باسم "projects"، لذا فلننشئ هذا المجلد وننتقل إليه: $ mkdir projects $ cd projects الآن من داخل هذا المجلد، سنشغّل الأمر mkdir لإنشاء مجلد "httpserver" ثم سنستخدم cd للانتقال إليه: $ mkdir httpserver $ cd httpserver الآن، بعد أن أنشأنا مجلدًا للبرنامج وانتقلنا إليه، يمكننا البدء في تحقيق خادم HTTP. الاستماع إلى الطلبات وتقديم الردود يتضمّن مخدّم HTTP في لغة جو مكونين رئيسيين: المخدّم الذي يستمع إلى الطلبات القادمة من العميل الذي يرسل طلبات HTTP (عميل HTTP أو عملاء HTTP) ومُعالج طلبات (أو أكثر) يستجيب لتلك الطلبات. سنبدأ حاليًا في استخدام الدالة http.HandleFunc التي تخبر المُخدّم بالدالة التي يجب استدعاؤها لمعالجة الطلب، ثم سنستخدم الدالة http.ListenAndServe لتشغيل الخادم وإخباره بالتحضير للاستماع إلى طلب HTTP جديد وتخديمه من خلال معالجته بدوال المعالجة Handler functions التي نُنشئها مُسبقًا. بما أننا الآن داخل مجلد "httpserver"، يمكننا فتح ملف "main.go" باستخدام محرر نانو nano أو أي محرر آخر تريده: $ nano main.go سننشئ داخل هذا الملف دالتين getRoot و getHello سيمثلان دوال المعالجة الخاصة بنا، ثم سننشئ دالةً رئيسية main لاستخدامها في إعداد معالجات الطلبات من خلال الدالة http.HandleFunc، وذلك بتمرير المسار / الخاص بالدالة getRoot والمسار hello/ الخاص بالدالة getHello. بمجرد أن ننتهي من إعداد دوال المعالجة الخاصة بنا، يمكننا استدعاء http.ListenAndServe لبدء تشغيل المخدّم والاستماع للطلبات. دعنا نضيف التعليمات البرمجية التالية إلى الملف لبدء تشغيل البرنامج وإعداد المعالجات: package main import ( "errors" "fmt" "io" "net/http" "os" ) func getRoot(w http.ResponseWriter, r *http.Request) { fmt.Printf("got / request\n") io.WriteString(w, "This is my website!\n") } func getHello(w http.ResponseWriter, r *http.Request) { fmt.Printf("got /hello request\n") io.WriteString(w, "Hello, HTTP!\n") } أعددنا في البداية الحزمة الخاصة بالبرنامج package مع استيراد الحزم المطلوبة من خلال تعليمة import، كما أنشأنا الدالتين getRoot و getHello، ونلاحظ أن لهما نفس البصمة Signature، إذ تقبلان نفس الوسيطين، هما: قيمة http.ResponseWriter وقيمة http.Request*. هاتان الدالتان لهما بصمة تطابق النوع http.HandlerFunc، والذي يشيع استخدامه لتعريف دوال معالجة HTTP. عند تقديم طلب إلى الخادم، فإنه يُزوِّد هاتين القيمتين بمعلومات حول الطلب الحالي، ثم يستدعي دالة المعالج التي تتوافق مع هذه القيم. تُستخدم القيمة http.ResponseWriter (االمُسماة w) ضمن http.HandlerFunc للتحكّم بمعلومات الاستجابة التي يُعاد كتابتها إلى العميل الذي قدّم الطلب، مثل متن الاستجابة response body (جزء من استجابة HTTP ويحمل الحمولة الفعلية أو المعلومات التي طلبها العميل أو التي يوفرها الخادم) أو رموز الحالة status codes، وهي معلومات حول نتيجة الطلب والحالة الحالية للخادم، وهي جزء من بروتوكول HTTP يجري تضمينها في ترويسة الاستجابة، وتشير إلى ما إذا كان الطلب ناجحًا أو واجه خطأً أو يتطلب إجراءً إضافيًا. بعد ذلك، تُستخدم القيمة http.Request* (المسماة r) للحصول على معلومات حول الطلب الذي جاء إلى الخادم، مثل المتن المُرسل في حالة طلب POST أو معلومات حول العميل الذي أجرى الطلب. في كل من معالجات HTTP التي أنشأناها، يمكننا استخدام الدالة fmt.Printf للطباعة، وذلك عندما يأتي طلب لدالة المعالجة، ثم نستخدم http.ResponseWriter لإرسال نص ما إلى متن الاستجابة. http.ResponseWriter هي واجهة في حزمة http تمثل الاستجابة التي سترسل مرةً أخرى إلى العميل عند تقديم طلب إلى الخادم، وهي io.Writer مما يعني أنها توفر إمكانية كتابة البيانات. نستخدم http.ResponseWriterفي الشيفرة السابقة مثل وسيطw في دوال المعالجة (getRoot و getHello)، للسماح بالتحكم في الرد المرسل إلى العميل، وبالتالي إمكانية كتابة متن الاستجابة، أو ضبط الترويسات headers، أو تحديد رمز الحالة باستخدامها. نستخدم الدالة io.WriteString لكتابة الاستجابة ضمن متن الرسالة. لنضيف الآن الدالة main إلى الشيفرة السابقة: ... func main() { http.HandleFunc("/", getRoot) http.HandleFunc("/hello", getHello) err := http.ListenAndServe(":3333", nil) ... تكون الدالة الرئيسية main في جزء الشيفرة السابق، مسؤولةً عن إعداد خادم HTTP وتحديد معالجات الطلب. هناك استدعاءان إلى الدالة http.HandleFunc، بحيث يربط كل استدعاء لها دالة معالجة من أجل مسار طلب محدد ضمن مجمّع الخادم الافتراضي default server multiplexer. يتطلب الأمر وسيطين: الأول هو مسار الطلب (في هذه الحالة / ثم hello/) ودالة المعالجة (getRoot ثم getHello على التوالي). الدالتان getRoot و getHello هما دوال المعالجة التي سيجري استدعاؤها عند تقديم طلب إلى المسارات المقابلة (/ و hello/). للدالتين توقيع مماثل للدالة http.HandlerFunc التي تقبلhttp.ResponseWriter و http.Request* مثل وسطاء. تُستخدم الدالة http.ListenAndServe لبدء تشغيل خادم HTTP للاستماع إلى الطلبات الواردة. يتطلب الأمر وسيطين، هما: عنوان الشبكة للاستماع عليه (في هذه الحالة 3333:) ومعالج اختياري http.Handler. يحدد 3333: في برنامجنا أن الخادم يجب أن يستمع إلى المنفذ 3333، ونظرًا لعدم تحديد عنوان IP، سيستمع إلى جميع عناوين IP المرتبطة بالحاسب. يمثّل منفذ الشبكة network port -مثل "3333"- طريقةً تمكّن جهاز الحاسوب من أن يكون لديه عدة برامج تتواصل مع بعضها بنفس الوقت، بحيث يستخدم كل برنامج منفذه المخصص، وبالتالي عند اتصال العميل مع منفذ معين يعلم الحاسوب إلى أي منفذ سيُرسل. إذا كنت تريد قصر الاتصالات على المضيف المحلي localhost فقط، فيمكنك استخدام ‎127.0.0.1:3333. تمرّر الدالة http.ListenAndServe قيمة nil من أجل المعامل http.Handler، وهذا يخبر دالة ListenAndServe بأنك تريد استخدام مجمّع الخادم الافتراضي وليس أي مجمّع ضبطه سابقًا. الدالة ListenAndServe هي استدعاء "حظر"، مما يعني أنها ستمنع تنفيذ التعليمات البرمجية الأخرى حتى يُغلق الخادم. تُعيد هذه الدالة خطًأ إذا فشلت عملية بدء تشغيل الخادم أو إذا حدث خطأ أثناء التشغيل. من المهم تضمين عملية معالجة الأخطاء بعد استدعاء http.ListenAndServe، وذلك لأن الدالة يمكن أن تفشل، بالتالي من الضروري التعامل مع الأخطاء المحتملة. يُستخدم المتغير err لالتقاط أي خطأ يُعاد بواسطة ListenAndServe. يمكننا إضافة شيفرة معالجة الأخطاء بعد هذا السطر لمعالجة أي أخطاء محتملة قد تحدث أثناء بدء تشغيل الخادم أو تشغيله كما سنرى. لنضيف الآن شيفرة معالجة الأخطاء إلى دالة ListenAndServe ضمن دالة main الرئيسية كما يلي: ... func main() { ... err := http.ListenAndServe(":3333", nil) if errors.Is(err, http.ErrServerClosed) { fmt.Printf("server closed\n") } else if err != nil { fmt.Printf("error starting server: %s\n", err) os.Exit(1) <^>} } بعد استدعاء http.ListenAndServe، يجري تخزين الخطأ المُعاد في المتغير err. تجري عملية فحص الخطأ الأولى باستخدام (errors.Is(err, http.ErrServerClosed، إذ يجري التحقق ما إذا كان الخطأ هو http.ErrServerClosed، والذي يُعاد عندما يُغلق الخادم أو يجري إيقاف تشغيله. يعني ظهور هذا الخطأ أن الخادم قد أُغلق بطريقة متوقعة، بالتالي طباعة الرسالة "server closed". يُنجز فحص الخطأ الثاني باستخدام Err != nil. يتحقق هذا الشرط مما إذا كان الخطأ ليس nil، مما يشير إلى حدوث خطأ أثناء بدء تشغيل الخادم أو تشغيله. إذا تحقق الشرط، فهذا يعني حدوث خطأ غير متوقع، وبالتالي طباعة رسالة خطأ مع تفاصيل الخطأ باستخدام fmt.Printf، كما يجري إنهاء البرنامج مع شيفرة الخطأ 1 باستخدام (os.Exit (1 للإشارة إلى حدوث خطأ. تجدر الإشارة إلى أن أحد الأخطاء الشائعة التي قد تواجهها هو أن العنوان قيد الاستخدام فعلًا address already in use. يحدث هذا عندما تكون الدالة ListenAndServe غير قادرة على الاستماع إلى العنوان أو المنفذ المحدد للخادم لأنه قيد الاستخدام فعلًا من قبل برنامج آخر، أي إذا كان المنفذ شائع الاستخدام أو إذا كان برنامج آخر يستخدم نفس العنوان أو المنفذ. ملاحظة: عند ظهور هذا الخطأ، يجب التأكد من إيقاف أي مثيلات سابقة للبرنامج ثم محاولة تشغيله مرة أخرى. إذا استمر الخطأ يجب علينا محاولة استخدام رقم منفذ مختلف لتجنب التعارضات، فمن المحتمل أن برنامجًا آخر يستخدم المنفذ المحدد. يمكن اختيار رقم منفذ مختلف (أعلى من 1024 وأقل من 65535) وتعديل الشيفرة وفقًا لذلك. على عكس برامج لغة جو الأخرى؛ لن يُنهى البرنامج فورًا من تلقاء نفسه. لنُشغّل ملف البرنامج "main.go" بعد حفظه من خلال الأمر go run: $ go run main.go بما أن البرنامج يبقى قيد التشغيل في الطرفية الحالية، فسنحتاج إلى فتح نافذة أخرى للطرفية لكي نتفاعل مع الخادم (ستظهر الأوامر بلون مختلف عن الطرفية الأولى). ضمن الطرفية الثانية التي فتحناها، نستخدم الأمر curl لتقديم طلب HTTP إلى خادم HTTP الخاص بنا. curl هي أداة مُثبّتة افتراضيًا على العديد من الأنظمة التي يمكنها تقديم طلبات للخوادم من أنواع مختلفة، وسنستخدمها في هذا المقال لإجراء طلبات HTTP. يستمع الخادم إلى الاتصالات على المنفذ 3333 لجهاز الحاسوب، لذا يجب تقديم الطلب للمضيف المحلي على نفس المنفذ: $ curl http://localhost:3333 سيكون الخرج على النحو التالي: This is my website! العبارة This is my website ناتجة عن الدالة getRoot، وذلك لأنك استخدمت المسار / على خادم HTTP. دعونا الآن نستخدم المسار hello/ على نفس المضيف والمنفذ، وذلك بإضافة المسار إلى نهاية أمر curl: $ curl http://localhost:3333/hello ليكون الخرج هذه المرة: Hello, HTTP! نلاحظ أن الخرج السابق كان نتيجةً لاستدعاء الدالة getHello. إذا عدنا إلى المحطة الطرفية الأولى التي يعمل عليها خادم HTTP، نلاحظ وجود سطرين أنتجهما الخادم الخاص بنا: واحد للطلب / والآخر للطلب hello/. got / request got /hello request سيستمر البرنامج بالعمل، لذا يجب علينا إيقافه يدويًا من خلال الضغط على المفتاحين "Ctrl+C". لقد أنشأنا برنامجًا يمثل خادم HTTP، لكنه يستخدم مجمّع خادم افتراضي وخادم HTTP افتراضي أيضًا. يمكن أن يؤدي الاعتماد على القيم الافتراضية أو العامة Global إلى حدوث أخطاء يصعب تكرارها أو يصعب إنتاجها باستمرار Reproduce consistently، إذ يمكن أن تُعدِّل أجزاءً مختلفة من البرنامج هذه القيم العامة في أوقات مختلفة، مما يؤدي إلى حالة غير صحيحة أو غير متسقة. يصبح تحديد مثل هذه الأخطاء أمرًا صعبًا لأنها قد تحدث فقط في ظل ظروف معينة أو إذا جرى استدعاء وظائف معينة بترتيب معين. معالجات طلبات التجميع عند بدء تشغيل خادم HTTP سابقًا؛ استخدمنا مجمّع خادم افتراضي عن طريق تمرير قيمة صفرية (أي nil) للمعامل http.Handler في دالة ListenAndServe. رأينا أيضًا أن هناك بعض المشاكل التي قد تطرأ في حالة استخدام المعاملات الافتراضية. بما أن http.Handler هو واجهة interface، فهذا يعني أنه لدينا الخيار لإنشاء بنية مخصصة تحقق هذه الواجهة. هناك طبعًا حالات نحتاج فيها فقط إلى http.Handler الافتراضي الذي يستدعي دالة واحدة لمسار طلب معين، أي كما في حالة مجمّع الخادم الافتراضي، لكن هناك حالات قد تتطلّب أكثر من ذلك؛ هذا ما نناقشه تاليًا. لنعدّل البرنامج الآن لاستخدام http.ServeMux، إنها أداة تعمل مثل مجمّع للخادم، وهي مسؤولة عن التوجيه والتعامل مع طلبات HTTP الواردة بناءً على مساراتها. تحقّقhttp.ServeMux الواجهة http.Handler المؤمنة من قِبل حزمة net/http، مما يعني قدرتها على التعامل مع طلبات HTTP وإنشاء الاستجابات المناسبة. بالتالي مزيد من التحكم في التوجيه والتعامل مع مسارات الطلبات المختلفة، وإتاحة الفرصة لتحديد دوال أو معالجات محددة لكل مسار. بالتالي نكون قد اتبعنا نهجًا أكثر تنظيمًا وقابلية للتخصيص للتعامل مع طلبات HTTP في البرنامج. يمكن تهيئة بنية http.ServeMux بطريقة مشابهة للمجمّع الافتراضي، لذا لن نحتاج إلى إجراء العديد من التغييرات على البرنامج لبدء استخدام مجمّع الخادم المخصّص بدلًا من الافتراضي. لتحديث البرنامج وفقًا لذلك، نفتح ملف "main.go" مرةً أخرى ونجري التعديلات اللازمة لاستخدام http.ServeMux: ... func main() { mux := http.NewServeMux() mux.HandleFunc("/", getRoot) mux.HandleFunc("/hello", getHello) err := http.ListenAndServe(":3333", mux) ... } أنشأنا http.ServeMux جديد باستخدام باني http.NewServeMux وأسندناه إلى المتغير mux، ثم عدّلنا استدعاءات http.HandleFunc لاستخدام المتغير mux بدلًا من استدعاء حزمة http مباشرة. أخيرًا، عدّلنا استدعاء http.ListenAndServe لتزويده بالمعالج http.Handler الذي أنشأنه mux بدلًا من nil. لنُشغّل ملف البرنامج "main.go" بعد حفظه من خلال الأمر go run: $ go run main.go سيستمر البرنامج في العمل كما في المرة السابقة، لذا نحتاج إلى تشغيل أوامر في طرفية أخرى للتفاعل مع الخادم. أولًا، نستخدم curl لطلب المسار /: $ curl http://localhost:3333 سيكون الخرج على النحو التالي: This is my website! الخرج كما هو في المرة السابقة. دعونا الآن نستخدم المسار hello/ على نفس المضيف والمنفذ، وذلك بإضافة المسار إلى نهاية أمر curl: $ curl http://localhost:3333/hello سيكون الخرج كما يلي: Hello, HTTP! نلاحظ أن الخرج السابق كان كما المرة السابقة أيضًا. إذا عدنا الآن إلى الطرفية الأولى، فسنرى مخرجات كل من / و hello / كما كان من قبل: got / request got /hello request نلاحظ أن التعديلات التي أجريناها لا تغيّر في وظيفة البرنامج، وإنما فقط بدلنا مجمّع الخادم الافتراضي بآخر مخصص. سيستمر البرنامج بالعمل، لذا يجب علينا إيقافه يدويًا من خلال الضغط على المفتاحين "Ctrl+C". تشغيل عدة خوادم في وقت واحد سنجري خلال هذا القسم تعديلات على البرنامج لاستخدام عدة خوادم HTTP في نفس الوقت باستخدام http.Server التي توفرها حزمة net/http. يمكننا بالحالة الافتراضية تشغيل خادم HTTP واحد فقط في البرنامج، لكن قد تكون هناك سيناريوهات نحتاج فيها إلى تخصيص سلوك الخادم أو تشغيل عدة خوادم في نفس الوقت، مثل استضافة موقع ويب عام Public website وموقع ويب إداري خاص Private admin website داخل نفس البرنامج. لأجل ذلك سنعدّل ملف "main.go" لإنشاء نسخ متعددة من http.Server لأغراض مختلفة، إذ سيكون لكل خادم التهيئة والإعدادات الخاصة به. يتيح لك هذا مزيدًا من التحكم في سلوك الخادم ويمكّنك من التعامل مع وظائف خادم متعددة داخل نفس البرنامج. سنعدّل أيضًا دوال المعالجة لتحقيق إمكانية الوصول إلى Context.Context المرتبط مع http.Request*؛ أي إمكانية الوصول إلى سياق الطلبات الواردة، إذ يمكننا من خلال هذا السياق تمييز الخادم الذي يأتي الطلب منه. إذًا من خلال تخزين هذه المعلومات في متغير السياق، يصبح بمقدورنا استخدامها داخل دالة المعالجة لتنفيذ إجراءات محددة أو تخصيص الاستجابة بناءً على الخادم الذي أنشأ الطلب. لنفتح ملف "main.go" ونعدّله بالتالي: package main import ( // os لاحظ أننا حذفنا استيراد "context" "errors" "fmt" "io" "net" "net/http" ) const keyServerAddr = "serverAddr" func getRoot(w http.ResponseWriter, r *http.Request) { ctx := r.Context() fmt.Printf("%s: got / request\n", ctx.Value(keyServerAddr)) io.WriteString(w, "This is my website!\n") } func getHello(w http.ResponseWriter, r *http.Request) { ctx := r.Context() fmt.Printf("%s: got /hello request\n", ctx.Value(keyServerAddr)) io.WriteString(w, "Hello, HTTP!\n") } عدّلنا بيان الاستيراد import لتضمين الحزم المطلوبة، ثم أنشأنا سلسلة نصية ثابتة const string تسمى keyServerAddr لتعمل مثل مفتاح لقيمة عنوان خادم HTTP في سياق http.Request، ثم عدّلنا دوال المعالجة getRoot و getHello للوصول إلى قيمة Context.Context التابعة إلى http.Request. بعد الحصول على القيمة يمكننا تضمين عنوان خادم HTTP في خرج fmt.Printf حتى نتمكن من معرفة أي من الخادمين تعامل مع طلب HTTP. لنعدّل الآن الدالة main بإضافة قيمتي http.Server: ... func main() { ... mux.HandleFunc("/hello", getHello) ctx, cancelCtx := context.WithCancel(context.Background()) serverOne := &http.Server{ Addr: ":3333", Handler: mux, BaseContext: func(l net.Listener) context.Context { ctx = context.WithValue(ctx, keyServerAddr, l.Addr().String()) return ctx }, } التغيير الأول الذي أجريناه هو إنشاء قيمة Context.Context جديدة مع دالة متاحة هي الدالة `cancelCtx'. هذا يسمح لنا بإلغاء السياق عند الحاجة. عرّفنا أيضًا نسخةً تسمى serverOne من http.Server، وهو مشابه لخادم HTTP الذي كنا نستخدمه، ولكن بدلًا من تمرير العنوان والمعالج مباشرةً إلى http.ListenAndServe، يمكن إسنادهما مثل قيم Addr و Handler في بنية http.Server. تعديل آخر كان بإضافة دالة BaseContext، وهي دالة تسمح بتعديل أجزاء من Context.Context الذي جرى تمريره إلى دوال المعالجة عند استدعاء التابع Context من http.Request*. أضفنا في الشيفرة السابقة عنوان الاستماع الخاص بالخادم إلى السياق باستخدام المفتاح serverAddr، وهذا يعني أن العنوان الذي يستمع فيه الخادم للطلبات الواردة مرتبط بمفتاح serverAddr في السياق. عندما نستدعي الدالة BaseContext، فإنها تتلقى net.Listener، والذي يمثل مستمع الشبكة الأساسي الذي يستخدمه الخادم. بالنسبة لبرنامجنا: من خلال استدعاء ()l.Addr(). String، نكون قد حصلنا على عنوان شبكة المستمع. يتضمن هذا عادةً عنوان IP ورقم المنفذ الذي يستمع الخادم عليه. بعد ذلك نضيف العنوان الذي حصلنا عليه إلى السياق باستخدام الدالة Context.WithValue، والتي تتيح لنا تخزين أزواج المفتاح والقيمة في السياق. في هذه الحالة يكون المفتاح هو serverAddr، والقيمة المرتبطة به هي عنوان الاستماع الخاص بالخادم. لنعرّف الآن الخادم الثاني serverTwo: ... func main() { ... serverOne := &http.Server { ... } serverTwo := &http.Server{ Addr: ":4444", Handler: mux, BaseContext: func(l net.Listener) context.Context { ctx = context.WithValue(ctx, keyServerAddr, l.Addr().String()) return ctx }, } نعرّف الخادم الثاني بنفس طريقة تعريف الأول، لكن نضع حقل العنوان على 4444: بدلًا من 3333:، وذلك لكي يستمع الخادم الأول على للاتصالات على المنفذ 3333 ويستمع الخادم الثاني على المنفذ 4444. لنعدّل الآن البرنامج من أجل استخدام الخادم الأول serverOne وجعله يعمل مثل تنظيم جو goroutine: ... func main() { ... serverTwo := &http.Server { ... } go func() { err := serverOne.ListenAndServe() if errors.Is(err, http.ErrServerClosed) { fmt.Printf("server one closed\n") } else if err != nil { fmt.Printf("error listening for server one: %s\n", err) } cancelCtx() }() نستخدم تنظيم goroutine لبدء تشغيل الخادم الأول serverOne باستخدام الدالة ListenAndServe كما فعلنا سابقًا، لكن هذه المرة دون أي معاملات لأن قيم http.Server جرى تهيئتها مسبقًا باستخدام العنوان والمعالج المطلوبين. تجري عملية معالجة الأخطاء كما في السابق؛ فإذا كان الخادم مغلقًا، فإنه يطبع رسالة تشير إلى أن "الخادم الأول" أصبح مغلقًا؛ وإذا كان هناك خطأ آخر بخلاف http.ErrServerClosed، فستظهر رسالة خطأ. تُستدعى أخيرًا الدالة CancelCtx لإلغاء السياق الذي قدمناه لمُعالجات HTTP ودوال BaseContext لكلا الخادمين. بالتالي إنهاء أي عمليات جارية تعتمد عليه بأمان. هذا يضمن أنه إذا انتهى الخادم لأي سبب، فسيُنهى أيضًا السياق المرتبط به. لنعدّل الآن البرنامج لاستخدام الخادم الثاني، بحيث يعمل مثل تنظيم جو : ... func main() { ... go func() { ... }() go func() { err := serverTwo.ListenAndServe() if errors.Is(err, http.ErrServerClosed) { fmt.Printf("server two closed\n") } else if err != nil { fmt.Printf("error listening for server two: %s\n", err) } cancelCtx() }() <-ctx.Done() } تنظيم جو هنا هو نفسه الأول من الناحية الوظيفية، فهو يُشغّل serverTwo فقط بدلًا من serverOne. بعد بدء تشغيل serverTwo، يصل التنظيم الخاص بالدالة main إلى السطر ctx.Done. ينتظر هذا السطر إشارةً من قناة ctx.Done، والتي تُعاد عند إلغاء السياق أو الانتهاء منه. من خلال هذا الانتظار نكون قد منعنا تنظيم الدالة الرئيسية من الخروج من الدالة main حتى تنتهي تنظيمات جو لكلا الخادمين من العمل. إذًا، الغرض من هذا الأسلوب هو التأكد من استمرار تشغيل البرنامج حتى انتهاء كلا الخادمين أو مواجهة خطأ. لنُشغّل ملف البرنامج "main.go" بعد حفظه من خلال الأمر go run: $ go run main.go نُشغّل الآن أوامر curl (في الطرفية الثانية) لطلب المسار / والمسارhello/ من الخادم الذي يستمع على 3333، مثل الطلبات السابقة: $ curl http://localhost:3333 $ curl http://localhost:3333/hello سيكون الخرج كما في المرات السابقة: This is my website! Hello, HTTP! لنشغّل الأوامر نفسها مرة أخرى، ولكن هذه المرة مع المنفذ 4444 الذي يتوافق مع serverTwo: $ curl http://localhost:4444 $ curl http://localhost:4444/hello سيبقى الخرج نفسه كما في المرة السابقة أيضًا: This is my website! Hello, HTTP! لنلقي نظرةً الآن على الطرفية الأولى حيث يعمل الخادم: [::]:3333: got / request [::]:3333: got /hello request [::]:4444: got / request [::]:4444: got /hello request الخرج مشابه لما رأيناه من قبل، لكنه يعرض هذه المرة الخادم الذي استجاب للطلب. يظهِر الطلبان الأولان أنهما جاءا من الخادم الذي يستمع على المنفذ 3333 أي serverOne، والطلبان الثانيان جاءا من الخادم الذي يستمع على المنفذ 4444 أي serverTwo. إنها القيم التي جرى استردادها من قيمة serverAddr في BaseContext. قد يكون الخرج مختلفًا قليلًا عن الخرج أعلاه اعتمادًا على ما إذا كان جهاز الحاسب المُستخدم يستخدم IPv6 أم لا. إذا كان الحاسب يستخدم IPv6 فسيكون الخرج كما أعلاه، وإلا سنرى 0.0.0.0 بدلًا من [::]. السبب في ذلك هو أن جهاز الحاسب سيتواصل مع نفسه عبر IPv6، و [::] هو تدوين IPv6 والذي يُقابل 0.0.0.0 في IPv4. بعد الانتهاء نضغط "CONTROL+C" لإنهاء الخادم. تعرفنا في هذا القسم على عملية إنشاء برنامج خادم HTTP وتحسينه تدريجيًا للتعامل مع السيناريوهات المختلفة. بدأنا بتهيئة الخادم الافتراضي باستخدام http.HandleFunc و http.ListenAndServe. بعد ذلك عدّلناه لاستخدام http.ServeMux مثل مجمّع خادم، مما يسمح لنا بالتعامل مع معالجات طلبات متعددة لمسارات مختلفة. وسّعنا البرنامج أيضًا لاستخدام http.Server، مما يمنحنا مزيدًا من التحكم في تهيئة الخادم ويسمح لنا بتشغيل خوادم HTTP متعددة في نفس الوقت داخل نفس البرنامج. على الرغم من أن البرنامج يعمل، إلا أنه يفتقر إلى التفاعل الذي يتجاوز تحديد المسارات المختلفة. لمعالجة هذا القيد، يركز القسم التالي على دمج قيم سلسلة الاستعلام query string values في وظائف الخادم، مما يتيح للمستخدمين التفاعل مع الخادم باستخدام معاملات الاستعلام. فحص سلسلة الاستعلام الخاصة بالطلب ينصب التركيز في هذا القسم على دمج قيم سلسلة الاستعلام في وظائف خادم HTTP. سلسلة الاستعلام هي مجموعة من القيم الملحقة بنهاية عنوان URL، تبدأ بالمحرف ? وتستخدم المُحدّد & للقيم الإضافية. توفر قيم سلسلة الاستعلام وسيلة للمستخدمين للتأثير على الاستجابة التي يتلقونها من خادم HTTP عن طريق تخصيص النتائج أو تصفيتها، فمثلًا قد يستخدم أحد الخوادم قيمة results للسماح للمستخدم بتحديد شيء مثل results = 10 ليقول إنه يرغب في رؤية 10 عناصر في قائمة النتائج. لتحقيق هذه الميزة نحتاج إلى تحديث دالة المعالجة getRoot في ملف "main.go" للوصول إلى قيم سلسلة الاستعلام http.Request* باستخدام التابع r.URL.Query، ثم طباعتها بعد ذلك على الخرج. نزيل أيضًا serverTwo وكل الشيفرات المرتبطة به من الدالة main، لأنها لم تعد مطلوبة للتغييرات القادمة: ... func getRoot(w http.ResponseWriter, r *http.Request) { ctx := r.Context() hasFirst := r.URL.Query().Has("first") first := r.URL.Query().Get("first") hasSecond := r.URL.Query().Has("second") second := r.URL.Query().Get("second") fmt.Printf("%s: got / request. first(%t)=%s, second(%t)=%s\n", ctx.Value(keyServerAddr), hasFirst, first, hasSecond, second) io.WriteString(w, "This is my website!\n") } ... يمكننا في دالة getRoot المحدَّثة استخدام الحقل r.URL الذي يتبع إلى http.Request* للوصول إلى الخصائص المتعلقة بعنوان URL المطلوب. باستخدام التابع Query في الحقل r.URL، يمكننا الوصول إلى قيم سلسلة الاستعلام المرتبطة بالطلب. هناك طريقتان يمكن استخدامهما للتفاعل مع بيانات سلسلة الاستعلام: يتحقق التابع Has ما إذا كانت سلسلة الاستعلام تحتوي على قيمة بمفتاح معين، مثل"first" أو "second". يعيد التابع قيمة بوليانية bool تشير إلى وجود المفتاح. يسترد التابع Get القيمة المرتبطة بمفتاح معين من سلسلة الاستعلام وتكون من نوع string، وإذا لم يعثر على المفتاح، يعيد عادةً سلسلة فارغة. نظريًّا يمكننا دائمًا استخدام Get لاسترداد قيم سلسلة الاستعلام لأنها ستعيد دائمًا إما القيمة الفعلية للمفتاح المحدد أو سلسلة فارغة إذا كان المفتاح غير موجود. هذا جيد في كثير من الحالات، ولكن في بعض الحالات الأخرى، قد نرغب في معرفة الفرق بين المستخدم الذي يقدم قيمة فارغة أو عدم تقديم قيمة إطلاقًا. إذًا، بناءً على حالة الاستخدام الخاصة بنا، قد يكون من المهم التمييز بين المستخدم الذي يقدم قيمة فارغة للمرشّح أو عدم تقديم مرشح filter إطلاقًا: يُقدّم المستخدم قيمة فارغة للمرشح filter: هنا يحدد المستخدم صراحة قيمة المرشح، ولكنه يضبطها على قيمة فارغة. قد يشير هذا إلى اختيار متعمد لاستبعاد نتائج معينة أو تطبيق شرط مرشحfilter معين. مثلًا، إذا كانت لدينا دالة بحث وقدم المستخدم قيمة فارغة للمرشح، فيمكننا تفسيرها على أنها "إظهار كافة النتائج". لا يوفر المستخدم مرشّح إطلاقًا: هنا لم يقدّم المستخدم مرشّحًا في الطلب. هذا يعني عادةً أن المستخدم يريد استرداد جميع النتائج دون تطبيق أي تصفية محددة. يمكن عدّه سلوكًا افتراضيًا، إذ لا تُطبّق شروط ترشيح محددة. يسمح لك استخدام Has و Get بالتمييز بين الحالات التي يقدم فيها المستخدم صراحة قيمةً فارغة والحالات التي لا تُقدّم فيها قيمة إطلاقًا. بالتالي إمكانية التعامل مع السيناريوهات المختلفة اعتمادًا على حالة الاستخدام المحددة الخاصة بنا. يمكنك تحديث دالة getRoot لعرض قيم Has و Get من أجل قيمتي سلسلة الاستعلام first و second. لنعدّل الدالة main بحيث نستخدم خادم واحد مرة أخرى: ... func main() { ... mux.HandleFunc("/hello", getHello) ctx := context.Background() server := &http.Server{ Addr: ":3333", Handler: mux, BaseContext: func(l net.Listener) context.Context { ctx = context.WithValue(ctx, keyServerAddr, l.Addr().String()) return ctx }, } err := server.ListenAndServe() if errors.Is(err, http.ErrServerClosed) { fmt.Printf("server closed\n") } else if err != nil { fmt.Printf("error listening for server: %s\n", err) } } نلاحظ ضمن الدالة main إزالة المراجع والشيفرات المرتبطة بالخادم الثاني serverTwo، لأننا لم نعد بحاجة إلى خوادم متعددة. نقلنا أيضًا تنفيذ الخادم (serverOne سابقًا) خارج التنظيم goroutine وإلى الدالة main. هذا يعني أنه سيجري بدء تشغيل الخادم بطريقة متزامنة، و ينتظر التنفيذ حتى يُغلق الخادم قبل المتابعة. تغيير آخر كان باستخدام الدالة server.ListenAndServe؛ فبدلًا من استخدام http.ListenAndServe، يمكن الآن استخدامserver.ListenAndServe لبدء الخادم. يتيح ذلك الاستفادة من تهيئة http.Server وأي تخصيصات أجريناها. كذلك أضفنا شيفرة لمعالجة الأخطاء، وذلك للتحقق ما إذا كان الخادم مغلقًا أو واجه أي خطأ آخر أثناء الاستماع. إذا كان الخطأ هو http.ErrServerClosed، فهذا يعني أن عملية الإغلاق عن قصد (إغلاق طبيعي)، وخلاف ذلك ستجري طباعة الخطأ. بإجراء هذه التغييرات سيُشغّل برنامجنا الآن خادم HTTP واحد باستخدام وفقًا لتهيئةhttp.Server ليبدأ في الاستماع للطلبات الواردة، ولن تحتاج إلى تحديثات أخرى من أجل تخصيصات للخادم مستقبلًا. لنُشغّل ملف البرنامج "main.go" بعد حفظه من خلال الأمر go run: $ go run main.go نُشغّل الآن أوامر curl (في الطرفية الثانية). هنا نحتاج إلى إحاطة عنوان URL بعلامات اقتباس مفردة (')، وإلا فقد تُفسر صدفة الطرفية (أي Shell) الرمز & في سلسلة الاستعلام على أنه ميزة "تشغيل الأمر في الخلفية". ضمن عنوان URL نضيف first=1 إلى first و =second إلى second: $ curl 'http://localhost:3333?first=1&second=' سيكون الخرج على النحو التالي: This is my website! لاحظ أن الخرج لم يتغير عن المرة السابقة، لكن إذا عدنا إلى خرج برنامج الخادم، فسنرى أن الخرج الجديد يتضمن قيم سلسلة الاستعلام: [::]:3333: got / request. first(true)=1, second(true)= يظهِر خرج قيمة سلسلة الاستعلام first أن التابع Has أعاد true لأن first لها قيمة، وأيضًا التابع Get أعاد القيمة 1. يُظهر ناتج second أنه قد أعاد true لأننا ضمّنّا second، لكن التابع Get لم يُعيد أي شيء إلا سلسلة فارغة. يمكننا أيضًا محاولة إجراء طلبات مختلفة عن طريق إضافة وإزالة first و second أو إسناد قيم مختلفة لنرى كيف تتغير النتائج. سيستمر البرنامج بالعمل، لذا يجب علينا إيقافه يدويًا من خلال الضغط على المفتاحين "Ctrl+C". حدّثنا في هذا القسم البرنامج لاستخدام http.Server واحد فقط مرةً أخرى، لكن أضفنا أيضًا دعمًا لقراءة قيم first و second من سلسلة الاستعلام لدالة المعالجة getRoot. ليس استخدام سلسلة الاستعلام الطريقة الوحيدة للمستخدمين لتقديم مدخلات إلى خادم HTTP، فهناك طريقة أخرى شائعة لإرسال البيانات إلى الخادم وهي تضمين البيانات في متن الطلب. سنعدّل في القسم التالي البرنامج لقراءة نص الطلب من بيانات http.Request*. قراءة متن الطلب عند إنشاء واجهة برمجة تطبيقات مبنية على HTTP، مثل واجهة برمجة تطبيقات REST، قد تكون هناك حالات تتجاوز فيها البيانات المُرسلة قيود تضمينها في عنوان URL نفسه، مثل الطول الأعظمي للعنوان. قد نحتاج أيضًا إلى تلقي بيانات لا تتعلق بكيفية تفسير البيانات، وهذا يشير إلى الحالات التي لا ترتبط فيها البيانات المرسلة في متن الطلب ارتباطًا مباشرًا بالمحتوى نفسه. بمعنى آخر: لا توفر هذه البيانات الإضافية إرشادات أو بيانات وصفية حول المحتوى، ولكنها تتضمن معلومات تكميلية تحتاج إلى المعالجة بطريقة منفصل. تخيل صفحة بحث، يمكن فيها للمستخدمين إدخال كلمات للبحث عن عناصر محددة. تمثل كلمات البحث نفسها تفسير المحتوى المقصود، ومع ذلك قد تكون هناك بيانات إضافية في متن الطلب، مثل تفضيلات المستخدم أو الإعدادات، والتي لا ترتبط مباشرةً باستعلام البحث نفسه ولكنها لا تزال بحاجة إلى النظر فيها أو معالجتها بواسطة الخادم. للتعامل مع مثل هذه السيناريوهات، يمكننا تضمين البيانات في متن طلب HTTP باستخدام توابع مثل POST أو PUT. تُستخدم قيمة http.Request* في http.HandlerFunc للوصول إلى معلومات متعلقة بالطلب الوارد، بما في ذلك متن الطلب، والذي يمكن الوصول إليه من خلال حقل Body. سنعدّل في هذا القسم دالة المعالجة getRoot لقراءة نص الطلب. لنفتح ملف main.go ونعدّل getRoot لاستخدام ioutil.ReadAll لقراءة حقلr.Body للطلب: package main import ( ... "io/ioutil" ... ) ... func getRoot(w http.ResponseWriter, r *http.Request) { ... second := r.URL.Query().Get("second") body, err := ioutil.ReadAll(r.Body) if err != nil { fmt.Printf("could not read body: %s\n", err) } fmt.Printf("%s: got / request. first(%t)=%s, second(%t)=%s, body:\n%s\n", ctx.Value(keyServerAddr), hasFirst, first, hasSecond, second, body) io.WriteString(w, "This is my website!\n") } ... تُستخدم الدالة ioutil.ReadAll لقراءة r.Body من http.Request* لاسترداد بيانات متن الطلب. ioutil.ReadAll هي أداة مساعدة تقرأ البيانات من io.Reader حتى تنتهي من القراءة أو ظهور خطأ. نظرًا لأن r.Body هو io.Reader، فيمكن استخدامه لقراءة متن الطلب. نعدّل العبارة fmt.Printf بعد قراءة النص لتضمين محتوى المتن في الخرج. لنُشغّل ملف البرنامج "main.go" بعد حفظه من خلال الأمر go run: $ go run main.go نُشغّل الآن أوامر curl (في الطرفية الثانية)، لتقديم طلب POST مع خيار X POST- وتحديد أن طريقة الطلب يجب أن تكون POST، ومتن طلب باستخدام الخيار b-. يُضبط متن الطلب على السلسلة النصيّة المقدمة، والتي في هذه الحالة هي "This is the body": $ curl -X POST -d 'This is the body' 'http://localhost:3333?first=1&second=' ليكون الخرج: This is my website! خرج دالة المعالجة هو نفسه، لكن سنرى تسجيلات الخادم الخاصة بك قد حُدّثت. سيُرسل طلب POST عندما نشغّل هذا الأمر إلى الخادم المحلي الذي يعمل على المنفذ 3333 مع محتوى المتن المحدد ومعلمات سلسلة الاستعلام. بالتالي يعالج الخادم الطلب ونرى الخرج في سجلات الطرفية للخادم، بما في ذلك قيم سلسلة الاستعلام ومتن الطلب. [::]:3333: got / request. first(true)=1, second(true)=, body: This is the body سيستمر البرنامج بالعمل، لذا يجب علينا إيقافه يدويًا من خلال الضغط على المفتاحين "Ctrl+C". عدّلنا في هذا القسم البرنامج لقراءة وطباعة متن الطلب. تفتح هذه الإمكانية إمكانيات التعامل مع أنواع مختلفة من البيانات، مثل جسون encoding/json، وتتيح إنشاء واجهات برمجة تطبيقات يمكنها التفاعل مع بيانات المستخدم بطريقة مألوفة. تجدر الملاحظة إلى أنه لا تأتي جميع بيانات المستخدم في شكل واجهات برمجة التطبيقات. تتضمن العديد من مواقع الويب نماذج يملؤها المستخدمون، والتي ترسل البيانات إلى الخادم مثل بيانات نموذج Form. سنعمل على تحسين البرنامج في القسم التالي ليشمل القدرة على قراءة بيانات النموذج ومعالجتها، بالإضافة إلى جسم الطلب وبيانات سلسلة الاستعلام التي كنا نعمل معها سابقًا. استرجاع بيانات النموذج لطالما كان إرسال البيانات باستخدام النماذج أو الاستمارات هو الطريقة القياسية للمستخدمين لإرسال البيانات إلى خادم HTTP والتفاعل مع مواقع الويب، وعلى الرغم من انخفاض شعبية النماذج بمرور الوقت، إلا أنها لا تزال تخدم أغراضًا مختلفة لتقديم البيانات. توفر قيمة http.Request* في http.HandlerFunc طريقةً للوصول إلى هذه البيانات، بطريقة مشابهة للطريقة التي توفر بها الوصول إلى سلسلة الاستعلام ومتن الطلب. سنعدّل في هذا القسم الدالة getHello لتلقي اسم مستخدم من نموذج والرد بتحية شخصية. لنفتح ملف "main.go" ونعدّل getHello لاستخدام التابع PostFormValue من http.Request*: ... func getHello(w http.ResponseWriter, r *http.Request) { ctx := r.Context() fmt.Printf("%s: got /hello request\n", ctx.Value(keyServerAddr)) myName := r.PostFormValue("myName") if myName == "" { myName = "HTTP" } io.WriteString(w, fmt.Sprintf("Hello, %s!\n", myName)) } ... الآن في دالة getHello المحدَّثة، تجري قراءة قيم النموذج المرسلة إلى دالة المعالجة والبحث عن قيمة تسمى myName. إذا لم يُعثر على القيمة أو كانت سلسلة فارغة، تُضبط القيمة الافتراضية HTTP إلى المتغير myName لمنع عرض اسم فارغ على الصفحة. نعدّل أيضًا الخرج المرسل إلى المستخدم لكي يعرض الاسم الذي قدمه أو HTTP إذا لم يُقدّم أي اسم. لنُشغّل ملف البرنامج "main.go" بعد حفظه من خلال الأمر go run: $ go run main.go نُشغّل الآن أوامر curl (في الطرفية الثانية)، لتقديم طلب POST مع خيار X POST-، وبدلًا من استخدام الخيار b-، نستخدم الخيار 'F 'myName=Sammy لتضمين بيانات النموذج مع حقل myName والقيمة Sammy: curl -X POST -F 'myName=Sammy' 'http://localhost:3333/hello' سيكون الخرج: Hello, Sammy! يُستخدم التابع r.PostFormValue في getHello لاسترداد قيمة حقل النموذج myName. يبحث هذا التابع تحديدًا عن القيم المنشورة (البيانات المرسلة من العميل إلى الخادم) في متن الطلب. يمكن أيضًا استخدام التابع r.FormValue، والذي يتضمن كلًا من متن النموذج وأي قيم أخرى في سلسلة الاستعلام. إذا استخدمنا ("r.FormValue("myName وأزلنا الخيار F-، فيمكن تضمين myName = Sammy في سلسلة الاستعلام لرؤية القيمة Sammy. يوصى عمومًا بأن نكون أكثر صرامة وأن نستخدم الدالة r.PostFormValue إذا كنا نريد تحديدًا استرداد القيم من متن النموذج، إذ تتجنب هذه الدالة التعارضات أو الأخطاء المحتملة التي قد تنشأ عن خلط قيم النموذج من مصادر مختلفة. إذًا، تعد الدالة r.PostFormValue خيارًا أكثر أمانًا عند التعامل مع بيانات النموذج، لكن إذا كنا نحتاج إلى المرونة في وضع البيانات في كل من متن النموذج وسلسلة الاستعلام، فربما نضطر إلى الدالة الأخرى. عند النظر إلى سجلات الخادم، سنرى أن طلب hello/ قد جرى تسجيله بطريقة مشابهة للطلبات السابقة: [::]:3333: got /hello request سيستمر البرنامج بالعمل، لذا يجب علينا إيقافه يدويًا من خلال الضغط على المفتاحين "CONTROL+C". عدّلنا في هذا القسم البرنامج لقراءة اسم من بيانات النموذج المنشورة على الصفحة ثم إعادة هذا الاسم إلى المستخدم. يمكن أن تسوء بعض الأشياء في هذه المرحلة من البرنامج، لا سيما عند التعامل مع أحد الطلبات، ولن يجري إعلام المستخدمين. لنعدّل في القسم التالي دوال المعالجة لإرجاع رموز حالة HTTP والترويسات headers. الرد باستجابة تتضمن الترويسات ورمز الحالة هناك بعض الميزات المستخدمة خلف الكواليس ضمن بروتوكول HTTP لتسهيل الاتصال الفعّال بين المتصفحات والخوادم. إحدى هذه الميزات هي "رموز الحالة"، والتي تعمل مثل وسيلة للخادم لتوضّح للعميل ما إذا كان الطلب ناجحًا أو واجه أي مشكلات في أي من الطرفين. آلية اتصال أخرى تستخدمها خوادم وعملاء HTTP هي استخدام "حقول الترويسة header fields"، التي تتكون من أزواج ذات قيمة مفتاح يجري تبادلها بين العميل والخادم لنقل المعلومات عن أنفسهم. يمتلك بروتوكول HTTP عدة ترويسات معرّفة مسبقًا، مثل ترويسة Accept، التي يستخدمها العميل لإعلام الخادم بنوع البيانات التي يمكنه التعامل معها. يمكن أيضًا تعريف ترويسات خاصة باستخدام البادئة x- متبوعة بالاسم المطلوب. سنعمل في هذا القسم على تحسين البرنامج بجعل حقل النموذج myName في دالة المعالجة getHello حقلًا إلزاميًا. بالتالي، إذا لم تُعطى قيمة للحقل myName، فسيستجيب الخادم للعميل برمز الحالة "Bad Request طلب غير صالح" وتضمين الترويسة x-missing-field في الاستجابة، والتي تُعلم العميل بالحقل المفقود من الطلب. لنفتح ملف "main.go" ونعدّل الدالة getHello وفقًا لما ذُكر: ... func getHello(w http.ResponseWriter, r *http.Request) { ctx := r.Context() fmt.Printf("%s: got /hello request\n", ctx.Value(keyServerAddr)) myName := r.PostFormValue("myName") if myName == "" { w.Header().Set("x-missing-field", "myName") w.WriteHeader(http.StatusBadRequest) return } io.WriteString(w, fmt.Sprintf("Hello, %s!\n", myName)) } … سابقًا: إذا كان حقل myName فارغًا، كان يُسند إليه قيمة افتراضية، أما الآن ضمن الدالة getHello المُحدَّثة، نُرسل رسالة خطأ إلى العميل. يُستخدم بدايةً التابع w.Header().Set لضبط الترويسة x-missing-field بقيمة myName في ترويسة الاستجابة، ثم التابع w.WriteHeader لكتابة ترويسات الاستجابة ورمز الحالة "طلب غير صالح" إلى العميل. أخيرًا تُستخدم تعليمة return لضمان إنهاء الدالة وعدم إرسال استجابة إضافية. من الضروري التأكد من ضبط الترويسات وإرسال رمز الحالة بالترتيب الصحيح، إذ يجب إرسال جميع الترويسات في بروتوكول HTTP قبل الجسم، مما يعني أنه يجب إجراء أي تعديلات على ()w.Header قبل استدعاء w.WriteHeader. عند استدعاء w.WriteHeader يُرسل رمز الحالة والترويسات، ويمكن كتابة المتن بعد ذلك حصرًا. يجب اتباع هذا الأمر لضمان حسن سير استجابة HTTP. لنُشغّل ملف البرنامج "main.go" بعد حفظه من خلال الأمر go run: $ go run main.go نُشغّل الآن الأمر curl -X POST (في الطرفية الثانية) مع المسار hello/ وبدون تضمين F- لإرسال بيانات النموذج. نحتاج أيضًا إلى تضمين الخيار v- لإخبار curl بإظهار الخرج المطوّل حتى نتمكن من رؤية جميع الترويسات والخرج للطلب: curl -v -X POST 'http://localhost:3333/hello' سنرى هذه المرة الكثير من المعلومات بسبب استخدامنا الخيار v-: * Trying ::1:3333... * Connected to localhost (::1) port 3333 (#0) > POST /hello HTTP/1.1 > Host: localhost:3333 > User-Agent: curl/7.77.0 > Accept: */* > * Mark bundle as not supporting multiuse < HTTP/1.1 400 Bad Request < X-Missing-Field: myName < Date: Wed, 02 Mar 2022 03:51:54 GMT < Content-Length: 0 < * Connection #0 to host localhost left intact تشير الأسطر الأولى إلى أن curl تحاول إنشاء اتصال بالخادم عند منفذ المضيف المحلي 3333. الأسطر المسبوقة بالرمز < تمثل الطلب المُرسل بواسطة curl، إذ يُرسل طلب POST إلى العنوان أو المسار hello/ باستخدام بروتوكول HTTP 1.1. يتضمن الطلب ترويسات مثل User-Agent و Accept و Host. والجدير بالذكر أن الطلب لا يتضمن متنًا، كما هو موضح في السطر الفارغ. تظهر استجابة الخادم بالسابقة > بعد إرسال الطلب. يشير السطر الأول إلى أن الخادم قد استجاب برمز حالة "طلب غير صالح" (المعروفة برمز الحالة 400)، كما تُضمّن الترويسة X-Missing-Field التي جرى ضبطها في ترويسة استجابة الخادم، مع تحديد أن الحقل المفقود هو myName. ينتهي الرد بدون أي محتوى في المتن، وهذا ما يتضح من طول المحتوى 0. إذا نظرنا مرةً أخرى إلى خرج الخادم، فسنرى طلب hello/ للخادم الذي جرت معالجته في الخرج: [::]:3333: got /hello request سيستمر البرنامج بالعمل، لذا يجب علينا إيقافه يدويًا من خلال الضغط على المفتاحين "CONTROL+C". عدّلنا في هذا القسم خادم HTTP ليشمل التحقق من صحة إرسال الطلب hello/. إذا لم يُقدّم اسم في في الطلب، تُضبط ترويسة باستخدام التابع w.Header(). Set للإشارة إلى الحقل المفقود. يُستخدم التابع w.WriteHeader بعد ذلك، لكتابة الترويسات إلى العميل، جنبًا إلى جنب مع رمز الحالة التي تشير إلى "طلب غير صالح". إذًا يُخبر الخادم العميل بوجود مشكلة في الطلب من خلال ضبط الترويسة ورمز الحالة. يسمح هذا الأسلوب بمعالجة الأخطاء بطريقة صحيحة ويوفر ملاحظات للعميل فيما يتعلق بحقل النموذج المفقود. الخاتمة تعلمنا كيفية إنشاء خادم HTTP في لغة جو باستخدام حزمة net/http، وطوّرنا الخادم باستخدام مجمّع مخصص للخادم واستخدام نسخhttp.Server متعددة. اسكتشفنا أيضًا طرقًا مختلفة للتعامل مع مُدخلات المستخدم، مثل قيم سلسلة الاستعلام ومتن الطلب وبيانات النموذج. تحققنا أيضًا من صحة الطلب من خلال إعادة ترويسات HTTP مخصصة ورموز حالة إلى العميل. تتمثل إحدى نقاط القوة في نظام بروتوكول HTTP في توافقه مع العديد من الأطر التي تتكامل بسلاسة مع حزمة net/http. توضح مشاريع مثل github.com/go-chi/chi ذلك من خلال توسيع وظائف خادم http القياسي من خلال تحقيق الواجهة http.Handler. يتيح ذلك للمطورين الاستفادة من البرامج الوسيطة والأدوات الأخرى دون الحاجة لإعادة كتابة المنطق الخاص الخادم، وهذا يسمح بالتركيز على إنشاء برمجيات وسيطة middleware وأدوات أخرى لتحسين الأداء بدلًا من التعامل مع الوظائف الأساسية فقط. توفر حزمة net/http المزيد من الإمكانات التي لم نتناولها في هذا المقال، مثل العمل مع ملفات تعريف الارتباط وخدمة حركة مرور HTTPS. ترجمة -وبتصرف- للمقال How To Make an HTTP Server in Go لصاحبه Kristin Davidson. اقرأ أيضًا المقال السابق كيفية استخدام صيغة جسون JSON في لغة جو Go. مدخل إلى خادم الويبن. مدخل إلى HTTP: شرح التخاطب بين العميل والخادم. رموز الحالة status codes.
  14. شهد الذكاء الاصطناعي Artificial Intellegence تحولًا ملحوظًا في العقد الأخير من الزمن، حيث خرج من عوالم الخيال العلمي إلى حياتنا اليومية. إنه القوة المُشغّلة للمساعدين الافتراضيين وأدوات التشخيص الطبي المتطورة والسيارات ذاتية القيادة وعمليات صنع القرار المعتمدة على البيانات في مختلف الصناعات وعدد لا يحصى من التطبيقات الأخرى التي تعيد تعريف كيفية تفاعلنا مع التكنولوجيا. ولكن ما الذي يُمكّن ثورة الذكاء الاصطناعي هذه بالضبط؟ خلف الكواليس، إنه عالم مكتبات وأطر عمل الذكاء الاصطناعي الرائع. مكتبات وأطر عمل الذكاء الاصطناعي هي من يمكّن المطورين من تحويل مفاهيم الذكاء الاصطناعي إلى واقع. فهي توفر اللبنات الأساسية والأدوات والموارد اللازمة لصياغة أنظمة ذكية، مما يجعل عملية التطوير أكثر كفاءة ويمكن تحقيقها أكثر من أي وقت مضى. في هذه المقالة الشاملة لمكتبات وأطر عمل الذكاء الاصطناعي، سنكشف عن أهمية مكتبات وأطر عمل الذكاء الاصطناعي ونفهم سبب أهميتها ونتعمق في أهم وأبرز الخيارات التي تساعد مطوري الذكاء الاصطناعي في جميع أنحاء العالم على أداء مهامهم. الفرق بين مكتبات الذكاء الاصطناعي وأطر عمل الذكاء الاصطناعي مكتبات الذكاء الاصطناعي عبارة عن مقتطفات أو وحدات برمجية مكتوبة مسبقًا توفر عددًا كبيرًا من الدوال Functions والأصناف Classes المصممة لأداء مهام محددة. يمكن أن تتراوح هذه المهام من معالجة اللغة الطبيعية NLP والرؤية الحاسوبية CV إلى التعلم المعزز RL والتعلم العميق DL. على عكس الأطر، لا تفرض المكتبات بنية محددة على المشروع بأكمله. وبدلاً من ذلك، فهي تقدم حلولاً محددة للمشكلات المستهدفة، مما يجعلها أدوات متعددة الاستخدامات للمطورين. عمومًا، تتصف أطر العمل بما يلي: الكفاءة: تعمل أطر عمل الذكاء الاصطناعي على تبسيط عملية التطوير من خلال توفير الأدوات والمكتبات والوظائف المعدة مسبقًا، مما يقلل حاجة المطورين إلى إعادة اختراع العجلة. تعمل هذه الكفاءة على تسريع تطوير مشروع الذكاء الاصطناعي. قابلية التوسع: توفر أطر العمل قابلية التوسع، مما يسمح للمطورين بالانتقال من التجربة على جهاز واحد إلى نشر النماذج على مجموعات من الأجهزة القوية أو حتى بيئات الحوسبة الموزعة. دعم المجتمع: تتمتع العديد من أطر عمل الذكاء الصناعي بدعم مجتمعي واسع النطاق. يُترجم هذا إلى ثروة من الموارد والبرامج التعليمية ومجتمع من المطورين الذين يمكنهم المساعدة في حل المشكلات، مما يجعل التطوير أكثر سلاسة. قابلية التشغيل البيني: غالبًا ما تدعم أطر عمل الذكاء الصناعي لغات برمجة متعددة ويمكن دمجها مع أدوات أخرى، مما يعزز مرونتها وتوافقها مع الأنظمة الحالية. تحسين الأداء: تتضمن تقنيات التحسين لجعل نماذج الذكاء الصناعي تعمل بشكل أسرع وتستهلك موارد أقل، وهو أمر بالغ الأهمية للتطبيقات التي تعمل في الزمن الحقيقي Real Time والأجهزة محدودة الموارد. باختصار، تعمل مكتبات وأطر الذكاء الصناعي على تمكين المطورين من التركيز على بناء نماذج الذكاء الصناعي وتحسينها بدلًا من التورط في تعقيدات التنفيذ على المستوى المنخفض، مما يجعل تطوير الذكاء الصناعي أكثر سهولة وكفاءة. لمزيد من التفاصيل، ارجع إلى مقال تعرف على مفهوم إطار العمل Framework وأهميته في البرمجة. أطر عمل الذكاء الاصطناعي تبرز أطر عمل الذكاء الاصطناعي باعتبارها الأساس الذي تُبنى عليه تطبيقات الذكاء الاصطناعي، إذ ذكرنا أن هذه الأطر عبارة عن حزم برمجية شاملة مصممة لتبسيط وتسريع عملية إنشاء نماذج وحلول الذكاء الاصطناعي. إنها توفر للمطورين صندوق أدوات مليء بالخوارزميات والمكتبات والأدوات المساعدة المعدة مسبقًا، مما يمكنهم من التركيز على الجوانب التطبيقية والإبداعية للذكاء الاصطناعي مع تجاوز الكثير من كتابة الشيفرات المعقدة منخفضة المستوى. أشهر أطر عمل الذكاء الاصطناعي سنذكر فيما يلي أطر عمل الذكاء الاصطناعي الأكثر شيوعًا والتي يعتمد عليها المطورون في إنشاء مشاريع الذكاء الاصطناعي الخاصة بهم وهي: تنسرفلو Tensorflow باي تورش PyTorch كيراس Keras تنسرفلو Tensorflow: إطار العمل الأكثر استخدامًا يعد تنسرفلو Tensorflow الذي طورته جوجل أحد أكثر أطر عمل الذكاء الاصطناعي مفتوحة المصدر استخدامًا وتنوعًا. يشتهر تنسرفلو بقابلية التوسع والمرونة، فهو تتيح للمطورين إنشاء شبكات عصبية كبيرة ومتطورة لتطبيقات الذكاء الاصطناعي المختلفة. أسلوب تنسرفلو في بناء الشبكات العصبية يُسهّل التوازي، مما يجعلها مثالية للتعامل مع معالجة البيانات على نطاق واسع. تمتد براعة تنسرفلو إلى أبعد من مجرد التعلم العميق. يشتمل نظامها البيئي على أدوات للمعالجة المسبقة للبيانات وتقييم النماذج والنشر. بدءًا من الإصدار 2.0، تبنى إطار العمل هذا نهجًا أكثر سهولة في الاستخدام وأكثر بديهية من خلال تبسيط أسلوب بناء وتنفيذ الشبكات العصبية ودمج إطار العمل كيراس معه. تعمل هذه التحسينات على تمكين المطورين من التركيز على بناء النماذج بدلاً من التورط في تعقيدات التنفيذ. باي تورش PyTorch: إطار عمل الباحثين المفضل يلبي باي تورش PyTorch احتياجات الباحثين وممارسي الذكاء الاصطناعي الذين يحتاجون إلى تحكم دقيق في نماذجهم. طُور باي تورش بواسطة مختبر أبحاث الذكاء الاصطناعي في فيسبوك، ويتبع نموذجًا حوسبيًا ديناميكيًا، مما يسمح للمستخدمين بتعريف نماذجهم وتعديلها وتصحيحها وتجريبها بسهولة. كما أن النظام البيئي ecosystem أو العمل في بيئة باي تورش يعد مرنًا للغاية. على الرغم من أن الطبيعة الديناميكية لباي تورش توفر مزايا في البحث والتجريب، فقد تأتي على حساب الأداء في سيناريوهات إنتاج معينة. الجهود الأخيرة (مثل إدخال TorchScript) تهدف إلى سد هذه الفجوة لجعل باي تورش خيارًا متعدد الاستخدامات لكل من البحث والنشر. كيراس Keras: تبسيط التعلم العميق باستخدام واجهة برمجة تطبيقات سهلة الاستخدام ظهرت كيراس Keras، التي غالبًا ما توصف بأنها واجهة برمجة تطبيقات، كإطار عمل لكل من المبتدئين وحتى الممارسين ذوي الخبرة في مجال الذكاء الاصطناعي على حد سواء. طُور في البداية كمشروع مستقل مفتوح المصدر، وقد تم الآن دمجه بالكامل مع تنسرفلو. تكمن قوته الأساسية في بساطته وسهولة استخدامه. يتخلص كيراس من تعقيدات تنفيذ النموذج، مما يسمح للمطورين بإنشاء شبكات عصبية معقدة ببضعة أسطر من التعليمات البرمجية. يتبع كيراس نموذج برمجة تصريحي عالي المستوى، مما يجعله مثاليًا للتجربة السريعة والنماذج الأولية، حيث تتطلّب عملية بناء النماذج والتدريب الحد الأدنى من التعليمات البرمجية. تأتي هذه البساطة مع مفاضلة، إذ قد تفتقر كيراس إلى المرونة والتحكّم الدقيق الذي توفره أطر العمل ذات المستوى الأدنى مثل تنسرفلو وباي تورش. أطر عمل الذكاء الاصطناعي الأقل شهرة بالإضافة إلى أطر عمل الذكاء الاصطناعي المعروفة مثل تنسرفلو و باي تورش وكيراس، هناك العديد من الأطر القوية الأخرى التي اكتسبت شعبية في مجتمع الذكاء الاصطناعي. نذكر منها: كافي Caffe: طوره مركز BVLC ويتميز بسرعته وكفاءته في مهام تصنيف الصور. سينتك CNTK: يوفر أداءً عاليًا وقابلية للتوسع لمهام التعلم العميق. وهو يدعم العديد من هياكل الشبكات العصبية وقد تم استخدامه في مهام تتراوح من التعرف على الصور والكلام إلى معالجة اللغة الطبيعية. إم إكس نت MXNet: طورته شركة أباتشي Apache، هو إطار عمل بارز آخر مصمم لتحقيق أهداف الكفاءة والمرونة. يتميّز بقدرته على التكيف مع الشبكات العصبية الديناميكية. ساهم دعمه للغات برمجية متعددة، بما في ذلك بايثون وجوليا، في اعتماده على نطاق واسع. فاست إي آي Fast.ai: هو إطار عمل يتميز بتركيزه على إضفاء الطابع الديمقراطي على تعليم الذكاء الاصطناعي (جعل تعليم الذكاء الصناعي وموارده في متناول مجموعة واسعة من الأشخاص، بغض النظر عن خلفيتهم أو خبرتهم). يوفر واجهات برمجة تطبيقات ومكتبات سهلة الاستخدام لتبسيط مهام التعلم العميق المعقدة. هذا يجعله اختيارًا ممتازًا للمبتدئين والباحثين الذين يرغبون في تجربة نماذج الذكاء الاصطناعي ونماذجها بسرعة. أخيرًا ثيانو Theano: استخدم على نطاق واسع في الماضي لمهام التعلم العميق بسبب حسابه الفعال للتعابير الرياضية. على الرغم من أن ثيانو لم يعد نشطًا كما في السابق، إلا أنه لعب دورًا مهمًا في تشكيل مشهد أطر عمل الذكاء الاصطناعي الصناعي. تستمر هذه الأطر، جنبًا إلى جنب مع غيرها من الأطر التي لم تُذكر مثل تشينر Chainer و Deeplearning4j وأونكس ONNX، في إثراء برمجة الذكاء الاصطناعي، مما يوفر خيارات متنوعة للمطورين والباحثين للاستكشاف والابتكار في مجال الذكاء الاصطناعي. اختيار إطار العمل المناسب لمشروعك يمكن أن يؤثر اختيار إطار العمل بشكل كبير على فعالية وكفاءة مشاريع الذكاء الاصطناعي. نتيجةً لكون مجال الذكاء الاصطناعي متنوع وديناميكي، ظهرت بعض الأطر كمعايير صناعية نظرًا لتعدد استخداماتها وأدائها وأدواتها الواسعة المصممة لمهام الذكاء الاصطناعي. يعد فهم نقاط القوة والضعف في مختلف أطر العمل أمرًا ضروريًا لاتخاذ قرارات مستنيرة عند البدء في مشاريع الذكاء الاصطناعي. تلعب عوامل مثل تعقيد النموذج وسهولة نشر النموذج ودعم المجتمع والتكامل مع التقنيات الأخرى دورًا في تحديد الأدوات التي تتوافق مع أهداف المشروع. نشرع في هذا القسم للحديث عن هذه العوامل بشيء من التفصيل. هنالك عدة عوامل يجب مراعاتها لمتطلبات المشروع ومهارات الفريق والأهداف، إذ يتضمن اختيار إطار العمل لمشروع الذكاء الاصطناعي الخاص بك تحليلًا مدروسًا لعدة عوامل. تشكل هذه العوامل مجتمعة الأساس الذي سيُبنى عليه مشروعك، نذكر منها: متطلبات المشروع: تُعد طبيعة مشروع الذكاء الصناعي الخاص بك اعتبارًا أساسيًا. هل تقوم بتطوير تطبيق رؤية حاسب أو أداة معالجة لغة طبيعية أو نظام توصية؟ قد يستفيد كل مجال من مجموعة مختلفة من الأدوات. التعقيد: ضع في اعتبارك مدى تعقيد المشكلة التي تحاول حلها. تتفوق بعض الأطر في التعامل مع العمليات الحسابية المعقدة، بينما يتناسب البعض الآخر بشكل أفضل مع المهام الأبسط. قابلية التوسع: هل سيحتاج مشروعك إلى التوسع مع البيانات المتزايدة ومتطلبات المستخدمين؟ توفر بعض الأطر خيارات أفضل لتحسين الأداء وقابلية التوسع. المجتمع والتوثيق: يمكن أن تكون المجتمعات القوية والوثائق الشاملة ذات قيمة لا تقدر بثمن عند استكشاف المشكلات وإصلاحها أو طلب التوجيه. مهارات الفريق: قم بتقييم خبرة فريق التطوير لديك. يمكن أن يؤدي اختيار لغة وإطار عمل مألوفين لفريقك إلى تسريع عملية التطوير وضمان مستوى أعلى من جودة التعليمات البرمجية. التكامل: ضع في اعتبارك مدى سهولة تكامل الإطار المختار مع الأدوات والخدمات وأنظمة التشغيل والمنصات الأخرى التي قد تحتاج إلى استخدامها. الصيانة طويلة المدى: التخطيط للمستقبل. تأكد من أن إطار العمل الذي تختاره له خارطة طريق للتحديثات والصيانة. توفر الموارد: يمكن أن يؤثر توفر الأدوات والموارد بشكل كبير على سرعة التطوير وكفاءته. سهولة الاستخدام: بعض الأطر أكثر سهولة في الاستخدام، مما يجعلها مثالية للمبتدئين. يوفر البعض الآخر مزيدًا من التحكم والمرونة للمطورين ذوي الخبرة. يعد اختيار لغة البرمجة والإطار المناسبين لمشروع الذكاء الاصطناعي الخاص بك قرارًا استراتيجيًا يتطلب تقييمًا شاملاً لمتطلبات المشروع وقدرات الفريق والأهداف طويلة المدى. توفر الخيارات الكثيرة المتاحة اليوم للمطورين الأدوات اللازمة لإنشاء تطبيقات مبتكرة وقوية. من خلال النظر في هذه العوامل والاستلهام من دراسات الحالة الناجحة، يمكن للمطورين وضع مشاريعهم على طريق النجاح. قيود استخدام أطر عمل الذكاء الاصطناعي يمكن أن تؤثر القيود المرافقة لأطر عمل الذكاء الاصطناعي على تطوير تطبيقات الذكاء الاصطناعي ونشرها. هناك العديد من القيود، ويمكن أن تختلف هذه القيود بناءً على إطار العمل المحدد. تتضمن بعض القيود الشائعة ما يلي: الأداء: قد تواجه أطر عمل معينة للذكاء الاصطناعي صعوبة في التوسع للتعامل مع مجموعات البيانات الكبيرة أو النماذج المعقدة بكفاءة. يمكن أن ينتج عن ذلك أوقات تدريب أبطأ (بعض النماذج تحتاج أيام) وتطبيقات أقل استجابة (أي تستغرق وقت أطول لكي تعطيك النتيجة). التوافق: قد لا تكون بعض أطر عمل الذكاء الصناعي متوافقة مع أجهزة أو منصات معينة، مما يحد من قابليتها للاستخدام في بيئات معينة. التعقيد: يمكن أن تكون عملية بناء بعض نماذج الذكاء الصناعي غير سهلة، خاصة للمبتدئين. يمكن أن تعيق الأطر التي تفتقر إلى التوثيق والدعم عملية التعلّم. الافتقار إلى المرونة: قد تُقيّد بعض أطر عمل الذكاء الاصطناعي المطورين على خوارزميات ونماذج محددة مسبقًا، مما يقلل من مرونة تجربة الحلول المخصصة. قابلية التشغيل البيني Interoperability: تشير إلى القدرة على تبادل البيانات أو الوظائف بين نظم مختلفة أو برمجيات مختلفة دون الحاجة إلى تعديل هذه البرمجيات. يعد التحقق من قابلية التشغيل البيني أمرًا مهمًا في عالم تطوير البرمجيات والتكنولوجيا. يمكن أن تؤدي قابلية التشغيل البيني المحدودة بين أطر الذكاء الاصطناعي المختلفة ولغات البرمجة إلى إعاقة تكامل الأدوات والتقنيات المتعددة. ** الدعم المحدود**: قد يكون لبعض أطر عمل الذكاء الاصطناعي قاعدة مستخدمين أصغر، مما يؤدي إلى دعم مجتمعي محدود. مخاوف أمنية: يمكن أن تتسبب أطر عمل الذكاء الاصطناعي في حدوث ثغرات أمنية، مما قد يؤدي إلى كشف بيانات حساسة. منحنى التعلم: يمكن أن يؤدي تعقيد بعض أطر عمل الذكاء الاصطناعي إلى منحنى تعليمي حاد (أي ليس من السهل تعلمها)، مما يتطلب وقتًا وجهدًا كبيرين حتى تصبح بارعًا. على الرغم من هذه القيود، يعمل البحث والتطوير المستمر على مواجهة هذه التحديات. مكتبات الذكاء الاصطناعي إلى جانب أطر عمل الذكاء الاصطناعي، تلعب المكتبات دورًا مهمًا في تحويل الخوارزميات المعقدة إلى أدوات يسهل الوصول إليها. في حين أن أطر العمل مثل TensorFlow و PyTorch تهيمن على مشهد الذكاء الاصطناعي، فمن الضروري التعرف على خيارات أخرى تُستخدم في تطوير الذكاء الاصطناعي وهي المكتبات. عكس أطر العمل، صممت المكتبات لتبسيط مهام محددة، حيث تقدم للمطورين وحدات ودوال مُعدة مسبقًا يمكن دمجها بسهولة في مشاريعهم. دعونا نستكشف أهمية مكتبات الذكاء الاصطناعي. أشهر مكتبات الذكاء الاصطناعي البارزة هناك عدد كبير من المكتبات المستخدمة في برمجة الذكاء الاصطناعي وكل منها يُستخدم ضمن لغة برمجة معين. تتضمّن بايثون الحصة الأكبر من مكتبات الذكاء الاصطناعي، فلديها مجموعة واسعة من المكتبات التي تلبي مهام الذكاء الاصطناعي المختلفة. توفر هذه المكتبات للمطورين أدوات ودوال جاهزة لتبسيط عملية التطوير، من المعالجة المسبقة للبيانات إلى التدريب النموذجي والتقييم. فيما يلي بعض المكتبات الأساسية الضرورية لتطوير الذكاء الاصطناعي. هاغينغ فيس Hugging Face هاغينغ فيس Hugging Face اسم معروف في عالم الذكاء الاصطناعي وهي الخيار رقم واحد في معالجة اللغة الطبيعية، إذ قدمت مساهمات ملحوظة في مجال معالجة اللغات الطبيعية NLP. تأسست شركة Hugging Face في عام 2016، وقد اكتسبت شهرة بسبب مكتبتها مفتوحة المصدر، مثل مكتبة المحولات Transformers. توفر هذه المكتبة مجموعة واسعة من مجموعات البيانات والنماذج المدربة مسبقًا لمجموعة واسعة من مهام معالجة اللغات الطبيعية، بما في ذلك تصنيف النصوص وترجمة اللغة ونماذج اللغة وتحليل المشاعر. ما يميز Hugging Face هو التزامها بإضفاء الطابع الديمقراطي على الذكاء الاصطناعي ومعالجة اللغات الطبيعية من خلال جعل النماذج المتطورة في متناول المطورين والباحثين في جميع أنحاء العالم. مع مجتمع مزدهر من المساهمين، تُواصل Hugging Face الابتكار وقيادة التقدم وتعزيز التعاون في عالم معالجة اللغات الطبيعية. لقد أصبح مصدرًا لا غنى عنه لأي شخص يعمل في مشاريع الذكاء الاصطناعي المتعلقة باللغة. نمباي Numpy مكتبة خاصة بلغة بايثون وهي أساس العديد من مشاريع الذكاء الاصطناعي. توفر دعمًا للمصفوفات الكبيرة والمعقّدة والمتعددة الأبعاد، جنبًا إلى جنب مع مجموعة واسعة من الدوال الرياضية لعمليات المصفوفة. هذه المكتبة لا غنى عنها للمهام التي تنطوي على حسابات رقمية ومعالجة البيانات. باندا Pandas مكتبة خاصة بلغة بايثون. هي مكتبة قوية لتحليل البيانات ومعالجتها. يسمح هيكل DataFrame الخاص بها للمطورين بالتعامل مع البيانات المهيكلة ومعالجتها بكفاءة، مما يجعل المهام مثل تنظيف البيانات وتحويلها وتجميعها أكثر ملاءمة. سكايت ليرن scikit-Learn مكتبة خاصة بلغة بايثون. تُعرف هذه المكتبة أيضًا باسم sklearn، وهي نقطة انطلاق لمهام التعلم الآلي. يقدم مجموعة متنوعة من خوارزميات التعلم الآلي للتصنيف والانحدار والتكتّل وتقليل الأبعاد والمزيد. توفر بالإضافة إلى ذلك أدوات لتقييم النموذج واختيار الميزات والمعالجة المسبقة للبيانات. مجموعة أدوات اللغة الطبيعية NLTK مكتبة خاصة بلغة بايثون مصممة خصيصًا لمهام معالجة اللغة الطبيعية. توفر أدوات وموارد للتقطيع tokenization والتشذيب stemming، وتصنيف أجزاء الكلام POST، وتحليل المشاعر ، وأكثر من ذلك ، مما يجعله ضروريًا للمشاريع التي تتضمن بيانات نصية. سباسي spaCy مكتبة خاصة بلغة بايثون. مكتبة قوية أخرى لمهام معالجة اللغة الطبيعية، تركز سبايسي على توفير إمكانيات لمعالجة اللغة الطبيعية بطريقة سريعة وفعّالة. كما أنها تتفوق في مهام مثل التعرف على الكيانات المسماة NER وتحليل التبعية والتحليل اللغوي. جينسم Gensim مكتبة خاصة بلغة بايثون مصممة لنمذجة الموضوعات وتحليل تشابه المستندات. إنها مفيدة بشكل خاص للعمل مع مجموعات نصية كبيرة وإنشاء تمثيلات رقمية للنصوص باستخدام تقنيات مثل Word2Vec. إكس جي بوست XGBoost مكتبة خاصة بلغة بايثون ولغة R وهي مكتبة شهيرة وحديثة للتعلم الآلي. تحظى بشعبية خاصة بالنسبة للبيانات المُهيكلة (كالتي تُنظّم في جداول) وتستخدم على نطاق واسع في مسابقات التعلم الآلي. مكتبة OpenCV مكتبة خاصة بلغة بايثون ولغة C++‎ وتعد مصدرًا قويًا لمهام الرؤية الحاسوبية. فهي توفر أكثر من 2500 خوارزمية محسنة لتحليل الصور والفيديو في الوقت الفعلي. من التعرف على الوجه إلى اكتشاف الأشياء، تعد OpenCV واحدة من المكتبات المفضلة لمطوري الرؤية الحاسوبية. مكتبتي forecast و tseries وهي مكتبات خاصة بلغة R تُستخدم في تحليل السلاسل الزمنية، حيث تسهل حزم forecast و tseries التنبؤ بالبيانات المعتمدة على الوقت وتحليلها. مكتبتي sp و sf مكتبات خاصة بلغة R تُستخدم في التحليل المكاني، حيث تُمكّن المستخدمين من معالجة البيانات المكانية وتحليلها. هذه كانت مجموعة من أهم مكتبات الذكاء الاصطناعي. تعمل هذه المكتبات بشكل جماعي على تمكين المطورين من إنشاء تطبيقات ذكاء اصطناعي معقدة عبر مجالات متنوعة. من خلال الاستفادة من قدرات هذه المكتبات، يمكن لممارسي الذكاء الاصطناعي تسريع عملية التطوير وإنشاء نماذج أكثر قوة ودقة. فوائد مكتبات الذكاء الاصطناعي كما أشرنا سابقًا، هناك العديد من الفوائد التي يمكن الحصول عليها من خلال إنشاء واستخدام مكتبات الذكاء الاصطناعي، وهي كما يلي: سهولة الاستخدام: تعمل المكتبات على تبسيط الخوارزميات المعقدة، مما يسهل على المطورين فهم حلول الذكاء الاصطناعي وتنفيذها دون الخوض في تعقيدات الخوارزميات الأساسية. النماذج الأولية السريعة: يمكن للمطورين إنشاء نماذج أولية سريعة لأفكارهم باستخدام المكتبات، مما يسمح لهم باختبار الفرضيات وتجربة أساليب مختلفة قبل الالتزام بحل محدد. دعم المجتمع: تمامًا مثل أطر العمل، تتمتع المكتبات الشعبية بمجتمعات نابضة بالحياة. تساهم هذه المجتمعات في المكتبات من خلال توفير التحديثات وإصلاحات الأخطاء والوظائف الإضافية، مما يضمن بقاء المكتبات قوية ومحدثة. قابلية التخصيص: توفر المكتبات التوازن بين الحلول المعدة مسبقًا والتخصيص. يمكن للمطورين تعديل أجزاء معينة من كود المكتبة لتخصيص الدالة وفقًا لمتطلبات مشروعهم. كفاءة الموارد: تتميز المكتبات عمومًا بقلة حجمها مقارنةً بأطر العمل، مما يجعلها مناسبة للبيئات المحدودة الموارد مثل أجهزة إنترنت الأشياء. خاتمة لقد أضفت مكتبات وأطر الذكاء الاصطناعي قوةً كبيرةً على تطوير الذكاء الاصطناعي، مما مكّن كلاً من المحترفين المتمرسين والوافدين الجدد من تسخير قوة الذكاء الاصطناعي. سواء كنت تقوم ببناء روبوتات محادثة ذكية أو مركبات ذاتية التحكم أو أنظمة تحليلات تنبؤية، فإن هذه المكتبات والأطر تعمل كحلفاء لك في مجال الذكاء الصناعي. إن فهم مشهد مكتبا وأطر عمل الذكاء الاصطناعي هو الخطوة الأولى في الشروع في رحلة لإنشاء الجيل القادم من الأنظمة الذكية التي ستشكل عالمنا. اقرأ أيضًا تعلم الذكاء الاصطناعي لغات برمجة الذكاء الاصطناعي خوارزميات الذكاء الاصطناعي أهمية الذكاء الاصطناعي فوائد الذكاء الاصطناعي
  15. إحدى الخصائص المهمة في البرامج الحديثة هو إمكانية التواصل مع البرامج الأخرى، سواءٌ كان برنامج جو يتحقق ما إذا كان لدى المستخدم حق الوصول إلى برنامج آخر، أو برنامج جافا سكريبت JavaScript يحصل على قائمة بالطلبات السابقة لعرضها على موقع ويب، أو برنامج رست Rust يقرأ نتائج اختبار من ملف، فهناك حاجة إلى طريقة نزوّد البرامج من خلالها بالبيانات. لدى أغلب لغات البرمجة طريقة خاصة في تخزين البيانات داخليًّا، والتي لا تفهمها اللغات البرمجية الأخرى. للسماح لهذه اللغات بالتفاعل مع بعضها، يجب تحويل البيانات إلى تنسيق أو صيغة مشتركة يمكنهم فهمها جميعًا. إحدى هذه الصيغ هي صيغة جسون JSON، إنها وسيلة شائعة لنقل البيانات عبر الإنترنت وكذلك بين البرامج في نفس النظام. تمتلك لغة جو والعديد من لغات البرمجة الأخرى طريقة لتحويل البيانات من وإلى صيغة جسون في مكتباتها القياسية. سنُنشئ في هذا المقال برنامجًا يستخدم حزمة encoding/json لتحويل صيغة البيانات من النوع map إلى بيانات جسون، ثم من النوع struct إلى جسون، كما سنتعلم كيفية إجراء تحويل عكسي. المتطلبات إصدار مُثبّت من جو 1.16 أو أعلى، ويمكنك الاستعانة بمقال تثبيت لغة جو Go وإعداد بيئة برمجة محلية على أبونتو Ubuntu لإعداده. تثبيت لغة جو وإعداد بيئة برمجة محلية على نظام ماك macOS. تثبيت لغة جو وإعداد بيئة برمجة محلية على ويندوز. (اختياري) معرفة بكيفية التعامل مع التاريخ والوقت في لغة جو. يمكنك الاطلاع على مقالة استخدام التاريخ والوقت في لغة جو Go. معرفة مسبقة بصيغةجسون. معرفة مسبقة بكيفية التعامل مع وسوم البنية Struct tags لتخصيص حقول البنية. يمكنك الاطلاع على مقال استخدام وسوم البنية Struct Tags في لغة جو. استخدام الروابط Maps لتوليد بيانات بصيغة JSON توفر حزمة encoding/json بعض الدوال لتحويل البيانات من وإلى جسون JSON. الدالة الأولى هي json.Marshal. التنظيم Marshaling أو المعروف أيضًا باسم السلسلة Serialization، هو عملية تحويل بيانات البرنامج من الذاكرة إلى تنسيق يمكن نقله أو حفظه في مكان آخر، وهذا ما تفعله الدالة json.Marshal في لغة جو، إذ تحول بيانات البرنامج قيد التشغيل (الموجود في الذاكرة) إلى بيانات جسون. تقبل هذه الدالة أي قيمة من النوع واجهة {}interface لتنظيمها بصيغة جسون، لذلك يُسمح بتمرير أي قيمة مثل معامل، لتُعيد الدالة البيانات ممثلة بصيغة جسون في النتيجة. سننشئ في هذا القسم برنامجًا يستخدم دالة json.Marshal لإنشاء ملف جسون يحتوي أنواعًا مختلفة من البيانات من قيم map، ثم سنطبع هذه القيم على شاشة الخرج. تُمثّل بيانات جسون غالبًا على شكل كائن مفاتيحه من سلاسل نصيّة وقيمه من أنواع مختلفة، وهذا يُشبه آلية تمثيل البيانات في روابط جو، لذا فإن الطريقة الأفضل لإنشاء بيانات جسون في لغة جو هي وضع البيانات ضمن رابطة map مع مفاتيح من النوع string وقيم من النوع {}interface. تٌفسّر مفاتيح map مباشرةً على أنها مفاتيح جسون، ويمكن أن تكون قيم النوع interface أي نوع بيانات في لغة جو، مثل int، أو string، أو حتى {}map[string]interface لبدء استخدام الحزمة encoding/json، وكما هو معتاد، سنحتاج لبدء إنشاء برامجنا إلى إنشاء مجلد للعمل ووضع الملفات فيه، ويمكن وضع المجلد في أي مكان على الحاسب، إذ يكون للعديد من المبرمجين عادةً مجلدٌ يضعون داخله كافة مشاريعهم. سنستخدم في هذا المقال مجلدًا باسم "projects"، لذا فلننشئ هذا المجلد وننتقل إليه: $ mkdir projects $ cd projects الآن، من داخل هذا المجلد، سنشغّل الأمر mkdir لإنشاء مجلد "jsondata" ثم سنستخدم cd للانتقال إليه: $ mkdir jsondata $ cd jsondata يمكننا الآن فتح ملف "main.go" باستخدام محرر نانو nano أو أي محرر آخر تريده: $ nano main.go نضيف داخل ملف "main.go" دالة main لتشغيل البرنامج، ثم نضيف قيمة {}map[string]interface مع مفاتيح وقيم من أنواع مختلفة، ثم نستخدم الدالة json.Marshal لتحويل بيانات map إلى بيانات جسون: package main import ( "encoding/json" "fmt" ) func main() { data := map[string]interface{}{ "intValue": 1234, "boolValue": true, "stringValue": "hello!", "objectValue": map[string]interface{}{ "arrayValue": []int{1, 2, 3, 4}, }, } jsonData, err := json.Marshal(data) if err != nil { fmt.Printf("could not marshal json: %s\n", err) return } fmt.Printf("json data: %s\n", jsonData) } نلاحظ في المتغير data أن كل قيمة لديها مفتاح string، لكن قيم هذه المفاتيح تختلف، فأحدها int وأحدها bool والأخرى هي رابط {}map[string]interface مع قيم int بداخلها. عند تمرير المتغير data إلى json.Marshal، ستنظر الدالة إلى جميع القيم التي يتضمنها الرابط وتحدد نوعها وكيفية تمثيلها في جسون، وإذا حدثت مشكلة في تفسير بيانات الرابط، ستُعيد خطأ يصف المشكلة. إذا نجحت العملية، سيتضمّن المتغير jsonData بيانات من النوع byte[] تُمثّل البيانات التي جرى تنظيمها إلى صيغة جسون. بما أن byte[] يمكن تحويلها إلى قيمة string باستخدام (myString := string(jsonData أو العنصر النائب s% ضمن تنسيق سلسلة، يمكننا طباعة بيانات جسون على شاشة الخرج باستخدام دالة الطباعة fmt.Printf. بعد حفظ وإغلاق الملف، لنُشغّل ملف البرنامج "main.go" من خلال الأمر go run: $ go run main.go سيكون الخرج على النحو التالي: json data: {"boolValue":true,"intValue":1234,"objectValue":{"arrayValue":[1,2,3,4]},"stringValue":"hello!"} نلاحظ من الخرج أن قيمة جسون هي كائن مُمثّل بأقواس معقوصة curly braces {} تحيط به، وأن جميع قيم المتغير data موجودة ضمن هذين القوسين. نلاحظ أيضًا أن رابط المفتاح objectValue الذي هو {}map[string]interface قد جرى تفسيره إلى كائن جسون آخر مُمثّل بأقواس معقوصة {} تحيط به أيضًا، ويتضمن أيضًا المفتاح arrayValue بداخله مع مصفوفة القيم المقابلة [1،2،3،4]. ترميز البيانات الزمنية في جسون لا تقتصر قدرات الحزمة encoding/json على إمكانية تمثيل البيانات من النوع string و int، إذ يمكنها التعامل مع أنواع أعقد مثل البيانات الزمنية من النوع time.Time من الحزمة time. لنفتح ملف البرنامج "main.go" مرةً أخرى ونضيف قيمة من النوع time.Time باستخدام الدالة time.Date: package main import ( "encoding/json" "fmt" "time" ) func main() { data := map[string]interface{}{ "intValue": 1234, "boolValue": true, "stringValue": "hello!", "dateValue": time.Date(2022, 3, 2, 9, 10, 0, 0, time.UTC), "objectValue": map[string]interface{}{ "arrayValue": []int{1, 2, 3, 4}, }, } ... } يؤدي هذا التعديل إلى ضبط التاريخ على March 2, 2022 والوقت على ‎9:10:00 AM في المنطقة الزمنية UTC وربطهم بالمفتاح dateValue. لنُشغّل ملف البرنامج "main.go" من خلال الأمر go run: $ go run main.go سيعطي الخرج التالي: json data: {"boolValue":true,"dateValue":"2022-03-02T09:10:00Z","intValue":1234,"objectValue":{"arrayValue":[1,2,3,4]},"stringValue":"hello!"} نلاحظ هذه المرة في الخرج الحقل dateValue ضمن بيانات جسون، وأن الوقت مُنسّقٌ وفقًا لتنسيق RFC 3339، وهو تنسيق شائع يُستخدم لنقل التواريخ والأوقات على أنها قيم string. ترميز قيم Null في جسون قد نحتاج إلى التعامل مع قيم null، وتحويلها إلى صيغة جسون أيضًا. يمكن لحزمة encoding/json تولي هذه المهمة أيضًا، إذ يمكننا التعامل مع قيم nil (تُقابل قيم null) مثل أي قيمة من نوع آخر ضمن الرابط. لنفتح ملف "main.go" ولنضع قيمتي null ضمن الرابط: ... func main() { data := map[string]interface{}{ "intValue": 1234, "boolValue": true, "stringValue": "hello!", "dateValue": time.Date(2022, 3, 2, 9, 10, 0, 0, time.UTC), "objectValue": map[string]interface{}{ "arrayValue": []int{1, 2, 3, 4}, }, "nullStringValue": nil, "nullIntValue": nil, } ... } وضعنا في الشيفرة أعلاه قيمتي null مع مفتاحين مختلفين، هما nullStringValue و nullIntValue على التوالي، وعلى الرغم من أن أسماء المفاتيح تُشير إلى قيم string و int، لكن هي ليست كذلك (مجرد أسماء). طبعًا كل القيم ضمن الرابط مُشتقة من النوع {}interface والقيمة nil هي قيمة مُحتملة لهذا النوع وبالتالي تُفسّر على أنها null فقط، وهذا كل شيء. لنُشغّل ملف البرنامج "main.go" من خلال الأمر go run: $ go run main.go سيكون الخرج كما يلي: json data: {"boolValue":true,"dateValue":"2022-03-02T09:10:00Z","intValue":1234,"nullIntValue":null,"nullStringValue":null,"objectValue":{"arrayValue":[1,2,3,4]},"stringValue":"hello!"} نلاحظ في الخرج أن الحقلين nullIntValue و nullStringValue مُضمنان مع قيمة null لكل منهما، بالتالي تمكنا من استخدام {}map[string]interface مع قيم null دون أية مشاكل. أنشأنا في هذا القسم برنامجًا يمكنه تحويل قيم بيانات من النوع {}map[string]interface إلى بيانات جسون. أضفنا بعد ذلك حقلًا يستخدم بيانات زمنية من النوع time.Time ضمن الرابط، وأخيرًا أضفنا حقلين يستخدمان القيمة null. بالرغم من مرونة استخدام {}map[string]interface لتحويل البيانات إلى بيانات جسون، إلا أنه قد يكون عُرضةً لحدوث أخطاء غير مقصودة، ولا سيما إذا كنا بحاجة إلى إرسال نفس البيانات إلى عدة أماكن. إذا أرسلنا نُسخًا من هذه البيانات إلى أكثر من مكان ضمن الشيفرة، فربما نُغيّر عن طريق الخطأ اسم حقل أو نضع قيمة غير صحيحة ضمن حقل. في هكذا حالات ربما يكون من المفيد استخدام النوع struct لتمثيل البيانات التي نُريد تحويلها إلى جسون. استخدام البنى Structs لتوليد بيانات بصيغة جسون تُعَدّ جو لغةً ثابتة الأنواع statically-typed language مثل لغة C وجافا Java و ++C، وهذا يعني أن كل تعليمة في البرنامج تُفحَص في وقت التصريف. تتمثل فائدة ذلك في السماح للمُصرّف باستنتاج نوع المتغيرات والتحقق منها وفرض التناسق بين قيم المتغيرات. تستفيد الحزمة encoding/json من ذلك من خلال تعريف بنية struct تُمثّل بيانات جسون. يمكننا التحكم في كيفية تفسير البيانات التي تتضمنها البنية باستخدام وسوم البنية Struct tags. سنعدّل البرنامج السابق خلال هذا القسم، لاستخدام بنية بدلًا من رابط، لتوليد بيانات جسون. عند استخدام struct لتعريف بيانات جسون، يجب علينا تصدير أسماء الحقول (وليس اسم النوع struct نفسه) التي نريد تحويلها إلى جسون، وذلك fأن نبدأ أسماء الحقول بحرف كبير (أي بدلًا من كتابة intValue نكتب IntValue) وإلا لن تكون الحزمة encoding/json قادرةً على الوصول إلى هذه الحقول لتحويلها إلى جسون. الآن بالنسبة لأسماء الحقول، إذا لم نستخدم وسوم البنية للتحكم في تسمية هذه الحقول، ستُفسّر كما هي مباشرةً ضمن البنية. قد يكون استخدام الأسماء الافتراضية هو ما نريده في بيانات جسون، وذلك وفقًا للطريقة التي نرغب بها بتنسيق بياناتنا، وبذلك لن نحتاج في هذه الحالة إلى أية وسوم. يستخدم العديد من المبرمجين تنسيقات أسماء، مثل intValue، أو int_value مع حقول البيانات، وسنحتاج في هذه الحالة إلى وسوم البنية للتحكم في كيفية تفسير هذه الأسماء. سيكون لدينا في المثال التالي بنية struct مع حقل وحيد اسمه IntValue وسنحول هذه البنية إلى صيغة جسون: type myInt struct { IntValue int } data := &myInt{IntValue: 1234} إذا حوّلنا المتغير data إلى صيغة جسون باستخدام الدالة json.Marshal، سنرى الخرج التالي: {"IntValue":1234} لكن لو كنا نريد أن يكون اسم الحقل في ملف جسون هو intValue بدلًا من IntValue، سنحتاج إلى إخبار encoding/json بذلك. بما أن json.Marshal لا تعرف ماذا نتوقع أن يكون اسم بيانات جسون، سنحتاج إلى إخبارها من خلال إضافة وسم البنية json بعد اسم الحقل مباشرةً مع إرفاقه بالاسم الذي نريد أن يظهر به في صيغة جسون. إذًا، من خلال إضافة هذا الوسم إلى الحقل IntValue مع الاسم الذي نريد يظهر به intValue، ستستخدم الدالة json.Marshal الاسم الذي نريده اسمًا للحقل ضمن صيغة جسون: type myInt struct { IntValue int `json:"intValue"` } data := &myInt{IntValue: 1234} إذا حوّلنا المتغير data إلى صيغة جسون باستخدام الدالة json.Marshal، سنرى الخرج التالي، وكما نلاحظ فإنه يستخدم اسم الحقل الذي نريده: {"intValue":1234} سنعدل البرنامج الآن لتعريف نوع بيانات struct يُمكن تحويله إلى بيانات جسون. سنضيف بنيةً باسم myJSON لتمثيل البيانات بطريقة يمكن تحويلها إلى جسون ونضيف البنية myObject التي ستكون قيمة للحقل ObjectValue ضمن البنية myJSON. سنضيف أيضًا وسمًا لكل اسم حقل ضمن البنية myJSON لتحديد الاسم الذي نريد أن يظهر به الحقل ضمن بيانات جسون. يجب أيضًا أن نحدّث الإسناد الخاص بالمتغير dataبحيث نسند له بنية myJSON مع التصريح عنه بنفس الطريقة التي نتعامل مع بنى جو الأخرى. ... type myJSON struct { IntValue int `json:"intValue"` BoolValue bool `json:"boolValue"` StringValue string `json:"stringValue"` DateValue time.Time `json:"dateValue"` ObjectValue *myObject `json:"objectValue"` NullStringValue *string `json:"nullStringValue"` NullIntValue *int `json:"nullIntValue"` } type myObject struct { ArrayValue []int `json:"arrayValue"` } func main() { otherInt := 4321 data := &myJSON{ IntValue: 1234, BoolValue: true, StringValue: "hello!", DateValue: time.Date(2022, 3, 2, 9, 10, 0, 0, time.UTC), ObjectValue: &myObject{ ArrayValue: []int{1, 2, 3, 4}, }, NullStringValue: nil, NullIntValue: &otherInt, } ... } تُشبه معظم التغييرات في الشيفرة أعلاه ما فعلناه في المثال السابق مع الحقل IntValue، إلا أن هناك بعض الأشياء تستحق الإشارة إليها. أحد هذه الأشياء هو الحقل ObjectValue الذي يستخدم قيمةً مرجعية myObject* لإخبار دالة json.Marshal -التي تؤدي عملية التنظيم- إلى وجود قيمة مرجعية من النوع myObject أو قيمة nil. بهذه الطريقة نكون قد عرّفنا كائن جسون بأكثر من طبقة، وفي حال كانت هذه الطريقة مطلوبة، سيكون لدينا بنيةً أخرى من نوع struct داخل النوع myObject، وهكذا، وبالتالي نلاحظ أنه بإمكاننا تعريف كائنات جسون أعقد وأعقد باستخدام أنواع struct وفقًا لحاجتنا. واحد من الأشياء الأخرى التي تستحق الذكر هي الحقلين NullStringValue و NullIntValue، وعلى عكس StringValue و IntValue؛ أنواع هذه القيم هي أنواع مرجعية int* و string*، وقيمها الافتراضية هي قيم صفريّة أي nil وهذا يُقابل القيمة 0 لنوع البيانات int والسلسلة الفارغة '' لنوع البيانات string. يمكننا من الكلام السابق أن نستنتج أنه في حال أردنا التعبير عن قيمة من نوع ما تحتمل أن تكون nil، فيجب أن نجعلها قيمةً مرجعية. مثلًا لو كنا نريد أن نعبر عن قيمة حقل تُمثّل إجابة مُستخدم عن سؤال ما، فهنا قد يُجيب المُستخدم عن السؤال أو قد لا يُجيب (نضع niil). نُعدّل قيمة الحقل NullIntValue بضبطه على القيمة 4321 لنُظهر كيف يمكن إسناد قيمة لنوع مرجعي مثل int*. تجدر الإشارة إلى أنه في لغة جو، يمكننا إنشاء مراجع لأنواع البيانات الأولية primitive types فقط، مثل int و string باستخدام المتغيرات. إذًا، لإسناد قيمة إلى الحقل NullIntValue، نُسند أولًا قيمةً إلى متغير آخر otherInt، ثم نحصل على مرجع منه otherInt& (بدلًا من كتابة 4321& مباشرةً). لنُشغّل ملف البرنامج "main.go" من خلال الأمر go run: $ go run main.go سيكون الخرج على النحو التالي: json data: {"intValue":1234,"boolValue":true,"stringValue":"hello!","dateValue":"2022-03-02T09:10:00Z","objectValue":{"arrayValue":[1,2,3,4]},"nullStringValue":null,"nullIntValue":4321} نلاحظ أن هذا الناتج هو نفسه عندما استخدمنا {}map[string]interface، باستثناء أن قيمة nullIntValue هذه المرة هي 4321 لأن هذه هي قيمة otherInt. يستغرق الأمر في البداية بعض الوقت لتعريف البنية struct وإعداد حقولها، ولكن يمكننا بعد ذلك استخدامها مرارًا وتكرارًا في الشيفرة، وستكون النتيجة هي نفسها بغض النظر عن مكان استخدامها، كما يمكننا تعديلها من مكان واحد بدلًا من تعديلها في كل مكان تتواجد نسخة منها فيه كما في حالة الروابط map. تتيح لنا الدالة json.Marshal إمكانية تحديد الحقول التي نُريد تضمينها في جسون، في حال كانت قيمة تلك الحقول صفريّة (أي Null وهذا يُكافئ 0 في حالة int و false في حالة bool والسلسلة الفارغة في حالة string ..إلخ). قد يكون لدينا أحيانًا كائن جسون كبير أو حقول اختيارية لا نريد تضمينها دائمًا في بيانات جسون، لذا يكون تجاهل هذه الحقول مفيدًا. يكون التحكم في تجاهل هذه الحقول -عندما تكون قيمها صفريّة أو غير صفريّة- باستخدام الخيار omitempty ضمن وسم بنية json. لنُحدّث البرنامج السابق لإضافة الخيار omitempty إلى حقل NullStringValue وإضافة حقل جديد يسمى EmptyString مع نفس الخيار: ... type myJSON struct { ... NullStringValue *string `json:"nullStringValue,omitempty"` NullIntValue *int `json:"nullIntValue"` EmptyString string `json:"emptyString,omitempty"` } ... بعد تحويل البنية myJSON إلى بيانات جسون، سنلاحظ أن الحقل EmptyString والحقل NullStringValue غير موجودان في بيانات جسون، لأن قيمهما صفريّة. لنُشغّل ملف البرنامج "main.go" من خلال الأمر go run: $ go run main.go سيكون الخرج على النحو التالي: json data: {"intValue":1234,"boolValue":true,"stringValue":"hello!","dateValue":"2022-03-02T09:10:00Z","objectValue":{"arrayValue":[1,2,3,4]},"nullIntValue":4321} نلاحظ أن الحقل nullStringValue لم يعد موجودًا في الخرج، لأنه يُعد حقلًا بقيمة nil، بالتالي فإن الخيار omitempty استبعده من الخرج. نفس الأمر بالنسبة للحقل emptyString، لأن قيمته صفريّة (القيمة الصفريّة تُكافئ nil كما سبق وذكرنا). استخدمنا في هذا القسم أسلوب البُنى struct بدلًا من الروابط، لتمثيل كائن جسون في برنامجنا، ثم حولناها إلى بيانات جسون باستخدام الدالة json.Marshal. تعلمنا أيضًا كيفية تجاهل القيم الصفريّة من بيانات جسون. هذه البيانات (بعد تحويلها لصيغة جسون) قد تُرسل إلى برامج أخرى، وبالتالي إذا كان البرنامج الآخر مكتوب بلغة جو، يجب أن نعرف كيف يمكننا قراءة هذا النوع من البيانات. يمكنك أن تتخيل الأمر على أنه برنامجي جو A و B أحدهما مُخدم والآخر عميل. يُرسل A طلبًا إلى B، فيعالجه ويرسله بصيغة جسون إلى A، ثم يقرأ A هذه البيانات. لأجل ذلك توفر الحزمة encoding/json طريقةً لفك ترميز بيانات جسون وتحويلها إلى أنواع جو المقابلة (مجرد عملية عكسية). سنتعلم في القسم التالي كيفية قراءة بيانات جسون وتحويلها إلى روابط. تحليل بيانات جسون باستخدام الروابط بطريقة مشابهة لما فعلناه عندما استخدمنا {}map[string]interface مثل طريقة مرنة لتوليد بيانات جسون، يمكننا استخدامها أيضًا مثل طريقة مرنة لقراءة بيانات جسون. تعمل الدالة json.Unmarshal بطريقة معاكسة للدالة json.Marshal، إذ تأخذ بيانات جسون وتحولها إلى بيانات جو، وتأخذ أيضًا متغيرًا لوضع البيانات التي جرى فك تنظيمها فيه، وتعيد إما خطأ error في حال فشل عملية التحليل أو nil في حال نجحت. سنعدّل برنامجنا في هذا القسم، بحيث نستخدم الدالة json.Unmarshal لقراءة بيانات جسون من سلسلة وتخزينها في متغير من النوع map، وطباعة الخرج على الشاشة. لنعدّل البرنامج إذًا، بحيث نفك تنظيم البيانات باستخدام الدالة السابقة ونحولها إلى رابط {}map[string]interface. لنبدأ باستبدال المتغير data الأصلي بمتغير jsonData يحتوي على سلسلة جسون، ثم نُصرّح عن متغير data جديد على أنه {}map[string]interfac لتلقي بيانات جسون، ثم نستخدم الدالة json.Unmarshal مع هذه المتغيرات للوصول إلى بيانات جسون: ... func main() { jsonData := ` { "intValue":1234, "boolValue":true, "stringValue":"hello!", "dateValue":"2022-03-02T09:10:00Z", "objectValue":{ "arrayValue":[1,2,3,4] }, "nullStringValue":null, "nullIntValue":null } ` var data map[string]interface{} err := json.Unmarshal([]byte(jsonData), &data) if err != nil { fmt.Printf("could not unmarshal json: %s\n", err) return } fmt.Printf("json map: %v\n", data) } جرى إسناد قيمة المتغير jsonData في الشيفرة أعلاه من خلال سلسلة نصية أولية، وذلك للسماح بكتابة البيانات ضمن أسطر متعددة لتسهيل القراءة. بعد التصريح عن المتغير data على أنه متغير من النوع {}map[string]interface، نمرر jsonData والمتغير data إلى الدالة json.Unmarshal لفك تنظيم بيانات جسون وتخزين النتيجة في data. يُمرَّر المتغير jsonData إلى دالة فك التنظيم على شكل مصفوفة بايت byte[]، لأن الدالة تتطلب النوع byte[]، والمتغير jsonData عُرّف على أنه قيمة من نوع سلسلة نصية string. طبعًا هذا الأمر ينجح، لأنه في لغة جو، يمكن تحويل string إلى byte[] والعكس. بالنسبة للمتغير data، ينبغي تمريره مثل مرجع، لأن الدالة تتطلب معرفة موقع المتغير في الذاكرة. أخيرًا، يجري فك تنظيم البيانات وتخزين النتيجة في المتغير data، لنطبع النتيجة بعدها باستخدام دالة fmt.Printf. لنُشغّل ملف البرنامج "main.go" من خلال الأمر go run: $ go run main.go سيكون الخرج على النحو التالي: json map: map[boolValue:true dateValue:2022-03-02T09:10:00Z intValue:1234 nullIntValue:<nil> nullStringValue:<nil> objectValue:map[arrayValue:[1 2 3 4]] stringValue:hello!] يُظهر الخرج نتيجة تحويل بيانات جسون إلى رابط. نلاحظ أن جميع الحقول من بيانات جسون موجودة، بما في ذلك القيم الصفريّة null. بما أن بيانات جو الآن مُخزنة في قيمة من النوع {}map[string]interface، سيكون لدينا القليل من العمل مع بياناتها؛ إذ نحتاج إلى الحصول على القيمة من الرابط باستخدام قيمة مفتاح string معينة، وذلك للتأكد من أن القيمة التي تلقيناها هي القيمة التي نتوقعها، لأن القيمة المعادة هي قيمة من النوع {}interface. لنفتح ملف "main.go" ونُحدّث البرنامج لقراءة حقل dateValue: ... func main() { ... fmt.Printf("json map: %v\n", data) rawDateValue, ok := data["dateValue"] if !ok { fmt.Printf("dateValue does not exist\n") return } dateValue, ok := rawDateValue.(string) if !ok { fmt.Printf("dateValue is not a string\n") return } fmt.Printf("date value: %s\n", dateValue) } استخدمنا في الشيفرة أعلاه المفتاح dateValue لاستخراج قيمة من الرابط بكتابة ["data["dateValue، وخزّنا النتيجة في rawDateValue ليكون قيمةً من النوع {}interface، واستخدمنا المتغير ok للتأكد من أن الحقل ذو المفتاح dateValue موجود ضمن الرابط. استخدمنا بعدها توكيد النوع type assertion، للتأكد من أن rawDateValue هو قيمة string، وأسندناه إلى المتغير dateValue. استخدمنا بعدها المتغير ok للتأكد من نجاح عملية التوكيد. أخيرًا، طبعنا dateValue باستخدام دالة الطباعة fmt.Printf. لنُشغّل ملف البرنامج "main.go" من خلال الأمر go run: $ go run main.go سيكون الخرج كما يلي: json map: map[boolValue:true dateValue:2022-03-02T09:10:00Z intValue:1234 nullIntValue:<nil> nullStringValue:<nil> objectValue:map[arrayValue:[1 2 3 4]] stringValue:hello!] date value: 2022-03-02T09:10:00Z يمكننا أن نلاحظ في السطر الأخير من الخرج استخراج قيمة الحقل dateValue من الرابط map وتغير نوعها إلى string. استخدمنا في هذا القسم الدالة json.Unmarshal لفك تنظيم unmarshal بيانات جسون وتحويلها إلى بيانات في برنامج جو بالاستعانة بمتغير من النوع {}map[string]interface. بعد ذلك استخرجنا قيمة الحقل dateValue من الرابط الذي وضعنا فيه بيانات جسون وطبعناها على الشاشة. الجانب السيء في استخدام النوع {}map[string]interface في عملية فك التنظيم، هو أن مُفسّر اللغة لا يعرف أنواع الحقول التي جرى فك تنظيمها؛ فكل ما يعرفه أنها من النوع {}interface، وبالتالي لا يمكنه أن يفعل شيئًا أكثر من تخمين الأنواع. بالتالي لن يجري فك تنظيم أنواع البيانات المعقدة، مثل time.Time في الحقل dateValue إلى بيانات من النوع time.Time، وإنما تُفسّر على أنها string. تحدث مشكلة مماثلة إذا حاولنا الوصول إلى أي قيمة رقمية number في الرابط بهذه الطريقة، لأن الدالة json.Unmarshal لا تعرف ما إذا كان الرقم من النوع int أو float أو int64 ..إلخ. لذا يكون التخمين الأفضل هو عدّ الرقم من النوع float64 لأنه النوع الأكثر مرونة. نستنتج مما سبق جانب إيجابي للروابط وهو مرونة استخدامها، وجانب سيئ يتجلى بالمشكلات السابقة. هنا تأتي ميزة استخدام البنى لحل المشكلات السابقة. بطريقة مشابهة لآلية تنظيم البيانات من بنية struct باستخدام الدالة json.Marshal لإنتاج بيانات جسون، يمكن إجراء عملية معاكسة باستخدام json.Unmarshal أيضًا كما سبق وفعلنا مع الروابط. يمكننا باستخدام البنى الاستغناء عن تعقيدات توكيد النوع التي عانينا منها مع الروابط، من خلال تعريف أنواع البيانات في حقول البنية لتحديد أنواع بيانات جسون التي يجري فك تنظيمها. هذا ما سنتحدث عنه في القسم التالي. تحليل بيانات جسون باستخدام البنى عند قراءة بيانات جسون، هناك فرصة جيدة لمعرفة أنواع البيانات التي نتلقاها من خلال استخدام البُنى؛ فمن خلال استخدام البُنى، يمكننا منح مُفسّر اللغة تلميحات تُساعده في تحديد شكل ونوع البيانات التي يتوقعها. عرّفنا في المثال السابق البنيتين myJSON و myObject وأضفنا وسوم json لتحديد أسماء الحقول بعد تحويلها إلى جسون. يمكننا الآن استخدام قيم البنية struct نفسها لفك ترميز سلسلة جسون المُستخدمة، وهذا ما قد يكون مفيدًا لتقليل التعليمات البرمجية المكررة في البرنامج عند تنظم أو فك تنظيم بيانات جسون نفسها. فائدة أخرى لاستخدام بنية في فك تنظيم بيانات جسون هي إمكانية إخبار المُفسّر بنوع بيانات كل حقل، وهناك فائدة أخرى تأتي من استخدام مُفسّر اللغة للتحقق من استخدام الأسماء الصحيحة للحقول، وبالتالي تجنب أخطاء قد تحدث في أسماء الحقول (من النوع string) عند استخدام الروابط. لنفتح ملف "main.go"، ونعدّل تصريح المتغير data لاستخدام مرجع للبنية myJSON ونضيف بعض تعليمات الطباعة fmt.Printf لإظهار بيانات الحقول المختلفة في myJSON: ... func main() { ... var data *myJSON err := json.Unmarshal([]byte(jsonData), &data) if err != nil { fmt.Printf("could not unmarshal json: %s\n", err) return } fmt.Printf("json struct: %#v\n", data) fmt.Printf("dateValue: %#v\n", data.DateValue) fmt.Printf("objectValue: %#v\n", data.ObjectValue) } نظرًا لأننا عرّفنا سابقًا أنواع البُنى، فلن نحتاج إلا إلى تحديث نوع الحقل data لدعم عملية فك التنظيم في بنية. تُظهر بقية التحديثات بعض البيانات الموجودة في البنية نفسها. لنُشغّل ملف البرنامج "main.go" من خلال الأمر go run: $ go run main.go ويظهر لدينا الخرج التالي: json struct: &main.myJSON{IntValue:1234, BoolValue:true, StringValue:"hello!", DateValue:time.Date(2022, time.March, 2, 9, 10, 0, 0, time.UTC), ObjectValue:(*main.myObject)(0x1400011c180), NullStringValue:(*string)(nil), NullIntValue:(*int)(nil), EmptyString:""} dateValue: time.Date(2022, time.March, 2, 9, 10, 0, 0, time.UTC) objectValue: &main.myObject{ArrayValue:[]int{1, 2, 3, 4}} هناك شيئان يجب أن نشير لهما في الخرج السابق، إذ نلاحظ أولًا في سطر json struct وسطر dateValue، أن قيمة التاريخ والوقت من بيانات جسون جرى تحويلها إلى قيمة من النوع time.Time (يظهر النوع time.Date عند استخدام العنصر النائب v#%). بما أن مُفسّر جو كان قادرًا على التعرّف على النوع time.Time في حقل DateValue، فهو قادر أيضًا على تحليل قيم النوع string. الشيء الثاني الذي نلاحظه هو أن EmptyString يظهر على سطر json struct على الرغم من أنه لم يُضمّن في بيانات جسون الأصلية. إذا جرى تضمين حقل في بنية مُستخدمة في عملية فك تنظيم بيانات جسون، وكان هذا الحقل غير موجود في بيانات جسون الأصلية، فإنه هذا الحقل يُضبط بالقيمة الافتراضية لنوعه ويجري تجاهله. يمكننا بهذه الطريقة تعريف جميع الحقول المحتملة التي قد تحتوي عليها بيانات جسون بأمان، دون القلق بشأن حدوث خطأ إذا لم يكن الحقل موجودًا في أي من جانبي العملية. ضُبط كل من الحقلين NullStringValue و NullIntValue على قيمتهما الافتراضية nil، لأن بيانات جسون تقول أن قيمهما null، لكن حتى لو لم يكونا ضمن بيانات جسون، سيأخذان نفس القيمة. على غرار الطريقة التي تجاهلت بها الدالة json.Unmarshal حقل EmptyString في البنية struct عندما كان حقل emptyString مفقودًا من بيانات جسون، فإن العكس هو الصحيح أيضًا؛ فإذا كان هناك حقل في بيانات جسون ليس له ما يقابله في البنية struct، سيتجاهل مفسّر اللغة هذا الحقل، وينتقل إلى الحقل التالي لتحليله. بالتالي إذا كانت بيانات جسون التي نقرأها كبيرة جدًا وكان البرنامج يحتاج عددًا صغيرًا من تلك الحقول، يمكننا إنشاء بنية تتضمن الحقول التي نحتاجها فقط، إذ يتجاهل مُفسّر اللغة أية حقول من بيانات جسون غير موجودة في البنية. لنفتح ملف "main.go" ونعدّل jsonData لتضمين حقل غير موجود في myJSON: ... func main() { jsonData := ` { "intValue":1234, "boolValue":true, "stringValue":"hello!", "dateValue":"2022-03-02T09:10:00Z", "objectValue":{ "arrayValue":[1,2,3,4] }, "nullStringValue":null, "nullIntValue":null, "extraValue":4321 } ` ... } لنُشغّل ملف البرنامج "main.go" من خلال الأمر go run: $ go run main.go سيكون الخرج على النحو التالي: json struct: &main.myJSON{IntValue:1234, BoolValue:true, StringValue:"hello!", DateValue:time.Date(2022, time.March, 2, 9, 10, 0, 0, time.UTC), ObjectValue:(*main.myObject)(0x14000126180), NullStringValue:(*string)(nil), NullIntValue:(*int)(nil), EmptyString:""} dateValue: time.Date(2022, time.March, 2, 9, 10, 0, 0, time.UTC) objectValue: &main.myObject{ArrayValue:[]int{1, 2, 3, 4}} نلاحظ عدم ظهور الحقل الجديد extraValue الذي أضفناه إلى بيانات جسون ضمن الخرج، إذ تجاهله مُفسّر اللغة، لأنّه غير موجود ضمن البنية myJSON. استخدمنا في هذا المقال أنواع البُنى struct المُعرّفة مسبقًا في عملية فك تنظيم بيانات جسون. رأينا كيف أن ذلك يُمكّن مُفسّر اللغة من تحليل قيم الأنواع المعقدة مثل time.Time، ويسمح بتجاهل الحقل EmptyString الموجود ضمن البنية وغير موجود ضمن بيانات جسون. رأينا أيضًا كيف يمكن التحكم في الحقول التي نستخرجها من بيانات جسون وتحديد ما نريده من حقول بدقة. الخاتمة أنشأنا في هذا المقال برنامجًا يستخدم الحزمة encoding/json من مكتبة لغة جو القياسية. استخدمنا بدايةً الدالة json.Marshal مع النوع {}map[string]interface لإنشاء بيانات جسون بطريقة مرنة. عدّلنا بعد ذلك البرنامج لاستخدام النوع struct مع وسوم json لإنشاء بيانات جسون بطريقة متسقة وموثوقة باستخدام الدالة json.Marshal. استخدمنا بعد ذلك الدالة json.Unmarshal مع النوع {}map[string]interface لفك ترميز سلسلة جسون وتحويلها إلى بيانات يمكن التعامل معها في برنامج جو. أخيرًا، استخدمنا نوع بنية struct مُعرّف مسبقًا في عملية فك تنظيم بيانات جسون باستخدام دالة json.Unmarshal للسماح لمُفسّر اللغة بإجراء التحليل واستنتاج أنواع البيانات وفقًا لحقول البنية التي عرّفناها. يمكننا من خلال الحزمة encoding/json التفاعل مع العديد من واجهات برمجة التطبيقات APIs المتاحة على الإنترنت لإنشاء عمليات متكاملة مع مواقع الويب الأخرى. يمكننا أيضًا تحويل بيانات جو في برامجنا إلى تنسيق يمكن حفظه ثم تحميله لاحقًا للمتابعة من حيث توقف البرنامج (لأن عملية السلسلة Serialization تحفظ البيانات قيد التشغيل في الذاكرة). تتضمن الحزمة encoding/json أيضًا دوالًا أخرى مُفيدة للتعامل مع بيانات جسون، مثل الدالة json.MarshalIndent التي تساعدنا في عرض بيانات جسون بطريقة مُرتبة وأكثر وضوحًا للاطلاع عليها ومساعدتنا في استكشاف الأخطاء وإصلاحها. ترجمة -وبتصرف- للمقال How To Use JSON in Go لصاحبه Kristin Davidson. اقرأ أيضًا المقال السابق استخدام السياقات Contexts في لغة جو Go استخدام وسوم البنية Struct Tags في لغة جو.
  16. عند تطوير التطبيقات الكبيرة، وخصوصًا برمجيات الخادم - يكون من المفيد أحيانًا لدالةٍ ما معرفة بعض المعلومات عن البيئة التي تُنفّذ بها إلى جانب المعلومات اللازمة لعمل الدالة نفسها. لنأخذ مثلًا دالة خادم ويب تتعامل مع طلب HTTP لعميل معين، هنا قد تحتاج الدالة إلى معرفة عنوان URL الذي يطلبه العميل فقط لتحقيق الاستجابة، وفي هذه الحالة ربما تحتاج فقط إلى تمرير العنوان مثل معامل إلى الدالة. المشكلة أن هناك بعض الأشياء المفاجئة التي يمكن أن تحدث مثل انقطاع الاتصال مع العميل قبل تحقيق الاستجابة وتلقيه الرد. بالتالي، إذا كانت الدالة التي تؤدي الاستجابة لا تعرف أن العميل غير متصل، لن يصل الرد والعمليات التي يجريها الخادم ستكون مجرد هدر للموارد الحاسوبية على استجابة لن تُستخدم. لتفادي هكذا حالات يجب أن يكون بمقدور الخادم معرفة سياق الطلب (مثل حالة اتصال العميل)، وبالتالي إمكانية إيقاف معالجة الطلب بمجرد انقطاع الاتصال مع العميل. هذا من شأنه الحفاظ على الموارد الحاسوبية ويحد من الهدر ويتيح للخادم التحرر أكثر من الضغط وتقديم أداء أفضل. تظهر فائدة هذا النوع من المعلومات أكثر في الحالات التي تتطلّب فيها الدوال وقتًا طويلًا نسبيًا في التنفيذ، مثل إجراء استدعاءات قاعدة البيانات. لمعالجة هذه القضايا ومنح إمكانية الوصول الكامل لمثل هذه المعلومات تُقدم لغة جو حزمة السياق context في المكتبة القياسية. سنُنشئ خلال هذا المقال برنامجًا يستخدم سياقًا داخل دالة. سنعدّل بعدها البرنامج لتخزين بيانات إضافية في السياق واستردادها من دالة أخرى. بعد ذلك سنستفيد من فكرة السياق لإرسال إشارة للدالة التي تُجري عملية المعالجة، لتوقف تنفيذ أي عمليات معالجة مُتبقية. المتطلبات إصدار مُثبّت من جو 1.16 أو أعلى، ويمكنك الاستعانة بمقال تثبيت لغة جو Go وإعداد بيئة برمجة محلية على أبونتو Ubuntu لإعداده. تثبيت لغة جو وإعداد بيئة برمجة محلية على نظام ماك macOS. تثبيت لغة جو وإعداد بيئة برمجة محلية على ويندوز. فهم لخيوط معالجة جو goroutines والقنوات channels. يمكنك الاطلاع على مقالة كيفية تشغيل عدة دوال على التساير في لغة جو Go. معرفة بكيفية التعامل مع التاريخ والوقت في لغة جو. يمكنك الاطلاع على مقالة استخدام التاريخ والوقت في لغة جو Go. معرفة كيفية التعامل مع تعليمة switch في لغة جو. يمكنك الاطلاع على مقالة التعامل مع التعليمة Switch في لغة جو Go. إنشاء سياق context تستخدم العديد من الدوال في لغة جو حزمة context لجمع معلومات إضافية حول البيئة التي تُنفّذ فيها، وعادةً ما يُقدّم هذا السياق للدوال التي تستدعيها أيضًا. ستتمكن البرامج من خلال واجهة context.Context التي توفرها حزمة السياق، وتمريرها من من دالة إلى أخرى، من نقل معلومات السياق من أعلى نقطة تنفيذ في البرنامج (دالة main) إلى أعمق نقطة تنفيذ في البرنامج (دالة ضمن دالة أخرى أو ربما أعمق). مثلًا، تُقدّم الدالة Context من النوع http.Request سياقًا context.Context يتضمن معلومات عن العميل الذي أرسل الطلب، ويُحذف أو ينتهي هذا السياق في حالة قُطع الاتصال مع العميل، حتى لو لم يكن الطلب قد انتهت معالجته. بالتالي، إذا كانت هناك دالة ما تستدعي الدالة QueryContext التابعة إلى sql.DB، وكان قد مُرر لهذه الدالة قيمة context.Context، وقُطع الاتصال مع العميل، سيتوقف تنفيذ الاستعلام مباشرةً في حالة لم يكن قد انتهى من التنفيذ. سننشئ في هذا القسم برنامجًا يتضمن دالة تتلقى سياقًا مثل معامل، ونستدعي هذه الدالة باستخدام سياق فارغ نُنشئه باستخدام الدالتين context.TODO و Context.Background. كما هو معتاد، سنحتاج لبدء إنشاء برامجنا إلى إنشاء مجلد للعمل ووضع الملفات فيه، ويمكن وضع المجلد في أي مكان على الحاسب، إذ يكون للعديد من المبرمجين عادةً مجلدٌ يضعون داخله كافة مشاريعهم. سنستخدم في هذا المقال مجلدًا باسم "projects"، لذا فلننشئ هذا المجلد وننتقل إليه: $ mkdir projects $ cd projects الآن من داخل هذا المجلد، سنشغّل الأمر mkdir لإنشاء مجلد "contexts" ثم سنستخدم cd للانتقال إليه: $ mkdir contexts $ cd contexts يمكننا الآن فتح ملف "main.go" باستخدام محرر نانو nano أو أي محرر آخر تريده: $ nano main.go سنُنشئ الآن دالة doSomething داخل ملف "main.go". تقبل هذه الدالة context.Context مثل معامل، ثم نضيف دالة main التي تُنشئ سياقًا وتستدعي doSomething باستخدام ذلك السياق. نضيف ما يلي داخل ملف main.go: package main import ( "context" "fmt" ) func doSomething(ctx context.Context) { fmt.Println("Doing something!") } func main() { ctx := context.TODO() doSomething(ctx) } استخدمنا الدالة context.TODO داخل الدالة main، لإنشاء سياق فارغ ابتدائي. يمكننا استخدام السياق الفارغ مثل موضع مؤقت placeholder عندما لا نكون متأكدين من السياق الذي يجب استخدامه. لدينا أيضًا الدالة doSomething التي تقبل معاملًا وحيدًا هو context.Context -له الاسم ctx- وهو الاسم الشائع له، ويُفضل أن يكون أول معامل في الدالة في حال كان هناك معاملات أخرى، لكن الدالة لا تستخدمه الآن فعليًّا. لنُشغّل ملف البرنامج "main.go" من خلال الأمر go run: $ go run main.go سيكون الخرج على النحو التالي: Doing something! نلاحظ أن الخرج الذي أظهرته الدالة fmt.Println هو !Doing something نتيجةً لاستدعاء الدالة doSomething. لنعدّل البرنامج الآن ونستخدم الدالة context.Background التي تُنشئ سياقًا فارغًا: ... func main() { ctx := context.Background() doSomething(ctx) } تنشئ الدالة context.Background سياقًا فارغًا مثل context.TODO، ومن حيث المبدأ تؤدي كلتا الدالتين السابقتين نفس الغرض. الفرق الوحيد هو أن context.TODO تُستخدم عندما تُريد أن تُخبر المطورين الآخرين أن هذا السياق هو مجرد سياق مبدأي وغالبًا يجب تعديله، أما context.Background تتُستخدم عندما لا نحتاج إلى هكذا إشارة إلى المطورين الآخرين، أي نحتاج ببساطة إلى سياق فارغ لا أكثر ولا أقل، وفي حال لم تكن متأكدًا أيهما تستخدم، ستكون الدالة context.Background خيارًا افتراضيًا. لنُشغّل ملف البرنامج "main.go" من خلال الأمر go run: $ go run main.go سيكون الخرج: Doing something! سيكون الخرج طبعًا نفسه كما في المرة السابقة، وبذلك نكون قد تعلمنا كيفية إنشاء سياق فارغ بطريقتين مختلفتين. لا يفيدنا السياق الفارغ تمامًا إذا بقي على هذا النحو، فعلى الرغم من قدرتنا على تمريره بين الدوال، إلا أنه لا يوفر أية معلومات. لجعل السياق مفيدًا نُضيف له بيانات يمكن للدوال الأخرى استردادها والاطلاع عليها. إضافة معلومات إلى السياق إحدى فوائد استخدام context.Context في برنامجٍ ما هي القدرة على الوصول إلى البيانات المخزنة داخل سياق ما، إذ يمكن لكل طبقة من البرنامج إضافة معلومات إضافية حول ما يحدث من خلال إضافة البيانات إلى سياق وتمرير السياق من دالة إلى أخرى. مثلًا، قد تضيف الدالة الأولى اسم مستخدم إلى السياق، والدالة التالية مسار الملف إلى المحتوى الذي يحاول المستخدم الوصول إليه، والدالة الثالثة تقرأ الملف من قرص النظام وتسجل ما إذا كان قد نجح تحميله أم لا، إضافةً إلى المستخدم الذي حاول تحميله. يمكن استخدم الدالة Context.WithValue من حزمة السياق لإضافة قيمة جديدة إلى السياق. تقبل الدالة ثلاث معاملات: السياق الأب (الأصلي) context.Context والمفتاح والقيمة. السياق الأب هو السياق الذي يجب إضافة القيمة إليه مع الاحتفاظ بجميع المعلومات الأخرى المتعلقة بالسياق الأصلي. يُستخدم المفتاح لاسترداد القيمة من السياق. يمكن أن يكون المفتاح والقيمة من أي نوع بيانات، وفي هذا المقال سيكونان من نوع سلسلة نصية string. تعيد الدالة Context.WithValue قيمةً من النوع context.Context تتضمن السياق الأب مع المعلومات المُضافة. يمكن الحصول على القيمة التي يُخزنها السياق context.Context من خلال استخدام التابع Value مع المفتاح. لنفتح الآن ملف "main.go" ولنضِف قيمة إلى السياق باستخدام الدالة السابقة، ثم نحدِّث دالة doSomething، بحيث تطبع تلك القيمة باستخدام دالة fmt.Printf: ... func doSomething(ctx context.Context) { fmt.Printf("doSomething: myKey's value is %s\n", ctx.Value("myKey")) } func main() { ctx := context.Background() ctx = context.WithValue(ctx, "myKey", "myValue") doSomething(ctx) } أسندنا في الشيفرة السابقة سياقًا جديدًا إلى المتغير ctx، الذي يُستخدم للاحتفاظ بالسياق الأب، ويُعد هذا نمط شائع الاستخدام في حال لم يكن هناك سبب للإشارة إلى سياق أب محدد. إذا كنا بحاجة إلى الوصول إلى السياق الأب في وقتٍ ما، يمكننا إسناد القيمة إلى متغير جديد، كما سنرى قريبًا. لنُشغّل ملف البرنامج "main.go" من خلال الأمر go run: $ go run main.go سيكون الخرج على النحو التالي: doSomething: myKey's value is myValue يمكننا رؤية القيمة myValue التي خزّناها في السياق ضمن الخرج السابق، وذلك نتيجةً لاستدعاء الدالة doSomething ضمن الدالة main. طبعًا القيمة myValue المُستخدمة هنا هي فقط من أجل التوضيح، فمثلًا لو كنا نعمل على خادم كان من الممكن أن تكون هذه القيمة شيئًا آخر مثل وقت بدء تشغيل البرنامج. عند استخدام السياقات من المهم معرفة أن القيم المخزنة في سياقٍ context.Context ما تكون ثابتة immutable. بالتالي عندما استدعينا الدالة context.WithValue ومررنا لها السياق الأب، حصلنا على سياق جديد وليس السياق الأب أو نسخة منه، وإنما حصلنا على سياق جديد يضم المعلومات الجديدة إضافةً إلى سياق الأب مُغلّفًا wrapped ضمنه. لنفتح الآن ملف "main.go" لإضافة دالة جديدة doAnother تقبل سياقًا context.Context وتطبع قيمة السياق من خلال المفتاح، ونعدّل أيضًا الدالة doSomething، بحيث نُنشئ داخلها سياقًا جديدًا يُغلّف السياق الأب ويضيف معلومات جديدة ولتكن anotherValue، ثم نستدعي الدالة doAnother على السياق anotherCtx الناتج، ونطبع في السطر الأخير من الدالة قيمة السياق الأب. ... func doSomething(ctx context.Context) { fmt.Printf("doSomething: myKey's value is %s\n", ctx.Value("myKey")) anotherCtx := context.WithValue(ctx, "myKey", "anotherValue") doAnother(anotherCtx) fmt.Printf("doSomething: myKey's value is %s\n", ctx.Value("myKey")) } func doAnother(ctx context.Context) { fmt.Printf("doAnother: myKey's value is %s\n", ctx.Value("myKey")) } ... لنُشغّل ملف البرنامج "main.go" من خلال الأمر go run: $ go run main.go سيكون الخرج: doSomething: myKey's value is myValue doAnother: myKey's value is anotherValue doSomething: myKey's value is myValue نلاحظ في الخرج سطرين من الدالة doSomething وسطر من الدالة doAnother. لم نغير شيئًا داخل الدالة main؛ إذ أنشأنا سياقًا فارغًا وغلّفناه مع القيمة myValue والمفتاح myKey ومرّرنا السياق الناتج إلى doSomething، ويمكننا أن نلاحظ أن هذه القيمة مطبوعة على أول سطر من الخرج. يظهر السطر الثاني من الخرج أنه عند استخدام context.WithValue داخل doSomething لتغليف السياق الأب ctx وتوليد السياق anotherCtx بالقيمة anotherValue والمفتاح myKey (المفتاح نفسه للأب لم يتغير) وتمرير هذا السياق الناتج إلى doAnother، فإن القيمة الجديدة تتخطى القيمة الابتدائية. بالنسبة للسطر الأخير، نلاحظ أنه يطبع القيمة المرتبطة بالمفتاح myKey والمرتبطة بالسياق الأب وهي myValue. نظرًا لأن الدالة context.WithValue تغلّف السياق الأب فقط، سيبقى السياق الأب محتفظًا بنفس القيم الأصلية نفسها. عندما نستدعي التابع Value على سياق ما، فإنه يُعيد القيمة المرتبطة بالمفتاح من المستوى الحالي للسياق. عند استدعاء anotherCtx.Value من أجل مفتاح myKey، سيعيد القيمة anotherValue لأنها القيمة المغلّفة للسياق، وبالتالي يتجاوز أي قيم أخرى مُغلّفة للمفتاح، وعند استدعاء anotherCtx داخل doSomething للمرة الثانية، لن تغلِّف anotherCtx السياق ctx، وستُعاد القيمة الأصلية myValue. ملاحظة: السياق أداة قوية يمكن من خلالها تخزين جميع القيم التي نريد الاحتفاظ بها. قد يبدو من المغري وضع جميع البيانات في السياق واستخدام تلك البيانات في الدوال بدلًا من المعاملات، ولكن يمكن أن يؤدي ذلك إلى شيفرة يصعب قراءتها والحفاظ عليه. يجب تحقيق التوازن بين البيانات المُخزنة في السياق والبيانات المُمررة إلى دالةٍ ما مثل معاملات. القاعدة الأساسية المناسبة هي أن أي بيانات مطلوبة لتشغيل دالة ما يجب أن تُمرر مثل معاملات. مثلًا قد يكون من المفيد الاحتفاظ بأسماء المستخدمين ضمن السياق لاستخدامها عند تسجيل المعلومات لاحقًا، إلا أنه من الممكن أن تكون هناك حاجة لاستخدام اسم المستخدم في تحديد ما إذا كانت الدالة يجب أن تعرض بعض المعلومات المحددة المرتبطة به، وبالتالي من الأفضل تضمينها مثل معامل للدالة حتى لو كانت متاحةً في السياق، لكي يسهل معرفة البيانات التي تُستخدم ضمن الدالة لنا وللآخرين. حدّثنا في هذا القسم برنامجنا لتخزين قيمة في سياق، ثم تغليف السياق لتجاوز هذه القيمة. هذه ليست الأداة الوحيدة التي يمكن للسياقات توفيرها، إذ يمكن للسياقات أن تُستخدم للإشارة إلى أجزاء أخرى من البرنامج عندما ينبغي التوقف عن المعالجة لتجنب هدر الموارد الحاسوبية. إنهاء سياق إحدى الأدوات الأخرى التي يُقدمها السياق context.Context هي إمكانية الإشارة إلى أي دالة تستخدمه بأن السياق قد انتهى ويجب عدّه مكتملًا، وبالتالي يمكن للدوال إيقاف تنفيذ أي عمل يؤدونه. هذا يسمح للبرنامج أن يكون أكثر كفاءة، فهو أصبح يعرف متى يجب أن يتوقف عن معالجة طلب ما عندما تنتهي الحاجة إليه. مثلًا، إذا أرسل المستخدم طلبًا للحصول على صفحة ويب من الخادم وليكن عن طريق الخطأ، ثم ضغط على زر "إيقاف" أو إغلاق المتصفح قبل انتهاء تحميل الصفحة، في هذه الحالة إن لم يُدرك الخادم أن المستخدم قرر إلغاء العملية، سيعالج هذا الطلب دون جدوى، فالمستخدم لن يرى النتيجة لأنه لا يريدها بعد الآن، وبالتالي تُهدر الموارد على طلب دون فائدة، ولا سيما إذا كانت الصفحة المطلوبة تتطلب تنفيذ بعض الاستعلامات من قاعدة البيانات؛ أما إذا كانت الدالة التي تُخدّم الطلب تستخدم سياقًا، فإنها ستعرف وستُخبر بقية الدوال ذات الصلة بأن السياق انتهى لأن الخادم ألغاه، وبالتالي يمكنهم تخطي تنفيذ أية استعلامات مُتبقية من قاعدة البيانات. يؤدي ذلك إلى الحد من هدر الموارد من خلال إتاحة وقت المعالجة هذا إلى طلب آخر ربما يكون منتظرًا. سنعدّل في هذا القسم البرنامج ليكون قادرًا على معرفة وقت انتهاء السياق، وسنتعرّف على 3 توابع لإنهاء السياق. تحديد انتهاء السياق يحدث تحديد ما إذا كان السياق قد انتهى بنفس الطريقة، وذلك بصرف النظر عن السبب؛ إذ يوفر النوع context.Context تابعًا يُسمى Done للتحقق من انتهاء سياق ما. يُعيد هذا التابع قناةً channel تُغلق حالما ينتهي السياق، وأي دالة تُتابع هذه القناة توقف تنفيذ أي عملية ذات صلة في حال أُغلقت القناة. لا يُكتب على هذه القناة أية قيم، وبالتالي عند إغلاق القناة تُعيد القيمة nil إذا حاولنا قراءتها. إذًا، يمكننا من خلال هذه القناة، إنشاء دوال يمكنها معالجة الطلبات ومعرفة متى يجب أن تكمل المعالجة ومتى يجب أن تتوقف من خلال التحقق الدوري من حالة القناة، كما أن الجمع بين معالجة الطلبات والفحص الدوري لحالة القناة وتعليمة select يمكن أن يقدم فائدةً أكبر من خلال السماح بإرسال أو استقبال البيانات من قنوات أخرى في نفس الوقت، إذ تُستخدم التعليمة select للسماح للبرنامج بالكتابة أو القراءة من عدة قنوات بصورة متزامنة. يمكن تنفيذ عملية واحدة خاصة بقناة في وقت واحد ضمن تعليمة select، لذا نستخدم حلقة for على تعليمة select كما سنرى في المثال التالي، لإجراء عدة عمليات على القناة. يُمكن إنشاء تعليمة select من خلال الكلمة المفتاحية select متبوعةً بقوسين {} مع تعليمة case واحدة أو أكثر ضمن القوسين. يمكن أن تكون كل تعليمة case عملية قراءة أو كتابة على قناة، وتنتظر تعليمة select حتى تُنفّذ إحدى حالتها (تبقى منتظرة حتى تُنفذ إحدى تعليمات case)، وفي حال أردنا ألا تنتظر، يمكننا أن نستخدم التعليمة default، وهي الحالة الافتراضية التي تُنفّذ في حال عدم تحقق شرط تنفيذ إحدى الحالات الأخرى (تشبه تعليمة switch). توضّح الشيفرة التالية كيف يمكن استخدام تعليمة select ضمن دالة، بحيث تتلقى نتائج من قناة وتراقب انتهاء السياق من خلال التابع Done: ctx := context.Background() resultsCh := make(chan *WorkResult) for { select { case <- ctx.Done(): // The context is over, stop processing results return case result := <- resultsCh: // عالج النتائج } } تُمرر عادةً قيم كل من ctx و resultsCh إلى دالة مثل معاملات، إذ يكون ctx سياقًا من النوع context.Context، بينما resultsCh هي قيمة من قناة يمكننا القراءة منها فقط داخل الدالة، وغالبًا ما تكون هذه القيمة نتيجة من عامل worker (أو خيوط معالجة جو) في مكانٍ ما. في كل مرة تُنفذ فيها تعلمية select سيوقف جو تنفيذ الدالة ويراقب جميع تعليمات case، وحالما تكون هناك إمكانية لتنفيذ أحدها (قراءة من قناة كما في في حالة resultsCh، أو كتابة أو معرفة حالة القناة عبر Done) يُنفذ فرع هذه الحالة، ولا يمكن التنبؤ بترتيب تنفيذ تعلميات case هذه، إذ من الممكن أن تُنفذ أكثر من حالة بالتزامن. نلاحظ في الشيفرة أعلاه أنه يمكننا استمرار تنفيذ الحلقة إلى الأبد طالما أن السياق لم ينتهي، أي طالما ctx.Done لم تُشر إلى إغلاق القناة، إذ لاتوجد أي تعليمات break أو return إلا داخل عبارة case. بالرغم من عدم إسناد الحالة case <- ctx.Done أي قيمة لأي متغير، إلا أنه سيظل بالإمكان تنفيذها عند إغلاق ctx.Done، لأن القناة تحتوي على قيمة يمكن قراءتها حتى لو لم نُسنِد تلك القيمة وتجاهلناها. إذا لم تُغلق القناة ctx.Done (أي لم يتحقق فرع الحالة case <- ctx.Done)، فسوف تنتظر تعليمة select حتى تُغلق أو حتى يُصبح بالإمكان قراءة قيمة من resultsCh. إذا كانت resultsCh تحمل قيمة يمكن قراءتها يُنفّذ فرع الحالة الذي يتضمنها ويجري انتظارها ريثما تنتهي من التنفيذ، وبعدها يجري الدخول في تكرار آخر للحلقة (تُنفّذ حالة واحدة في الاستدعاء الواحد لتعليمة select)، وكما ذكرنا سابقًا إذا كان بالإمكان تنفيذ الحالتان، يجري الاختيار بينهما عشوائيًا. في حال وجود تعليمة default، فإن الأمر الوحيد الذي يتغير هو أنه يُنفذ حالًا في حال لم تكن إحدى الحالات الأخرى قابلة للتنفيذ، وبعد تنفيذ default يجري الخروج من select ثم يجري الدخول إليها مرةً أخرى بسبب وجود الحلفة. يؤدي هذا إلى تنفيذ حلقة for بسرعة كبيرة لأنها لن تتوقف أبدًا وتنتظر القراءة من قناة. تُسمى الحلقة في هذه الحالة "حلقة مشغولة busy loop" لأنه بدلًا من انتظار حدوث شيء ما، تكون الحلقة مشغولة بالتكرار مرارًا وتكرارًا. يستهلك ذلك الكثير من دورات وحدة المعالجة المركزية CPU، لأن البرنامج لا يحصل أبدًا على فرصة للتوقف عن التشغيل للسماح بتنفيذ التعليمات البرمجية الأخرى. عمومًا تكون هذه العملية مفيدة أحيانًا، فمثلًا إذا كنا نريد التحقق مما إذا كانت القناة جاهزة لفعل شيء ما قبل الذهاب لإجراء عملية أخرى غير متعلقة بالقناة. كما ذكرنا، تتجلى الطريقة الوحيدة للخروج من الحلقة في هذا المثال بإغلاق القناة المُعادة من التابع Done، والطريقة الوحيدة لإغلاق هذه القناة هي إنهاء السياق، بالتالي نحن بحاجة إلى طريقة لإنهائه. توفر لغة جو عدة طرق لإجراء ذلك وفقًا للهدف الذي نبتغيه، والخيار المباشر هو استدعاء دالة "إلغاء cancel" السياق. إلغاء السياق يعد إلغاء السياق Cancelling context أكثر طريقة مباشرة ويمكن التحكم بها لإنهاء السياق. يمكننا -بأسلوب مشابه لتضمين قيمة في سياق باستخدام دالة context.WithValue- ربط دالة إلغاء سياق مع سياق باستخدام دالة context.WithCancel، إذ تقبل هذه الدالة السياق الأب مثل معامل وتعيد سياقًا جديدًا إضافةً إلى دالة يمكن استخدامها لإلغاء السياق المُعاد؛ وكذلك يؤدي استدعاء دالة الحذف المُعادة فقط إلى إلغاء السياق الذي أُعيد مع جميع السياقات الأخرى التي تستخدمه مثل سياق أب، وذلك بطريقة مشابهة أيضًا لأسلوب عمل دالة context.WithValue. هذا لا يعني طبعًا أنه لا يمكن إلغاء السياق الأب الذي مررناه إلى دالة context.WithCancel، بل يعني أنه لا يُلغى إذا استدعيت دالة الإلغاء بهذا الشكل. لنفتح ملف "main.go" لنرى كيف نستخدم context.WithCancel: package main import ( "context" "fmt" "time" ) func doSomething(ctx context.Context) { ctx, cancelCtx := context.WithCancel(ctx) printCh := make(chan int) go doAnother(ctx, printCh) for num := 1; num <= 3; num++ { printCh <- num } cancelCtx() time.Sleep(100 * time.Millisecond) fmt.Printf("doSomething: finished\n") } func doAnother(ctx context.Context, printCh <-chan int) { for { select { case <-ctx.Done(): if err := ctx.Err(); err != nil { fmt.Printf("doAnother err: %s\n", err) } fmt.Printf("doAnother: finished\n") return case num := <-printCh: fmt.Printf("doAnother: %d\n", num) } } } ... أضفنا استيرادًا للحزمة time وجعلنا الدالة doAnother تقبل قناةً جديدة لطباعة أرقام على شاشة الخرج. استخدمنا تعليمة select ضمن حلقة for للقراءة من تلك القناة والتابع Done الخاص بالسياق. أنشأنا ضمن الدالة doSomething سياقًا يمكن إلغاؤه إضافةً إلى قناة لإرسال الأرقام إليها. أضفنا استدعاءً للدالة doAnother سبقناه بالكلمة المفتاحية go ليُنفّذ مثل خيوط معالجة جو goroutine، ومررنا له السياق ctx والقناة printCh. أخيرًا أرسلنا بعض الأرقام إلى القناة ثم ألغينا السياق. لنُشغّل ملف البرنامج "main.go" من خلال الأمر go run: $ go run main.go سيكون الخرج على النحو التالي: doAnother: 1 doAnother: 2 doAnother: 3 doAnother err: context canceled doAnother: finished doSomething: finished تعمل الدالة doSomething في الشيفرة السابقة على إرسال العمل إلى خيط معالجة واحد أو أكثر يقرؤون الأرقام من القناة ويطبعوها، وتكون في هذه الحالة الدالة doAnother هي العامل worker وعملها هو طباعة الأرقام، وحالما يبدأ خيط معالجة جو doAnother، تبدأ دالة doSomething بإرسال الارقام للطباعة. تنتظر تعليمة select -داخل الدالة doAnother- إغلاق قناة ctx.Done أو استقبال رقم على القناة printCh. تبدأ الدالة doSomething عملية إرسال الأرقام إلى القناة بعد بدء تنفيذ doAnother كما نرى في الشيفرة أعلاه، إذ ترسل 3 أرقام إلى القناة، وبالتالي تُفعّل 3 عمليات طباعة fmt.Printf لكل رقم (فرع الحالة الثانية داخل تعليمة select)، ثم تستدعي دالة cancelCtx لإلغاء السياق. بعد أن تقرأ الدالة doAnother الأرقام الثلاثة من القناة، ستنتظر العملية التالية من القناة، أي تبقى منتظرة ولن يُنفّذ الفرع المقابل في select، وبما أن doSomething في هذه الأثناء استدعت cancelCtx، بالتالي يُستدعى فرع ctx.Done، الذي يستخدم الدالة Err التي يوفرها النوع context.Context لتحديد كيفية إنهاء السياق. بما أننا ألغينا السياق باستخدام cancelCtx، بالتالي سيكون الخطأ الذي نراه هو context canceled. ملاحظة: إذا سبق وشغّلت برامج لغة جو من قبل ونظرت إلى الخرج، فربما سبق وشاهدت الخطأ context canceled من قبل، فهو خطأ شائع عند استخدام حزمة http من لغة جو، ويهدف لمعرفة وقت قطع اتصال العميل بالخادم، قبل أن يعالج الخادم الاستجابة. أخيرًا، تستخدم الدالة doSomething بعد ذلك الدالة time.Sleep للانتظار لفترة قصيرة من الوقت، لتعطي بذلك الدالة doAnother وقتًا لمعالجة حالة السياق المُلغى وإنهاء التنفيذ، ثم تطبع الدالة doSomething رسالة تُشير إلى انتهاء التنفيذ. تجدر الملاحظة إلى أنه لا داعٍ إلى استخدام دالة time.Sleep غالبًا، لكنها ضرورية عندما تنتهي الشيفرة من التنفيذ بسرعة، وسينتهي بدونها البرنامج دون رؤية كامل الخرج على الشاشة. تكون الدالة context.WithCancel ودالة الإلغاء التي تُعيدها مفيدةً أكثر عندما يتطلب الأمر قدرًا كبيرًا من التحكم عند إنهاء السياق، لكن في كثير من الأحيان قد لا نحتاج إلى هذا القدر من التحكم. الدالة التالية المتاحة لإنهاء السياقات في حزمة السياق context هي Context.WithDeadline، إذ تنهي هذه الدالة السياق تلقائيًا نيابةً عنا. إعطاء السياق مهلة زمنية للانتهاء يمكننا تحديد مهلة زمنية Deadline يجب خلالها أن ينتهي السياق باستخدام الدالة Context.WithDeadline، وبعد انتهاء هذه المهلة سوف ينتهي السياق تلقائيًا. الأمر أشبه بأن نكون في امتحان، ويكون هناك مهلة محددة لحل الأسئلة، وتُسحب الورقة منا عند انتهائها تلقائيًا، حتى لو لم ننتهي من حلها. لتحديد موعد نهائي لسياق ما، نستخدم الدالة Context.WithDeadline مع تمرير السياق الأب وقيمة زمنية من النوع time.Time تُشير إلى الموعد النهائي. تُعيد هذه الدالة سياقًا جديدًا ودالة لإلغاء السياق، وكما رأينا في Context.WithCancel تُطبق عملية الإلغاء على السياق الجديد وعلى أبنائه (السياقات التي تستخدمه). يمكننا أيضًا إلغاء السياق يدويًا عن طريق استدعاء دالة الإلغاء كما في دالة context.WithCancel. لنفتح ملف البرنامج لنحدثه ونستخدم دالة Context.WithDeadline بدلًا من context.WithCancel: ... func doSomething(ctx context.Context) { deadline := time.Now().Add(1500 * time.Millisecond) ctx, cancelCtx := context.WithDeadline(ctx, deadline) defer cancelCtx() printCh := make(chan int) go doAnother(ctx, printCh) for num := 1; num <= 3; num++ { select { case printCh <- num: time.Sleep(1 * time.Second) case <-ctx.Done(): break } } cancelCtx() time.Sleep(100 * time.Millisecond) fmt.Printf("doSomething: finished\n") } ... تستخدم الشيفرة الآن الدالة Context.WithDeadline ضمن الدالة context.WithDeadline لإلغاء السياق تلقائيًا بعد 1500 ميلي ثانية (1.5 ثانية) من بدء تنفيذ الدالة، إذ حددنا الوقت من خلال دالة time.Now. إضافةً إلى استخدام الدالة Context.WithDeadline، أجرينا بعض التعديلات الأخرى؛ فنظرًا لأنه من المحتمل أن ينتهي البرنامج الآن عن طريق استدعاء cancelCtx مباشرةً أو الإلغاء التلقائي وفقًا للموعد النهائي، حدّثنا دالة doSomething، بحيث نستخدم تعليمة select لإرسال الأرقام على القناة. بالتالي، إذا كانت doAnother لا تقرأ من printCh وكانت قناة ctx.Done مُغلقة، ستلاحظ ذلك doSomething وتتوقف عن محاولة إرسال الأرقام. نلاحظ أيضًا استدعاء cancelCtx مرتين، مرة عبر تعليمة defer ومرة كما في السابق. وجود الاستدعاء الأول غير ضروري طالما أن الاستدعاء الثاني موجود وسيُنفذ دومًا، ولكن من المهم وجوده لو كانت هناك تعليمة return أو حدث ما يُمكن أن يتسبب في عدم تنفيذ الاستدعاء الثاني. على الرغم من إلغاء السياق بعد انقضاء المهلة الزمنية، إلا أننا نستدعي دالة الإلغاء، وذلك من أجل تحرير أية موارد مُستخدمة بمثابة إجراء أكثر أمانًا. لنُشغّل ملف البرنامج "main.go" من خلال الأمر go run: $ go run main.go سيكون الخرج على النحو التالي: doAnother: 1 doAnother: 2 doAnother err: context deadline exceeded doAnother: finished doSomething: finished نلاحظ هذه المرة إلغاء السياق بسبب خطأ تجاوز الموعد النهائيdeadline exceeded قبل طباعة جميع الأرقام الثلاثة، وهذا منطقي فالمهلة الزمنية هي 1.5 ثانية بدءًا من لحظة تنفيذ doSomething، وبما أن doSomething تنتظر ثانية واحدة بعد إرسال رقم، فستنتهي المهلة قبل طباعة الرقم الثالث. بمجرد انقضاء المهلة الزمنية، ينتهي تنفيذ كل من doSomething و doAnother، وذلك لأنهما يراقبان لحظة إغلاق قناة ctx.Done. لو عدّلنا مدة المهلة وجعلناها أكثر من 3 ثوان، فربما سنشاهد الخطأ context canceled يظهر من جديد، وذلك لأن المهلة طويلة. قد يكون الخطأ context deadline exceeded مألوفًا أيضًا، ولا سيما للمبرمجين الذين يستخدمون تطبيقات لغة جو ويقرأون رسائل الخطأ التي تظهر. هذا الخطأ شائع في خوادم الويب التي تستغرق وقتًا في إرسال الاستجابات إلى العميل. مثلًا، إذا استغرق استعلام من قاعدة البيانات أو عملية ما وقتًا طويلًا، فقد يتسبب ذلك بإلغاء سياق الطلب وظهور هذا الخطأ لأن الخادم لا يسمح بتجاوز مهلة معينة في معالجة طلب ما. يسمح لنا إلغاء السياق باستخدام context.WithCancel بدلًا من cont4ext.WithCancel بإلغاء السياق تلقائيًا بعد انتهاء مهلة نتوقع أنها كافية، دون الحاجة إلى تتبع ذلك الوقت. بالتالي: إذا كنا نعرف متى يجب أن ينتهي السياق (أي نعرف المهلة الكافية)، ستكون هذه الدالة خيارًا مناسبًا. أحيانًا ربما لا نهتم بالوقت المحدد الذي ينتهي فيه السياق، وتريد أن ينتهي بعد دقيقة واحدة من بدئه. هنا يمكننا أيضًا استخدام الدالة context.WithDeadline مع بعض توابع الحزمة time لتحقيق الأمر، لكن لغة جو توفر لنا الدالة context.WithTimeout لتبسيط الأمر. إعطاء السياق وقت محدد تؤدي دالة context.WithTimeout نفس المهمة التي تؤديها الدالة السابقة، والفرق الوحيد هو أننا في context.WithDeadline نمرر قيمة زمنية محددة من النوع time.Time لإنهاء السياق، أما في context.WithTimeout نحتاج فقط إلى تمرير المدة الزمنية، أي قيمة من النوع time.Duration. إذًا، يمكننا استخدام context.WithDeadline إذا أردنا تحديد وقت معين time.Time. ستحتاج -بدون context.WithTimeout- إلى استخدام الدالة ()time.Now والتابع Add لتحديد المهلة الزمنية، أما مع context.WithTimeout، فيمكنك تحديد المهلة مباشرةً. لنفتح ملف البرنامج مجددًا ونعدله، بحيث نستخدم context.WithTimeout بدلًا من context.WithDeadline: ... func doSomething(ctx context.Context) { ctx, cancelCtx := context.WithTimeout(ctx, 1500*time.Millisecond) defer cancelCtx() ... } ... لنُشغّل ملف البرنامج main.go من خلال الأمر go run: $ go run main.go ليكون الخرج: doAnother: 1 doAnother: 2 doAnother err: context deadline exceeded doAnother: finished doSomething: finished نلاحظ أن الخرج ورسالة الخطأ هي نفسها التي حصلنا عليها في الخرج السابق عندما استخدمنا الدالة context.WithDeadline، ورسالة الخطأ أيضًا نفسها التي تظهر أن context.WithTimeout هي فعليًا مغلّّف يجري عمليات رياضية نيابةً عنك. استخدمنا في هذا القسم 3 طرق مختلفة لإنهاء السياق context.Context.؛ إذ بدأنا بالدالة context.WithCancel التي تسمح لنا باستدعاء دالة لإلغاء السياق؛ ثم الدالة context.WithDeadline مع قيمة time.Time لإنهاء السياق في وقت محدد؛ ثم الدالة context.WithTimeout مع قيمة time.Duration لإنهاء السياق بعد مدة معينة. نضمن من خلال استخدام هذه الدوال أن البرنامج لن يستهلك موارد الحاسب أكثر مما تحتاجه، كما يُسهّل فهم الأخطاء التي تُسببها السياقات عملية استكشاف الأخطاء وإصلاحها في برامج جو. الخاتمة أنشأنا خلال هذا المقال برنامجًا يستخدم حزمة السياق context التي تقدمها لغة جو بطرق مختلفة. أنشأنا دالةً تقبل سياقًا context.Context مثل معامل، واستخدمنا الدالتين context.TODO و context.Background لإنشاء سياق فارغ. بعد ذلك، استخدمنا الدالة context.WithValue لإنشاء سياق جديد يُغلّف سياقًا آخر ويحمل قيمة جديدة، وتعرّفنا على كيفية قراءة هذه القيمة لاحقًا من خلال التابع Value من داخل الدوال الأخرى التي تستخدم هذا السياق. بعد ذلك، تعرفنا على التابع Done الذي يُساعدنا في معرفة الوقت الذي تنتفي فيه الحاجة إلى إبقاء السياق. تعلمنا أيضًا كيف نلغي السياق بطرق مختلفة من خلال الدوال context.WithCancel و context.WithDeadline و context.WithTimeout وكيف نضع حدًا للمدة التي يجب أن تُنفّذ بها التعليمات البرمجية التي تستخدم تلك السياقات. ترجمة -وبتصرف- للمقال How To Use Contexts in Go لصاحبه Kristin Davidson. اقرأ أيضًا المقال السابق استخدام التاريخ والوقت في لغة جو Go بناء تطبيقات لغة Go على أنظمة التشغيل والمعماريات المختلفة.
  17. في المشهد سريع التطور للذكاء الصناعي Artificial Intellegence، تلعب لغات البرمجة الذكاء الاصطناعي دورًا محوريًا في تشكيل الطريقة التي نطّور بها الأنظمة والتطبيقات الذكية. توفر لغات البرمجة للمطورين اللبنات الأساسية لتسخير إمكانات الذكاء الاصطناعي وإنشاء حلول مبتكرة من خلال تسهيل عملية بناء نماذج التعلم الآلي وتطبيقات الرؤية الحاسوبية وأدوات معالجة اللغة الطبيعية. تتعمق هذه المقالة الشاملة في المشهد العام للغات برمجة الذكاء الاصطناعي، وتوجهك من خلال عرض المفاهيم الأساسية للغات البرمجة التي تشكل أساس تطوير الذكاء الاصطناعي. سوف نستكشف الأسباب الكامنة وراء بروز لغات معينة في عالم برمجة الذكاء الاصطناعي، مثل لغة بايثون Python ولغة R، ونطلع على العديد من لغات برمجة الذكاء الاصطناعي الأخرى المُستخدمة في بناء نماذج وأنظمة الذكاء الصناعي. سواء كنت مطورًا طموحًا لتعلّم الذكاء الاصطناعي، أو مبرمجًا متمرّسًا يتطلع إلى الانتقال إلى الذكاء الاصطناعي، أو متحمسًا فضوليًا حريصًا على فهم تفاصيل تقنية الذكاء الاصطناعي، تهدف مقالة لغات برمجة الذكاء الاصطناعي هذه إلى تزويدك بفهم شامل للغات برمجة الذكاء الاصطناعي. دعنا نبدأ رحلة لاكتشاف لغات برمجة الذكاء الصناعي التي تعمل على تعزيز مستقبل الآلات الذكية. معايير اختيار لغات برمجة الذكاء الاصطناعي تلعب لغات البرمجة دورًا محوريًا في مجال الذكاء الاصطناعي. إنها بمثابة الوسيلة التي يتم من خلالها تطوير خوارزميات ونماذج وأنظمة الذكاء الاصطناعي وتنفيذها ونشرها. يمكن فهم أهمية لغات برمجة الذكاء الاصطناعي من خلال عدة جوانب رئيسية نذكر منها ما يلي. التعبير والمرونة توفر لغات البرمجة وسيلة للتعبير عن خوارزميات الذكاء الاصطناعي المعقدة والمنطق بطريقة منظمة ومفهومة. توفر مجموعة واسعة من هياكل البيانات والمكتبات التي تُمكّن مطوري الذكاء الاصطناعي من تنفيذ مختلف تقنيات التعلم الآلي والتعلم العميق. المجتمع تتمتع لغات البرمجة المشهورة المستخدمة في الذكاء الاصطناعي، مثل بايثون، بمجتمعات واسعة ونشطة. وهذا يعزز تبادل المعرفة والتعاون وتطوير مكتبات وأطر الذكاء الاصطناعي. على سبيل المثال، لدى موقع حسوب I/O مجتمع خاص بلغة بايثون، وهو مخصص لطرح الأسئلة وتبادل المعارف المتعلقة بلغة بايثون ..إلخ. السرعة والكفاءة تسمح لغات البرمجة مثل بايثون Python وجوليا Julia وC++‎ لخوارزميات الذكاء الاصطناعي بالعمل بشكل أسرع والتعامل مع مجموعات البيانات الكبيرة بكفاءة. تعتبر تقنيات التحسين في هذه اللغات ضرورية لتطبيقات الذكاء الاصطناعي التي تتطلب معالجة في الزمن الحقيقي Real Time. النماذج الأولية والتجارب لغات مثل بايثون معروفة ببساطتها وسهولة قراءتها، مما يجعلها مثالية للنماذج الأولية السريعة والتجارب في أبحاث وتطوير الذكاء الصناعي. منحنى التعلم بعض لغات البرمجة مثل بايثون Python مناسبة للمبتدئين، مما يتيح لمجموعة واسعة من الأشخاص دخول مجال الذكاء الصناعي. غالبًا ما تحتوي لغات برمجة الذكاء الصناعي على وثائق واسعة النطاق وموارد عبر الإنترنت للتعلم. مكتبات وأطر عمل خاصة بالذكاء الاصطناعي توفر المكتبات وأطر العمل مثل نمباي NumPy وسكاي باي SciPy وباندا pandas في لغة بايثون وظائفًا أساسيةً متعلقة بالذكاء الاصطناعي. قابلية التوسع تصمّم بعض لغات البرمجة لتكون ذات قابلية التوسع، مما يجعلها مناسبة لتطبيقات الذكاء الاصطناعي التي تحتاج إلى معالجة البيانات الضخمة أو العمل في بيئات موزعة. باختصار، لغات البرمجة هي الأدوات الأساسية لتطوير الذكاء الاصطناعي. إنها تمكن مهندسي الذكاء الاصطناعي وعلماء البيانات والباحثين من إنشاء أنظمة ذكية وتحليل البيانات وحل المشكلات المعقدة. يعتمد اختيار لغة البرمجة على تطبيق الذكاء الاصطناعي المحدد وخبرة فريق التطوير ومتطلبات المشروع، ولكن الفهم القوي للغات البرمجة أمر أساسي للنجاح في الذكاء الاصطناعي. دورة الذكاء الاصطناعي احترف برمجة الذكاء الاصطناعي AI وتحليل البيانات وتعلم كافة المعلومات التي تحتاجها لبناء نماذج ذكاء اصطناعي متخصصة. اشترك الآن أشهر لغات برمجة الذكاء الاصطناعي هناك العديد من لغات البرمجة المُستخدمة في مجال الذكاء الصناعي، لكل منها قدرات مختلفة عن الأخرى. نستعرض في هذا القسم أشهر اللغات المستخدمة في برمجة الذكاء الاصطناعي وهي باختصار: لغة بايثون لغة R لغة جافا لغة C++‎ لغة جوليا Julia لغة برولوج Prolog لغة ماتلاب Matlab لغة سويفت لغة رست Rust لغة ليسب Lisp لغة لوا Lua لغة بيرل Perl بايثون في المشهد الكبير لتطوير الذكاء الاصطناعي، تلعب لغات البرمجة دورًا حاسمًا في تشكيل الطريقة التي تتعلم بها الآلات الذكية وتتحدث وتتفاعل مع العالم. برزت لغة بايثون من بين هذه اللغات كحجر زاوية يقود الابتكار والتحوّل في مجال الذكاء الاصطناعي. اكتسبت لغة بايثون، وهي لغة برمجة عالية المستوى ومتعددة الاستخدامات -شعبية هائلة لبساطتها وقابليتها للقراءة وقوّتها. تؤكد فلسفة تصميم اللغة المعروفة باسم زن بايثون Zen of Python، وهي تسعة عشر «مبدءًا توجيهيًا» لكتابة برامج الحاسب -على الأناقة والبساطة والتطبيق العملي. هذه الفلسفة لها صدى جيد بين أوساط ممارسي الذكاء الاصطناعي الذين يبحثون عن أدوات فعّالة لتسهيل حل المشاكل المعقدة. يمكن أن يُعزى صعود بايثون وهيمنتها على مجال برمجة الذكاء الاصطناعي إلى عدة عوامل: بيئة غنية بالمكتبات وأطر العمل: تفتخر بايثون بمجموعة واسعة من المكتبات والأطر المصممة للذكاء الاصطناعي والتعلم الآلي مثل مكتبة نمباي NumPy وباندا Pandas وماتبلوتليب Matplotlib وأطر عمل مثل تنسرفلو Tensorflow وباي تورش PyTorch وسكايت ليرن Scikit-learn التي توفر إمكانات هائلة لتطبيقات التعلم العميق والتعلم الآلي، مما يتيح للمطورين إنشاء نماذج معقدة وتدريبها بسهولة. سهولة الاستخدام وقابلية القراءة: تعمل صياغة بايثون البسيطة والسهلة على تسريع عملية التطوير مما يقلل منحنى التعلم عند العمل في مشاريع الذكاء الاصطناعي المعقدة، حيث تجذب سهولة الاستخدام هذه المطورين المخضرمين والوافدين الجدد إلى هذا المجال. النماذج الأولية السريعة والتجارب: تسمح الطبيعة الديناميكية لبايثون للمطورين بتجربة نماذج الذكاء الاصطناعي بسرعة. تشجع البيئة التفاعلية على النماذج الأولية السريعة، مما يتيح التطوير التكراري والاختبار الفعّال للفرضيات. دعم المجتمع الكبير: يعزز مجتمع بايثون النشط للغاية مشاركة المعرفة والتعاون وإنشاء أدوات مفتوحة المصدر. يمكن للمطورين العثور على وثائق وبرامج تعليمية ومنتديات شاملة للتعاون وتشارك المعرفة وحل المشاكل، مما يسهل التغلب على التحديات ومواكبة أحدث اتجاهات الذكاء الاصطناعي. يوسع تعدد استخدامات بايثون نفوذها عبر مجالات الذكاء الاصطناعي المختلفة، مما يتيح للمطورين صياغة حلول معقدة لمجموعة واسعة من تطبيقات الذكاء الصناعي. معالجة اللغة الطبيعية: بايثون هي الخيار المفضل لمهام البرمجة اللغوية العصبية. توفر مكتبات مثل NLTK و SpaCy أدوات لتحليل النص والمعالجة المسبقة وفهم اللغة وتحليل المشاعر ..إلخ. يمكن للمطورين باستخدام بايثون إنشاء روبوتات محادثة ومترجمات آلية للغات المختلفة وأنظمة تلخيص نصية وكل ما يتعلق بهذا المجال. الرؤية الحاسوبية: مكتبة OpenCV، التي تُستخدم غالبًا مع بايثون، تُساعد المبرمجين على إنشاء تطبيقات رؤية الحاسب المختلفة. يمكن للمطورين بناء أنظمة التعرف على الصور واكتشاف الأشياء والتعرف على الوجه، ودفع التقدم في مجالات مثل المركبات ذاتية القيادة والتصوير الطبي. التعلم الآلي والتعلم العميق: تتيح مكتبات بايثون وأطر عملها إنشاء نماذج التعلم الآلي المختلفة مثل مكتبة سكايت ليرن Scikit-learn. بينما تُسهّل أطر التعلم العميق مثل تنسرفلو Tensorflow وباي تورش PyTorch تطوير الشبكات العصبية للمهام المعقدة مثل التعرّف على الصور وفهم اللغة الطبيعية وتركيب الكلام. يمتد تأثير بايثون إلى ما وراء التطبيقات العملية؛ لقد أصبح عنصرًا أساسيًا في أبحاث الذكاء الاصطناعي. تسمح مرونته وتوافر مكتبات عالية المستوى للباحثين بالتركيز على تصميم الخوارزمية وتجريبها بدلًا من تفاصيل التنفيذ منخفضة المستوى. أدى هذا التسريع في البحث إلى حدوث طفرة في الذكاء الاصطناعي وساهم في التطور السريع لهذا المجال. لغة R برزت لغة R في مجال علم البيانات والتحليل الإحصائي كأداة قوية تُمكّن الباحثين والمحللين ومحترفي البيانات من فتح الرؤى واتخاذ قرارات مستنيرة من البيانات. بفضل مكتباتها وحزمها الواسعة المصممة للحساب الإحصائي وتصور البيانات والنمذجة التنبؤية، أصبحت لغة R حجر الأساس لأولئك الذين يسعون إلى اشتقاق المعاني من مجموعات البيانات المعقدة. طوّرت لغة البرمجة R في البداية في جامعة أوكلاند في عام 1993، مع التركيز بشكل أساسي على الحوسبة والرسومات الإحصائية. أدت طبيعتها مفتوحة المصدر ومجتمعها النشط إلى إنشاء بيئة واسعة النطاق من الحزم التي تلبي مجموعة واسعة من مهام تحليل البيانات. تكمن قوة لغة R في قدرتها على التعامل مع البيانات ومعالجتها وإجراء الاختبارات الإحصائية وإنشاء تصوّرات مقنعة تُسهّل تفسير مجموعات البيانات المعقدة. إن قدرة لغة R على التعامل مع المهام الإحصائية المعقدة وتحليل وتصور البيانات وتلبية متطلبات المجالات ذات الصلة تجعلها أداة لا غنى عنها لمحترفي البيانات عبر الصناعات المختلفة. عند الحديث عن لغات برمجة الذكاء الاصطناعي، فإن أول الخيارات التي يجب أن تفكر فيها هي بايثون ولغة R، فهما الخياران الأكثر شيوعًا بين المطورين والشركات. لكن هذا لايعني أن نتجاهل لغات برمجة أخرى يمكن استخدامها لدعم تطبيقات الذكاء الاصطناعي والتي تلبي تطبيقات وتفضيلات خاصة في الذكاء الاصطناعي سنذكرها تاليًا. جافا Java وجدت لغة جافا المعروفة بقوتها وقابليتها للنقل -طريقها إلى تطوير الذكاء الاصطناعي. مع مكتبات مثل Deeplearning4j و DL4J، يمكن لعشاق جافا الاستفادة من قوة التعلم العميق والشبكات العصبية. إن بنية جافا غرضية التوجّه وتعدد الاستخدامات الذي تتمتع به والمكتبات الواسعة تجعلها مناسبة لبناء حلول الذكاء الاصطناعي التي تتطلب قابلية التوسع والتكامل مع تطبيقات جافا. لغة C++‎ تحظى لغة C++‎ بتقدير كبير على أدائها وتحكمها، مما يجعلها خيارًا مناسبًا لتطبيقات الذكاء الاصطناعي التي تتطلب الكفاءة الحوسبية. توفر مكتبات مثل Shark و dlib قدرات التعلم الآلي ورؤية الحاسب. تعد سرعة C++‎ أمرًا بالغ الأهمية في تطبيقات الزمن الحقيقي مثل المركبات ذاتية القيادة والروبوتات، حيث تكون القرارات في أجزاء من الثانية أمرًا ضروريًا. جوليا Julia تزداد شعبية جوليا في برمجة الذكاء الاصطناعي بشكل مطرد بسبب تركيزها على الحوسبة الرقمية عالية الأداء، إذ يمكن من خلال تصريف JIT الخاص بها مطابقة أداء لغات مثل C++‎ مع تقديم بنية أكثر سهولة في الاستخدام، وتكمن نقطة جوليا الرائعة في التطبيقات كثيفة البيانات والمحاكاة والحوسبة العلمية. برولوج Prolog يشيع استخدام لغة برولوج Prolog في برمجة الذكاء الاصطناعي وهي لغة برمجة منطقية، تتفوق في المهام التي تتضمن التفكير والاستدلال. يُبسّط بناء الجملة التصريحي الذي تتبعه اللغة بناء الأنظمة المعقدة المستندة إلى القواعد، مما يجعلها مناسبة للأنظمة الخبيرة وفهم اللغة الطبيعية. ماتلاب MATLAB إن اقتران لغة ماتلاب بالحوسبة الرياضية والذكاء الاصطناعي يجعلها المفضلة لدى الكثير من المهندسين والباحثين. يوفر صندوق الأدوات الغني الخاص بها دوالًا لمعالجة الإشارات وتحليل الصور والتعلم الآلي. سويفت Swift هي لغة برمجة سهلة الاستخدام، ويسهل تعلمها عند مقارنتها باللغات الأخرى المستخدمة عادةً لتطبيقات الذكاء الاصطناعي. تخطو لغة سويفت (لغة برمجة Apple) خطوات واسعة في مجال الذكاء الاصطناعي، حيث برز Swift for TensorFlow كأداة قوية تجمع بين بناء الجملة البديهي وإمكانيات تحسين الأداء من لغة سويفت مع بيئة تنسرفلو. رست Rust إن تركيز لغة رست على السلامة والتزامن يجعلها مُرشحًا مُقنعًا لبناء أنظمة آمنة للذكاء الاصطناعي. على الرغم من عدم ارتباطه تقليديًا بالذكاء الاصطناعي، إلا أن ميزات راست الحديثة وإدارة الذاكرة القوية يمكن أن تساهم في بناء حلول ذكاء اصطناعي موثوقة تعطي الأولوية للأمان. ليسب Lisp تحمل لغة ليسب علاقة فريدة وتاريخية بمجال الذكاء الاصطناعي. طوّرت ليسب بواسطة جون مكارثي في أواخر الخمسينيات من القرن الماضي، وتم تصميمها كلغة برمجة مناسبة خصيصًا للبحث والتطوير في مجال الذكاء الاصطناعي. إن طبيعتها المرنة والتي تعتمد على الرموز جعلتها خيارًا مثاليًا لبناء الانظمة الخبيرة وتمثيل المعرفة والمنطق، وهذا ما يتوافق بشكل جيد مع أهداف أنظمة الذكاء الاصطناعي في ذلك الوقت. لوا Lua هي لغة برمجة خفيفة بسيطة تُستخدم غالبًا لدمج مكونات الذكاء الاصطناعي في التطبيقات والألعاب، وهي معروفة ببساطتها وكفاءتها. بيرل Perl تشتهر لغة بيرل بقدراتها على معالجة النصوص وتستخدم أحيانًا للمهام التي تتضمن معالجة البيانات في مشاريع الذكاء الاصطناعي. أخيرًا وليس آخرًا، يمتد مشهد برمجة الذكاء الاصطناعي إلى ما هو أبعد من بايثون Python ولغة R، مما يوفر عددًا كبيرًا من الخيارات للمطورين لاستكشافها. تقدم كل لغة نقاط قوتها الفريدة إلى الطاولة، حيث تلبي مجالات وتطبيقات الذكاء الاصطناعي المختلفة. من تعدد استخدامات لغة جافا إلى سرعة C++ إلى حوسبة جوليا عالية الأداء إلى معالجة برولوغ وليسب المنطقية، فإن لدينا خيارات كثيرة كمبرمجين. مقارنة بين لغات برمجة الذكاء الاصطناعي تتضمن مقارنة لغات برمجة الذكاء الاصطناعي تقييم ميزاتها وقدراتها ومدى ملاءمتها للمهام المتعلقة بالذكاء الاصطناعي. لقد استعرضنا في فقرة سابقة بالفعل ميزات كل لغة برمجة فيما يتعلق بمجال الذكاء الصناعي، لذا نُسلّط في هذا القسم الضوء على التحديات ونقاط الضعف لأبرز لغات البرمجة التي ذكرناها. سلبيات لغة بايثون Python أثبتت لغة بايثون نفسها كقوة كبيرة في مجال البرمجة عمومًا. ومع ذلك، ومثل أي تقنية، فإن بايثون لا تخلو من التحديات والاعتبارات: الأداء والسرعة: قد نرى أحيانًا اختناقات في الأداء وبطء في لغة بايثون، خاصةً عند مقارنتها بلغات مثل C++‎ أو Java. يمكن أن يكون هذا ملحوظًا بشكل خاص عند التعامل مع مهام حسابية مكثفة أو مجموعات بيانات كبيرة أو معالجة في الوقت الفعلي. لمواجهة هذا التحدي، غالبًا ما يلجأ المطورون إلى تقنيات التحسين، مثل استخدام المكتبات الخارجية (مثلًا مكتبة مكتوبة بلغة سي C++‎ نستخدمها في بايثون)، وأبرز مثال على ذلك مكتبة نمباي للحسابات الرقمية. ** التنفيذ المتوازي**: بايثون لاتدعم فكرة تعدد الخيوط multi-threading والبرمجة المتوازية بشكل جيد، مما يعني أن بايثون قد لا تستفيد بشكل كامل من المعالجات متعددة النواة للتنفيذ المتوازي. يلجأ المطورون أحيانًا إلى استخدام لغات أخرى جنبًا إلى جنب مع بايثون للتغلب على هذا القيد، أي بشكل مشابه لما ذكرناه منذ قليل. استهلاك الذاكرة: يمكن أن يكون استهلاك الذاكرة في بايثون أعلى أحيانًا من استهلاك لغات مثل C أو C++‎. يمكن أن يكون هذا مصدر قلق عند التعامل مع التطبيقات واسعة النطاق أو البيئات محدودة الموارد. إدارة تبعية المكتبات: تعد بيئة بايثون الشاملة نقطة قوة بلا شك، ولكنها يمكن أن تشكل أيضًا تحديات من حيث إدارة التبعية. مع نمو المشاريع والاعتماد على مكتبات خارجية متعددة، قد يصبح الحفاظ على التوافق وإدارة التبعيات أمرًا معقدًا. يمكن أن تظهر تعارضات الإصدار والحزم المهملة والثغرات الأمنية إذا لم تتم إدارتها بعناية. سلبيات لغة R بينما تعد لغة R أداة قوية لعلوم البيانات، إلا أنها لا تخلو من التحديات أيضًا: الأداء: كما في لغة بايثون، قد تعاني لغة R من بطء تنفيذ العمليات الحسابية الكبيرة. استخدام الذاكرة: قد يتطلب التعامل مع مجموعات البيانات الكبيرة إدارة دقيقة للذاكرة. منحنى التعلم: يمكن أن يكون إتقان لغة البرمجة R وحزمها مضيعة للوقت للمبتدئين نظرًا لصعوبتها. سلبيات لغة جافا Java تُعتبر لغة جافا خيارًا قيمًا لتطوير الذكاء الاصطناعي في السيناريوهات التي تفوق فيها نقاط قوتها، مثل الاستقلالية عن نظام التشغيل والأداء وقابلية التوسع. إلا أن لها العديد من التحديات، أهمها: الكود المطول: تشتهر لغة جافا بإسهابها verbosity، مما يعني الحاجة إلى قدر كبير من التعليمات البرمجية للتعبير عن وظيفة معينة. هذا يمكن أن يجعل تطوير الذكاء الاصطناعي في جافا أكثر استهلاكًا للوقت مقارنة باللغات ذات بناء الجملة الأكثر إيجازًا مثل بايثون التي يمكننا فيها التعبير عن وظيفة معينة ببضع تعليمات برمجية. منحنى التعلم: تتميز جافا بمنحنى تعليمي أكثر حدة، خاصة للمبتدئين في برمجة الذكاء الاصطناعي. يمكن أن يكون نظام الكتابة الصارم وبناء الجملة المعقد أمرًا مخيفًا لأولئك الجدد في اللغة. نماذج أولية متأخرة: قد لا تكون الخيار الأفضل للنماذج الأولية السريعة والتجارب (مثلًا تريد بناء تطبيق ذكاء اصطناعي بسرعة لتجربة فكرة أو ميزة معينة). غالبًا ما يفضل باحثو الذكاء الاصطناعي لغات مثل بايثون أو لغة R، والتي تتيح لهم اختبار الأفكار والخوارزميات بسرعة. مكتبات محدودة: على الرغم من أن جافا تحتوي على مكتبات للذكاء الاصطناعي، إلا أن نظامها البيئي ليس غنيًا وواسع النطاق مثل نظام بايثون. أعباء الأداء: يمكن أن يكون أداء جافا أقل قليلاً من لغات مثل C++‎ أو جوليا Julia. النظام البيئي للتعلم العميق: النظام البيئي للتعلم العميق في جافا ليس ناضجًا مثل نظام بايثون. في حين أنه من الممكن بناء نماذج التعلم العميق في جافا، فإن مجتمع التعلم العميق يستخدم في الغالب بايثون لهذا الغرض. سلبيات لغة C++‎ على الرغم من السرعة والأداء العالي الذي تتمتع به هذه اللغة، إلا أنها مثل أي لغة برمجة أخرى لها جوانب سلبية في سياقات معينة، وأهمها: منحنى التعلم: كما في جافا وربما أكثر. إدارة الذاكرة: تتطلب إدارة يدوية للذاكرة، مما قد يؤدي إلى تسرب الذاكرة وحدوث أخطاء إذا لم يتم التعامل معها بشكل صحيح. مكتبات أقل خاصة بالذكاء الاصطناعي: ليست واسعة النطاق مثل تلك المتوفرة في بايثون Python أو لغة R. الكود المطول: كما في جافا. عدم الاستقلالية عن نظام التشعيل: مما يجعله أقل قابلية للتنقل عبر الأنظمة المختلفة دون تعديلات. سلبيات ماتلاب MATLAB يُعتبر ماتلاب MATLAB برنامجًا خاصًا يتطلب ترخيصًا للاستخدام التجاري، مما يجعله مكلفًا (ممكن أن يكون باهظ الثمن) للشركات وأنظمة الإنتاج واسعة النطاق. كما أن مكتباتها ليست غنية ودعم المجتمع لها محدود ومنحنى التعلم فيها حاد. بالنسبة للغات البرمجة الأخرى فمعظمها تعاني من مشاكل مثل الدعم المجتمعي الضعيف وقلة المكتبات. يعتمد اختيار لغة برمجة الذكاء الاصطناعي على عوامل مثل مجال الذكاء الاصطناعي المحدد ومتطلبات المشروع والمهارات الحالية ودعم المجتمع. لا تزال لغة بايثون هي اللغة المهيمنة في الذكاء الاصطناعي بسبب نظامها البيئي الواسع، ولكن اللغات الأخرى يمكن أن تكون ذات قيمة للمهام المتخصصة أو احتياجات المشروع الفريدة، فكل لغة من لغات برمجة الذكاء الاصطناعي لديها مزاياها الفريدة وسلبياتها، مثلًا تتفوق لغة R في التحليل الإحصائي وتصوّر البيانات، مما يجعلها الخيار الأفضل في مجال تحليل البيانات. تشتهر جوليا بقدراتها الحاسوبية عالية الأداء، مما يجعلها مناسبة لتطبيقات الذكاء الاصطناعي التي تتطلب حسابات مكثفة. يعد سويفت Swift من خلال دمجه في منصات الأجهزة المحمولة، مثاليًا لتطبيقات الأجهزة المحمولة التي تعمل بالذكاء الاصطناعي. في النهاية، يجب أن يتوافق اختيار لغة البرمجة مع المتطلبات المحددة لمشروعك وخبرة فريقك. من الضروري الموازنة بين إيجابيات وسلبيات كل لغة والأخذ في الاعتبار عوامل مثل السرعة ودعم المكتبة وقابلية التوسع لاتخاذ قرار مستنير يهيئ مشروع الذكاء الاصطناعي الخاص بك لتحقيق النجاح. الخاتمة: تشكيل مستقبل الذكاء الاصطناعي بلغات البرمجة ظهرت لغات البرمجة باعتبارها حجر الأساس الذي يُبنى عليه مستقبل الآلات الذكية. تلعب هذه الأدوات دورًا تحويليًا، ليس فقط في تسخير قوة البيانات والخوارزميات ولكن أيضًا في إطلاق العنان لإمكانية إنشاء حلول مبتكرة كانت تعتبر في السابق غير قابلة للتحقيق. أصبحت لغات برمجة الذكاء الاصطناعي مثل لغة بايثون ولغة R حجر الأساس في تطوير تطبيقات الذكاء الاصطناعي، مما يوفر للمطورين خيارات كثيرة لبناء النماذج والخوارزميات المعقدة. تعمل هذه اللغات على تمكين المبرمجين من معالجة البيانات وتحليلها وتصميم شبكات عصبية معقدة وصياغة أنظمة ذكية يمكنها التعلم والتكيف. كما في أي مجال علمي آخر هناك قيود واعتبارات لابد من الإشارة إليها لأخذها بعين الاعتبار. من المهم لممارسي الذكاء الاصطناعي اختيار لغة البرمجة التي تتوافق بشكل أفضل مع متطلبات مشروعهم المحددة مع إدراكهم للقيود المحتملة وإيجاد طرق للتغلب عليها. لا يقوم المطورون فقط بإنشاء التعليمات البرمجية؛ إنهم يصممون مستقبل الذكاء نفسه. نظرًا لأننا نقف على أعتاب الاحتمالات التي لم يتم استكشافها بعد، هناك شيء واحد مؤكد: رحلة برمجة الذكاء الاصطناعي لا تشكل المستقبل فحسب، بل تعمل أيضًا على تمكين المطورين من صياغة حلول ذكية تحدد عالم الغد. اقرأ أيضًا تعلم الذكاء الاصطناعي تعلم البرمجة أسهل لغات البرمجة مستويات لغات البرمجة أهمية الذكاء الاصطناعي مستقبل الذكاء الاصطناعي
  18. صُممت البرمجيات لتسهيل إنجاز الأعمال، وفي بعض الأحيان يتطلب الأمر التعامل مع التاريخ والوقت، إذ يمكن رؤية قيم التاريخ والوقت في معظم البرامج الحديثة، فمثلًا لو أردنا تطوير تطبيق يعطينا تنبيهات عن أوقات الصلاة، سيتوجب على البرنامج تشغيل تنبيه عند وقت وتاريخ محدد. مثال آخر هو تتبع الوقت في السيارات الحديثة، إذ نحتاج إلى إرسال تنبيهات إلى مالك السيارة لإبلاغه عن وجود مشكلة أو حاجة معينة للسيارة، أو تتبع التغييرات في قاعدة بيانات لإنشاء سجل تدقيق، أو الوقت الذي يتطلبه إنجاز عملية ما مثل عبور شارع معين للوصول إلى الوجهة …إلخ. يشير هذا إلى ضرورة التعامل مع التاريخ والوقت في البرامج والتفاعل معها وعرضها على المستخدمين بتنسيق واضح وسهل الفهم، فهذه خاصية أساسية لهكذا تطبيقات. سنُنشئ خلال هذا المقال برنامج جو يمكنه معرفة التوقيت المحلي الحالي من خلال جهاز الحاسب الذي يعمل عليه، ثم عرضه على الشاشة بتنسيق سهل القراءة. بعد ذلك، سنفسّر سلسلة نصية لاستخراج معلومات التاريخ والوقت، كما سنغيّر أيضًا قيم التاريخ والوقت من منطقة زمنية إلى أخرى، ونضيف أو نطرح قيم الوقت لتحديد الفاصل الزمني بين وقتين. المتطلبات لتتابع هذا المقال، ستحتاج إلى: إصدار مُثبّت من جو 1.16 أو أعلى، ويمكنك الاستعانة بمقال تثبيت لغة جو Go وإعداد بيئة برمجة محلية على أبونتو Ubuntu لإعداده. تثبيت لغة جو وإعداد بيئة برمجة محلية على نظام ماك macOS. تثبيت لغة جو وإعداد بيئة برمجة محلية على ويندوز. الحصول على التاريخ والوقت الحالي سنستخدم خلال هذا القسم حزمة لغة جو time للوصول إلى قيم التاريخ والوقت الحالي. تُقدّم لنا هذه الحزمة القياسية من مكتبة لغة جو القياسية -العديد من الدوال المتعلقة بالتاريخ والوقت، والتي يمكن من خلالها تمثيل نقاط معينة من الوقت باستخدام النوع time.Time. يمكننا أيضًا من خلال هذه الدوال التقاط بعض المعلومات عن المنطقة الزمنية التي تمثِّل التاريخ والوقت المقابل. كما هي العادة، سنحتاج لبدء إنشاء برامجنا إلى إنشاء مجلد للعمل ووضع الملفات فيه. يمكن أن نضع المجلد في أي مكان على الحاسب، إذ يكون للعديد من المبرمجين عادةً مجلدٌ يضعون داخله كافة مشاريعهم. سنستخدم في هذا المقال مجلدًا باسم "projects"، لذا فلننشئ هذا المجلد وننتقل إليه: $ mkdir projects $ cd projects الآن، من داخل هذا المجلد، سنشغّل الأمر mkdir لإنشاء مجلد "datetime" ثم سنستخدم cd للانتقال إليه: $ mkdir datetime $ cd datetime يمكننا الآن فتح ملف main.go باستخدام محرر نانو nano أو أي محرر آخر تريده: $ nano main.go نضيف الدالة main التي سنكتب فيها تعليمات الحصول على التاريخ والوقت وعرضهما: package main import ( "fmt" "time" ) func main() { currentTime := time.Now() fmt.Println("The time is", currentTime) } استخدمنا الدالة time.Now من الحزمة time للحصول على الوقت الحالي مثل قيمة من النوع time.Time وتخزينها في المتغير currentTime، ثم طبعنا قيمة هذا المتغير باستخدام الدالة fmt.Println، إذ سيُطبع وفقًا لتنسيق سلسلة نصية افتراضي خاص بالنوع time.Time. شغّل الآن ملف البرنامج "main.go" باستخدام الأمر go run: $ go run main.go سيبدو الخرج الذي يعرض التاريخ والوقت الحاليين مشابهًا لما يلي: The time is 2021-08-15 14:30:45.0000001 -0500 CDT m=+0.000066626 طبعًا في كل مرة نُشغّل فيها هذا البرنامج سيكون التاريخ والوقت مختلفين، كما أن المنطقة الزمنية ‎0500 CDT- مُتغيرة تبعًا للمنطقة الزمنية التي ضُبط عليها الحاسب كما سبق وأشرنا. نلاحظ وجود القيمة =m، التي تُشير إلى ساعة رتيبة monotonic clock، وتُستخدم ضمنيًّا في جو عند قياس الاختلافات في الوقت، وقد صُممت لتعويض التغييرات المحتملة في تاريخ ووقت ساعة نظام الحاسب أثناء تشغيل البرنامج. من خلال هذه الساعة، ستبقى القيمة المُعادة من الدالة time.Now صحيحة حتى لو جرى تغيير ساعة نظام الحاسب لاحقًا. مثلًا لو استدعينا الدالة time.Now الآن وكان الوقت 10:50، ثم بعد دقيقتين جرى تأخير لساعة الحاسب بمقدار 60 دقيقة، ثم بعد 5 دقائق (من الاستدعاء الأول للدالة time.Now) استدعينا الدالة time.Now مرةً أخرى (في نفس البرنامج)، سيكون الخرج 10:55 وليس 9:55. لست بحاجة إلى فهم أكثر من ذلك حول آلية عمل هذه الساعة خلال المقال، لكن إذا كنت ترغب في معرفة المزيد حول الساعات الرتيبة وكيفية استخدامها، لكن يمكنك الذهاب إلى التوثيق الرسمي ورؤية المزيد من التفاصيل لو أحببت. قد يكون التنسيق الذي يظهر به التاريخ والوقت على شاشة الخرج غير مناسب وقد ترغب بتغييره أو أنه يتضمن أجزاء من التاريخ أو الوقت أكثر مما تريد عرضه. لحسن الحظ، يوفر النوع time.Time العديد من الدوال لتنسيق عرض التاريخ والوقت وعرض أجزاء محددة منهما؛ فمثلًا لو أردنا معرفة السنة فقط من المتغير currentTime يمكننا استخدام التابع Year أو يمكننا عرض الساعة فقط من خلال التابع Hour. لنفتح ملف "main.go" مرةً أخرى ونضيف التعديلات التالية لرؤية ذلك: ... func main() { currentTime := time.Now() fmt.Println("The time is", currentTime) fmt.Println("The year is", currentTime.Year()) fmt.Println("The month is", currentTime.Month()) fmt.Println("The day is", currentTime.Day()) fmt.Println("The hour is", currentTime.Hour()) fmt.Println("The minute is", currentTime.Hour()) fmt.Println("The second is", currentTime.Second()) } شغّل البرنامج باستخدام go run: $ go run main.go سيكون الخرج: The time is 2021-08-15 14:30:45.0000001 -0500 CDT m=+0.000066626 The year is 2021 The month is August The day is 15 The hour is 14 The minute is 14 The second is 45 كما ذكرنا منذ قليل: في كل مرة نُشغّل فيها هذا البرنامج سيكون التاريخ أو الوقت مختلفين، لكن التنسيق يجب أن يكون متشابهًا. طبعنا في هذا المثال التاريخ كاملًا (السطر الأول)، ثم استخدمنا توابع النوع time.Time لعرض تفاصيل محددة من التاريخ كلٌ منها بسطر منفرد؛ بحيث عرضنا السنة ثم الشهر ثم اليوم ثم الساعة وأخيرًا الثواني. ربما نلاحظ أن الشهر طُبع اسمه August وليس رقمه كما في التاريخ الكامل، وذلك لأن التابع Month يعيد الشهر على أنه قيمة من النوع time.Month بدلًا من مجرد رقم، ويكون التنسيق عند طباعته سلسلة نصية string. لنحدّث ملف "main.go" مرةً أخرى ونضع استدعاءات التوابع السابقة كلها ضمن دالة fmt.Printf لنتمكن من طباعة التاريخ والوقت الحاليين بتنسيق أقرب إلى ما قد نرغب في عرضه على المستخدم: ... func main() { currentTime := time.Now() fmt.Println("The time is", currentTime) fmt.Printf("%d-%d-%d %d:%d:%d\n", currentTime.Year(), currentTime.Month(), currentTime.Day(), currentTime.Hour(), currentTime.Hour(), currentTime.Second()) } شغّل البرنامج باستخدام go run: $ go run main.go سيكون الخرج على النحو التالي: The time is 2021-08-15 14:30:45.0000001 -0500 CDT m=+0.000066626 2021-8-15 14:14:45 الخرج الآن أقرب بكثير إلى ما نريد، ولكن لا تزال هناك بعض الأشياء التي يمكن تعديلها في الخرج، فمثلًا عُرض الشهر رقمًا هذه المرة، لأننا استخدمنا العنصر النائب d% الذي سيُجبر النوع time.Month على استخدام رقم وليس سلسلة نصية. هنا قد نفضل عرض رقمين 08 بدلًا من رقم واحد 8، وبالتالي يجب أن نغيّر تنسيق fmt.Printf وفقًا لذلك، ولكن ماذا لو أردنا أيضًا إظهار الوقت بتنسيق 12 ساعة بدلًا من 24 ساعة كما هو موضح في الخرج أعلاه؟ سيتطلب هذا بعض العمليات الحسابية الخاصة ضمن الدالة fmt.Printf. بالرغم من إمكانية طباعة التواريخ والأوقات باستخدام fmt.Printf، ولكن يمكن أن يُصبح اﻷمر مرهقًا إذا أردنا إجراء تنسيقات أعقد، فقد ينتهي بنا الأمر بكتابة عدد كبير من الأسطر البرمجية لكل جزء نريد عرضه، أو قد نحتاج إلى إجراء عدد من العمليات الحسابية الخاصة لتحديد ما نريد عرضه. أنشأنا في هذا القسم برنامجًا يستخدم الدالة time.Now للحصول على الوقت الحالي واستخدمنا دوال مثل Year و Hour على النوع time.Time لعرض معلومات عن التاريخ والوقت الحاليين، كما تعرّفنا على كيفية إجراء تنسيقات بسيطة على آلية عرضهما، مثل عرض أجزاء محددة من التاريخ والوقت. رأينا أيضًا أن عملية الحصول على تنسيقات أعقد تصبح أصعب باستخدام الدالة fmt.Printf. لتسهيل الأمر توفر لغة جو تابع خاص لتنسيق التواريخ والأوقات ويعمل بطريقة مشابهة لعمل الدالة fmt.Printf التي استخدمناها في البداية. طباعة وتنسيق تواريخ محددة إضافة إلى التوابع التي رأيناها في القسم السابق، يوفر النوع time.Time تابعًا يُدعى Format، يسمح لك بتقديم نسق layout على هيئة سلسلة نصية string (بطريقة مشابهة لتنسيق السلاسل في دالتي fmt.Printf و fmt.Sprintf). يُخبر هذا النسق التابع Format بالشكل الذي نريده لطباعة التاريخ والوقت. نستخدم خلال هذا القسم نفس الشيفرة السابقة، ولكن بطريقة أكثر إيجازًا وسهولةً من خلال التابع Format. بدايةً ربما يكون من الأفضل معرفة كيف يؤثر تابع Format على خرج التاريخ والوقت في حال لم يتغير في كل مرة نُشغّل البرنامج، أي عندما نُثبّت التاريخ والوقت. نحصل حاليًا على الوقت الحالي باستخدام time.Now، لذلك في كل مرة نُشغّل البرنامج سيظهر رقم مختلف. هناك دالةٌ مفيدة أخرى توفرها الحزمة time، وهي الدالة time.Date التي تسمح بتحديد تاريخ ووقت محددين لتمثيلهم مثل قيم من النوع time.Time. لنبدأ باستخدام الدالة time.Date بدلًا من time.Now في البرنامج. افتح ملف "main.go" مرةً أخرى واستبدل time.Now بدالة time.Date: ... func main() { theTime := time.Date(2021, 8, 15, 14, 30, 45, 100, time.Local) fmt.Println("The time is", theTime) } تتضمن معاملات الدالة time.Date السنة والشهر واليوم والساعة والدقيقة والثواني من التاريخ والوقت اللذين نريد تمثيلهما للنوع time.Time. يمثل المعامل قبل الأخير النانو ثانية، والمعامل الأخير هو المنطقة الزمنية المطلوب إنشاء الوقت لها (نُغطي موضوع المناطق الزمنية لاحقًا). شغّل البرنامج باستخدام go run: $ go run main.go سيكون الخرج على النحو التالي: The time is 2021-08-15 14:30:45.0000001 -0500 CDT سيكون الخرج الذي الذي سنراه في كل مرة نُشغّل فيها البرنامج هو نفسه باستثناء المنطقة الزمنية لأنها تعتمد على المنطقة الزمنية لجهاز الحاسب الذي تُشغّل عليه، وذلك لأننا نستخدم تاريخ ووقت محددين. طبعًا لا يزال تنسيق الخرج مشابهًا لما رأيناه من قبل، لأن البرنامج لا يزال يستخدم التنسيق الافتراضي للنوع time.Time. بعد أن أصبح لدينا تاريخ ووقت قياسي للعمل به -يمكننا استخدامه لتغيير كيفية تنسيق الوقت عند عرضه باستخدام التابع Format. تنسيق عرض التاريخ والوقت باستخدام تابع Format تتضمن العديد من لغات البرمجة طريقة مشابهة لتنسيق التواريخ والأوقات التي تُعرض، ولكن الطريقة التي تُنشئ بها لغة جو تصميمًا لتلك التنسيقات قد تكون مختلفةً قليلًا عن باقي لغات البرمجة. يستخدم تنسيق التاريخ في اللغات الأخرى نمطًا مشابهًا لكيفية عمل Printf في لغة جو، إذ يُستخدم محرف % متبوعًا بحرف يمثل جزءًا من التاريخ أو الوقت المطلوب إدراجه. مثلًا قد تُمثّل السنة المكونة من 4 أرقام بواسطة العنصر النائب Y% موضوعًا ضمن السلسلة؛ أما في لغة جو تُمثّل هذه الأجزاء من التاريخ أو الوقت بمحارف تمثل تاريخًا محددًا. مثلًا، لتمثيل سنة معينة من 4 أرقام نكتب 2006 ضمن السلسلة. تتمثل فائدة هذا النوع من التصميم في أن ما نراه في الشيفرة يمثل ما سنراه في الخرج، وبالتالي عندما نكون قادرين على رؤية شكل الخرح، فإنه يسهل علينا التحقق من أن التنسيق الذي كتبناه يُطابق ما نبحث عنه، كما أنه يسهل على المطوّرين الآخرين فهم خرج البرنامج دون تشغيل البرنامج أصلًا. يعتمد جو التصميم الافتراضي التالي، لعرض التاريخ والوقت: 01/02 03:04:05PM '06 -0700 إذا نظرت إلى كل جزء من التاريخ والوقت في هذا التصميم الافتراضي، فسترى أنها تزيد بمقدار واحد لكل جزء. يأتي الشهر أولًا 01 ثم يليه يوم الشهر 02 ثم الساعة 03 ثم الدقيقة 04 ثم الثواني 05 ثم السنة 06 (أي 2006)، وأخيرًا المنطقة الزمنية 07. يسهل هذا الأمر إنشاء تنسيقات التاريخ والوقت مستقبلًا. يمكن أيضًا العثور على أمثلة للخيارات المتاحة للتنسيق في توثيق لغة جو الخاصة بحزمة time. سنستخدم الآن التابع الجديد Format لاستبدال وتنظيف تنسيق التاريخ الذي طبعناه في القسم الأخير. يتطلب الأمر -بدون Format- عدة أسطر واستدعاءات لدوال من أجل عرض ما نريده، لكن باستخدام هذه الدالة يُصبح الأمر أسهل وأنظف. لنفتح ملف "main.go" ونضيف استدعاء جديد للدالة fmt.Println ونمرر لها القيمة المُعادة من استدعاء التابع Format على المتغير theTime: ... func main() { theTime := time.Date(2021, 8, 15, 14, 30, 45, 100, time.Local) fmt.Println("The time is", theTime) fmt.Println(theTime.Format("2006-1-2 15:4:5")) } إذا نظرنا إلى التصميم المستخدم للتنسيق، فسنرى أنه يستخدم نفس التصميم الافتراضي في تحديد كيفية تنسيق التاريخ (January 2, 2006). شيء واحد يجب ملاحظته هو أن الساعة تستخدم 15 بدلًا من 03. يوضح هذا أننا نرغب في عرض الساعة بتنسيق 24 ساعة بدلًا من تنسيق 12 ساعة. شغّل البرنامج باستخدام go run: $ go run main.go سيكون الخرج على النحو التالي: The time is 2021-08-15 14:30:45.0000001 -0500 CDT 2021-8-15 14:30:45 نلاحظ أننا حصلنا على خرج مماثل للقسم الأخير لكن بطريقة أبسط. كل ما نحتاج إليه هو سطر واحد من التعليمات البرمجية وسلسلة تمثّل التصميم، ونترك الباقي للدالة Format. اعتمادًا على التاريخ أو الوقت الذي نعرضه، من المحتمل أن يكون استخدام تنسيق متغير الطول مثل التنسيق السابق عند طباعة الأرقام مباشرةً صعب القراءة لنا أو للمستخدمين أو لأي شيفرة أخرى تحاول قراءة القيمة. يؤدي استخدام 1 لتنسيق الشهر إلى عرض شهر آذار (مارس) بالرقم 3، بينما يحتاج شهر تشرين الأول (أكتوبر) محرفين ويظهر بالرقم 10. افتح الملف "main.go" وأضِف سطرًا إضافيًا إلى البرنامج لتجربة نسق آخر أكثر تنظيمًا. هذه المرة سنضع بادئة 0 قبل أجزاء التاريخ والوقت الفردية، ونجعل الساعة تستخدم تنسيق 12 ساعة. func main() { theTime := time.Date(2021, 8, 15, 14, 30, 45, 100, time.Local) fmt.Println("The time is", theTime) fmt.Println(theTime.Format("2006-1-2 15:4:5")) fmt.Println(theTime.Format("2006-01-02 03:04:05 pm")) } شغّل البرنامج باستخدام go run: $ go run main.go سيكون الخرج: The time is 2021-08-15 14:30:45.0000001 -0500 CDT 2021-8-15 14:30:45 2021-08-15 02:30:45 pm بإضافة بادئة 0 إلى أجزاء سلسلة النسق، يصبح الرقم 8 للشهر في الخرج الجديد 08، ونفس الأمر بالنسبة للساعة التي أصبحت الآن بتنسيق 12 ساعة. لاحظ أن النسق الذي نكتبه يوافق ما نراه في الخرج، وهذا يجعل الأمور أبسط. في بعض الحالات يكون هناك برامج أخرى تتفاعل مع البرنامج الذي لدينا، وقد يتضمن ذلك التعامل مع تنسيقات وتصميمات التواريخ في برنامجنا، كما أنه قد يكون من المتعب ومن العبء إعادة إنشاء هذه التنسيقات في كل مرة. في هذه الحالة ربما يكون من الأبسط استخدام تصميم تنسيق مُعرّف مُسبقًا ومعروف. استخدام تنسيقات معرفة مسبقا هناك العديد من التنسيقات جاهزة الاستخدام والشائعة للتاريخ، مثل العلامات الزمنية Timestamps لرسائل التسجيل log messages، وفي حال أردت إنشاء هكذا تنسيقات في كل مرة تحتاجها، سيكون أمرًا مملًا ومتعبًا. تتضمن حزمة time تنسيقات جاهزة يمكنك استخدامها لجعل الأمور أسهل واختصار الجهد المكرر. أحد هذه التنسيقات هو RFC 3339، وهو مستند يُستخدم لتحديد كيفية عمل المعايير على الإنترنت، ويمكن بعد ذلك بناء تنسيقات RFC على بعضها. تحدد بعض تنسيقات RFC مثل RFC 2616 كيفية عمل بروتوكول HTTP، وهناك تنسيقات تُبنى على هذا التنسيق لتوسيع تعريف بروتوكول HTTP. لذا في حالة RFC 3339، يحدد RFC تنسيقًا قياسيًا لاستخدامه في الطوابع الزمنية على الإنترنت. التنسيق معروف ومدعوم جيدًا عبر الإنترنت، لذا فإن فرص رؤيته في مكان آخر عالية. تُمثّل جميع تنسيقات الوقت المُعرّفة مسبقًا في الحزمة الزمنية time بسلسلة ثابتة const string تُسمى بالتنسيق الذي تمثله. هناك اثنين من التنسيقات المتاحة للتنسيق RFC 3339، هما: time.RFC3339 و time.RFC3339Nano، والفرق بينهما أن التنسيق الثاني يُمثّل الوقت بالنانو ثانية. لنفتح الآن ملف "main.go" ونعدّل البرنامج لاستخدام تنسيق time.RFC3339Nano للخرج: ... func main() { theTime := time.Date(2021, 8, 15, 14, 30, 45, 100, time.Local) fmt.Println("The time is", theTime) fmt.Println(theTime.Format(time.RFC3339Nano)) } نظرًا لأن التنسيقات المعرّفة مسبقًا هي قيم من النوع string للتنسيق المطلوب، نحتاج فقط إلى استبدال التنسيق الذي كُنا تستخدمه عادةً بهذا التنسيق (أو أي تنسيق جاهز آخر نريده). شغّل البرنامج باستخدام go run: $ go run main.go سيكون الخرج على النحو التالي: The time is 2021-08-15 14:30:45.0000001 -0500 CDT 2021-08-15T14:30:45.0000001-05:00 يُعد تنسيق RFC 3339 جيدًا للاستخدام إذا كنا بحاجة إلى حفظ قيمة زمنية مثل سلسلة نصية string في مكان ما، إذ يمكن قراءتها بسهولة من قبل العديد من لغات البرمجة والتطبيقات الأخرى، كما أنها مختصرة قدر الإمكان. عدّلنا خلال هذا القسم البرنامج بحيث استخدمنا التابع Format لطباعة التاريخ والوقت، إذ يمنحنا هذا النسق المرن إمكانية الحصول على الخرج الأخير. أخيرًا، تعرّفنا واستخدمنا واحدًا من السلاسل النصية المعرفة مسبقًا لطباعة الوقت والتاريخ اعتمادًا على تنسيقات جاهزة ومدعومة يمكن استخدامها مباشرةً دون تكلّف عناء كتابة تصميمات تنسيق يدويًا. سنعدّل في القسم التالي برنامجنا لتحويل قيمة السلسلة string إلى النوع time.Time لنتمكن من معالجتها من خلال تحليلها Parsing. يمكنك الاطلاع على مقال تحليل التاريخ والوقت في dot NET على أكاديمية حسوب لمزيدٍ من المعلومات حول مفهوم التحليل. تحويل السلاسل النصية إلى قيم زمنية عبر تحليلها قد نصادف في العديد من التطبيقات تواريخ ممثلة بسلاسل نصية من النوع string، وقد نرغب بإجراء بعض التعديلات أو بعض العمليات عليها؛ فمثلًا قد تحتاج إلى استخراج جزء التاريخ من القيمة، أو جزء الوقت، أو كامل القيمة للتعامل معها ..إلخ، إضافةً إلى إمكانية استخدام التابع Format لإنشاء قيم string من قيم النوع time.Time. تتيح لغة جو أيضًا إمكانية التحويل بالعكس، أي من سلسلة إلى time.Time من خلال الدالة time.Parse، إذ تعمل هذه الدالة بآلية مشابهة للتابع Format، بحيث تأخذ نسق التاريخ والوقت المتوقع إضافةً إلى قيمة السلسلة مثل معاملات. لنفتح ملف "main.go" ونحدّثه لاستخدام دالة time.Parse لتحويل timeString إلى متغير time.Time: ... func main() { timeString := "2021-08-15 02:30:45" theTime, err := time.Parse("2006-01-02 03:04:05", timeString) if err != nil { fmt.Println("Could not parse time:", err) } fmt.Println("The time is", theTime) fmt.Println(theTime.Format(time.RFC3339Nano)) } على عكس التابع Format، تُعيد الدالة time.Parse قيمة خطأ محتملة في حالة عدم تطابق قيمة السلسلة المُمرّرة مع التنسيق المُمرّر. إذا نظرنا إلى النسق المستخدم، سنرى أن النسق المُعطى لدالة time.Parse يستخدم 1 للشهر 2 لليوم من الشهر ..إلخ، وهذا هو نفس النسق المُستخدم في تابع Format. شغّل البرنامج باستخدام go run: $ go run main.go سيكون الخرج كما يلي: The time is 2021-08-15 02:30:45 +0000 UTC 2021-08-15T02:30:45Z هناك بعض الأشياء التي يجب ملاحظتها في هذا الخرج، أولها هو أن المنطقة الزمنية الناتجة عن تحليل المتغير timeString هي المنطقة الزمنية الافتراضية، وهي إزاحة 0+، والتي تُعرف بالتوقيت العالمي المُنسّق Coordinated Universal Time -أو اختصارًا UTC أو توقيت غرينتش، فنظرًا لعدم احتواء القيمة الزمنية أو التصميم على المنطقة الزمنية، لا تعرف الدالة time.Parse المنطقة الزمنية التي تريد ربطها بها، وبالتالي تعدّه غرينتش. إذا كنت بحاجة لتحديد المنطقة الزمنية، يمكنك استخدام الدالة time.ParseInLocation لذلك. يمكن ملاحظة استخدام نسق time.RFC3339Nano، لكن الخرج لا يتضمن قيم بالنانو ثانية، وسبب ذلك هو أن القيم التي تحللها دالة time.Parse ليست نانو ثانية، وبالتالي تُضبط القيمة لتكون 0 افتراضيًا، وعندما تكون النانو ثانية 0، لن يتضمّن استخدام التنسيق time.RFC3339Nano قيمًا بالنانو ثانية في الخرج. يمكن للتابع time.Parse أيضًا استخدام أيًّا من تنسيقات الوقت المعرفة مسبقًا والمتوفرة في حزمة الوقت time عند تحليل قيمة سلسلة. لنفتح ملف "main.go" ونعدّل قيمة timeString، يحبث نحل مشكلة عدم ظهور قيم النانو ثانية عند استخدام تنسيق time.RFC3339Nano ولنحدّث معاملات time.Parse بطريقة توافق التعديلات الجديدة: ... func main() { timeString := "2021-08-15T14:30:45.0000001-05:00" theTime, err := time.Parse(time.RFC3339Nano, timeString) if err != nil { fmt.Println("Could not parse time:", err) } fmt.Println("The time is", theTime) fmt.Println(theTime.Format(time.RFC3339Nano)) } شغّل البرنامج باستخدام go run: $ go run main.go سيكون الخرج على النحو التالي: The time is 2021-08-15 14:30:45.0000001 -0500 CDT 2021-08-15T14:30:45.0000001-05:00 يُظهر خرج Format هذه المرة بأن time.Parse كانت قادرة على تحليل كلٍ من المنطقة الزمنية وقيم النانو ثانية من متغير timeString. تعلمنا في هذا القسم كيفية استخدام دالة time.Parse لتحليل سلسلة string تُمثّل تاريخًا معينًا وفقًا لتصميم معين، وتعلمنا كيفية تحديد المنطقة الزمنية للسلسلة المُحللة. سنتعرّف في القسم التالي على كيفية التعامل مع المناطق الزمنية بطريقة أوسع وكيفية التبديل بين المناطق الزمنية المختلفة من خلال الميزات التي يوفرها النوع time.Time. التعامل مع المناطق الزمنية يُشاع في التطبيقات التي يستخدمها مُستخدمين من أنحاء مختلفة من العالم -تخزّين التواريخ والأوقات باستخدام توقيت غرينيتش UTC، ثم التحويل إلى التوقيت المحلي للمستخدم عند الحاجة. يسمح ذلك بتخزين البيانات بتنسيق موحّد ويجعل الحسابات بينها أسهل، نظرًا لأن التحويل مطلوب فقط عند عرض التاريخ والوقت للمستخدم. أنشأنا خلال المقال برنامجًا يعمل وفقًا للمنطقة الزمنية المحلية المتواجدين بها. لحفظ قيم البيانات من النوع time.Time على أنها قيم UTC، نحتاج أولًا إلى تحويلها إلى قيم UTC باستخدام التابع UTC الذي يُعيد القيمة الزمنية الموافقة لنظام UTC من متغير التاريخ الذي يستدعيها. ملاحظة: يجري التحويل هنا من المنطقة المحلية التي يوجد بها الحاسب الذي نستخدمه إلى UTC، وبالتالي إذا كان حاسبنا موجود ضمن منطقة UTC لن نرى فرقًا. لنفتح ملف "main.go" ونُطبق هذا الكلام: ... func main() { theTime := time.Date(2021, 8, 15, 14, 30, 45, 100, time.Local) fmt.Println("The time is", theTime) fmt.Println(theTime.Format(time.RFC3339Nano)) utcTime := theTime.UTC() fmt.Println("The UTC time is", utcTime) fmt.Println(utcTime.Format(time.RFC3339Nano)) } هذه المرة يُنشئ البرنامج متغير theTime على أنه فيمة من النوع time.Time وفقًا لمنطقتنا الزمنية، ثم يطبعه بتنسيقين مختلفين، ثم يستخدم تابع UTC للتحويل من المنطقة الزمنية المحلية الحالية إلى المنطقة الزمنية UTC. شغّل البرنامج باستخدام go run: $ go run main.go سيكون الخرج على النحو التالي: The time is 2021-08-15 14:30:45.0000001 -0500 CDT 2021-08-15T14:30:45.0000001-05:00 The UTC time is 2021-08-15 19:30:45.0000001 +0000 UTC 2021-08-15T19:30:45.0000001Z سيختلف الخرج اعتمادًا على المنطقة الزمنية المحلية، ولكن سنرى في الخرج السابق أن المنطقة الزمنية في المرة الأولى كانت بتوقيت CDT (التوقيت الصيفي المركزي لأمريكا الشمالية)، وهي "5-" ساعات من UTC. بعد استدعاء تابع UTC وطباعة الوقت وفق نظام UTC، سنرى أن الوقت تغير من 14 إلى 19، لأن تحويل الوقت إلى UTC أضاف خمس ساعات. من الممكن أيضًا تحويل UTC إلى التوقيت المحلي باستخدام التابع Local على متغير النوع time.Time بنفس الأسلوب. افتح ملف "main.go" مرةً أخرى، وضِف استدعاءً للتابع Local على المتغير utcTime لتحويله مرةً أخرى إلى المنطقة الزمنية المحلية: ... func main() { theTime := time.Date(2021, 8, 15, 14, 30, 45, 100, time.Local) fmt.Println("The time is", theTime) fmt.Println(theTime.Format(time.RFC3339Nano)) utcTime := theTime.UTC() fmt.Println("The UTC time is", utcTime) fmt.Println(utcTime.Format(time.RFC3339Nano)) localTime := utcTime.Local() fmt.Println("The Local time is", localTime) fmt.Println(localTime.Format(time.RFC3339Nano)) } شغّل البرنامج باستخدام go run: $ go run main.go ليكون الخرج: The time is 2021-08-15 14:30:45.0000001 -0500 CDT 2021-08-15T14:30:45.0000001-05:00 The UTC time is 2021-08-15 19:30:45.0000001 +0000 UTC 2021-08-15T19:30:45.0000001Z The Local time is 2021-08-15 14:30:45.0000001 -0500 CDT 2021-08-15T14:30:45.0000001-05:00 نلاحظ أنه جرى التحويل من UTC إلى المنطقة الزمنية المحلية، وهذا يعني طرح خمس ساعات من UTC، وتغيير الساعة من 19 إلى 14. عدّلنا خلال هذا القسم البرنامج لتحويل البيانات الزمنية من منطقة زمنية محلية إلى توقيت غرينتش UTC باستخدام التابع UTC، كما أجرينا تحويلًا معاكسًا باستخدام التابع Local. تتمثل إحدى الميزات الإضافية التي تقدمها حزمة time والتي يمكن أن تكون مفيدة في تطبيقاتك -في تحديد ما إذا كان وقت معين قبل أو بعد وقت آخر. مقارنة الأوقات الزمنية قد تكون مقارنة تاريخين مع بعضهما صعبة أحيانًا، بسبب المتغيرات التي يجب أخذها بالحسبان عند المقارنة، مثل الحاجة إلى الانتباه إلى المناطق الزمنية أو حقيقة أن الأشهر لها عدد مختلف من الأيام عن بعضها. توفر الحزمة الزمنية time تابعين لتسهيل ذلك، هما: Before و After اللذان يمكن تطبيقهما على متغيرات النوع time.Time. تقبل هذه التوابع قيمةً زمنيةً واحدة وتعيدان إما true أو false اعتمادًا على ما إذا كان الوقت المُمثّل بالمتغير الذي يستدعيهما قبل أو بعد الوقت المقدم. لنفتح ملف "main.go" ونرى كيف تجري الأمور: ... func main() { firstTime := time.Date(2021, 8, 15, 14, 30, 45, 100, time.UTC) fmt.Println("The first time is", firstTime) secondTime := time.Date(2021, 12, 25, 16, 40, 55, 200, time.UTC) fmt.Println("The second time is", secondTime) fmt.Println("First time before second?", firstTime.Before(secondTime)) fmt.Println("First time after second?", firstTime.After(secondTime)) fmt.Println("Second time before first?", secondTime.Before(firstTime)) fmt.Println("Second time after first?", secondTime.After(firstTime)) } شغّل البرنامج باستخدام go run: $ go run main.go سيكون الخرج على النحو التالي: The first time is 2021-08-15 14:30:45.0000001 +0000 UTC The second time is 2021-12-25 16:40:55.0000002 +0000 UTC First time before second? true First time after second? false Second time before first? false Second time after first? true بما أننا نستخدم في الشيفرة أعلاه نظام الوقت UTC، فيجب أن يكون الخرج متشابهًا بغض النظر عن المنطقة الزمنية. نلاحظ أنه عند استخدام تابع Before مع المتغير firstTime وتمرير الوقت secondTime الذي نريد المقارنة به، ستكون النتيجة true أي أن ‎2021-08-15 قبل ‎2021-12-25. عند استخدام After مع المتغير firstTime وتمرير secondTime، تكون النتيجة false لأن ‎2021-08-15 ليس بعد 2021-12-25. يؤدي تغيير ترتيب استدعاء التوابع على secondTime إلى إظهار نتائج معاكسة. هناك طريقة أخرى لمقارنة القيم الزمنية في حزمة الوقت وهي تابع Sub، الذي يطرح تاريخًا من تاريخ آخر ويُعيد قيمةً من نوع جديد هو time.Duration. على عكس قيم النوع time.Time التي تمثل نقاط زمنية مطلقة، تمثل قيمة time.Duration فرقًا في الوقت. مثلًا، قد تعني عبارة "في ساعة واحدة" مدة duration لأنها تعني شيئًا مختلفًا بناءً على الوقت الحالي من اليوم، لكنها تمثّل "عند الظهر" وقتًا محددًا ومطلقًا. تستخدم لغة جو النوع time.Duration في بعض الحالات، مثل الوقت الذي نريد فيه تحديد المدة التي يجب أن تنتظرها الدالة قبل إعادة خطأ أو كما هو الحال هنا، إذ نحتاج إلى معرفة مقدار الزمن بين وقت وآخر. لنفتح الآن ملف "main.go" ونستخدم التابع Sub على المتغير firstTime و secondTime ونطبع النتائج: ... func main() { firstTime := time.Date(2021, 8, 15, 14, 30, 45, 100, time.UTC) fmt.Println("The first time is", firstTime) secondTime := time.Date(2021, 12, 25, 16, 40, 55, 200, time.UTC) fmt.Println("The second time is", secondTime) fmt.Println("Duration between first and second time is", firstTime.Sub(secondTime)) fmt.Println("Duration between second and first time is", secondTime.Sub(firstTime)) شغّل البرنامج باستخدام go run: $ go run main.go سيكون الخرج على النحو التالي: The first time is 2021-08-15 14:30:45.0000001 +0000 UTC The second time is 2021-12-25 16:40:55.0000002 +0000 UTC Duration between first and second time is -3170h10m10.0000001s Duration between second and first time is 3170h10m10.0000001s يوضح الناتج أعلاه أن هناك 3170 ساعة و 10 دقائق و 10 ثوان و 100 نانو ثانية بين التاريخين، وهناك بعض الأشياء التي يجب ملاحظتها في الخرج، أولها أن المدة بين المرة الأولى first time والثانية second time قيمة سالبة، وهذا يشير إلى أن الوقت الثاني بعد الأول، وسيكون الأمر مماثلًا إذا طرحنا 5 من 0 وحصلنا على 5-. نلاحظ أيضًا أن أكبر وحدة قياس للمدة هي ساعة، لذلك فهي لا تُقسم إلى أيام أو شهور. بما أن عدد الأيام في الشهر غير متسق وقد يكون لكلمة "اليوم" معنى مختلف عند التبديل للتوقيت الصيفي، فإن قياس الساعة هو أدق مقياس، إذ أنه لا يتقلب. عدّلنا البرنامج خلال هذا القسم مرتين للمقارنة بين الأوقات الزمنية باستخدام ثلاث توابع مختلفة؛ إذ استخدمنا أولًا التابعين Before و After لتحديد ما إذا كان الوقت قبل أو بعد وقت آخر؛ ثم استخدمنا Sub لمعرفة المدة بين وقتين. ليس الحصول على المدة بين وقتين الطريقة الوحيدة التي تستخدم بها الحزمة الزمنية الدالة time.Duration، إذ يمكننا أيضًا استخدامها لإضافة وقت أو إزالته من قيمة من النوع time.Time. إضافة وطرح الأوقات الزمنية إحدى العمليات الشائعة عند كتابة التطبيقات التي تستخدم التواريخ والأوقات هي تحديد وقت الماضي أو المستقبل بناءً على وقت آخر مرجعي، فمثلًا يمكن استخدام هذا الوقت المرجعي لتحديد موعد تجديد الاشتراك بخدمة، أو ما إذا كان قد مر قدرٌ معينٌ من الوقت منذ آخر مرة جرى فيها التحقق من أمر معين. توفر حزمة الوقت time طريقةً لمعالجة الأمر، وذلك من خلال تحديد المدد الزمنية الخاصة بنا باستخدام متغيرات من النوع time.Duration. إنشاء قيمة من النوع time.Duration بسيط جدًا والعمليات الرياضية عليه متاحة كما لو أنه متغير عددي عادي؛ فمثلًا لإنشاء مدة زمنية time.Duration تُمثّل ساعة أو ساعتين أو ثلاثة ..إلخ، يمكننا استخدام time.Hour مضروبةً بعدد الساعات التي نريدها: oneHour := 1 * time.Hour twoHours := 2 * time.Hour tenHours := 10 * time.Hour نستخدم time.Minute و time.Second في حال الدقائق والثواني: tenMinutes := 10 * time.Minute fiveSeconds := 5 * time.Second يمكن أيضًا إضافة مدة زمنية إلى مدة أخرى للحصول على مجموعهما. لنفتح ملف main.go ونطبق ذلك: ... func main() { toAdd := 1 * time.Hour fmt.Println("1:", toAdd) toAdd += 1 * time.Minute fmt.Println("2:", toAdd) toAdd += 1 * time.Second fmt.Println("3:", toAdd) } شغّل البرنامج باستخدام go run: $ go run main.go سيكون الخرج على النحو التالي: 1: 1h0m0s 2: 1h1m0s 3: 1h1m1s نلاحظ من الخرج أن المدة الأولى المطبوعة هي ساعة واحدة، وهذا يتوافق مع ‎1 * time.Hour في الشيفرة. أضفنا بعد ذلك ‎1 * time.Minute إلى القيمة toAdd أي ساعة ودقيقة واحدة. أخيرًا أضفنا ‎1 * time.Second إلى قيمة toAdd، لينتج لدينا ساعة واحدة ودقيقة واحدة وثانية في المدة الزمنية الممثلة بالنوع time.Duration. يمكن أيضًا دمج عمليات إضافة المُدد معًا في عبارة واحدة، أو طرح مدة من أخرى: oneHourOneMinute := 1 * time.Hour + 1 * time.Minute tenMinutes := 1 * time.Hour - 50 * time.Minute لنفتح ملف "main.go" ونعدله بحيث نستخدم توليفة من هذه العمليات لطرح دقيقة واحدة وثانية واحدة من toAdd: ... func main() { ... toAdd += 1 * time.Second fmt.Println("3:", toAdd) toAdd -= 1*time.Minute + 1*time.Second fmt.Println("4:", toAdd) } شغّل البرنامج باستخدام go run: $ go run main.go سيعطي الخرج التالي: 1: 1h0m0s 2: 1h1m0s 3: 1h1m1s 4: 1h0m0s يظهر السطر الرابع من الخرج، والذي يُمثّل ناتج طرح المجموع ‎1*time.Minute + 1*time.Second من toAdd أن العملية ناجحة، إذ حصلنا على قيمة ساعة واحدة (طُرحت الثانية والدقيقة). يتيح لنا استخدام هذه المُدد المقترنة بالتابع Add للنوع time.Time حساب المدد الزمنية بين نقطتين زمنيتين إحداهما نقطة مرجعية، مثل حساب المدة منذ أول يوم اشتراك في خدمة معينة حتى اللحظة. لرؤية مثال آخر نفتح ملف "main.go" ونجعل قيمة toAdd تساوي 24 ساعة أي ‎24 * time.Hour، ثم نستخدم التابع Add على قيمة متغير من النوع time.Time لمعرفة الوقت الذي سيكون بعد 24 ساعة من تلك النقطة: ... func main() { theTime := time.Date(2021, 8, 15, 14, 30, 45, 100, time.UTC) fmt.Println("The time is", theTime) toAdd := 24 * time.Hour fmt.Println("Adding", toAdd) newTime := theTime.Add(toAdd) fmt.Println("The new time is", newTime) } شغّل البرنامج باستخدام go run: $ go run main.go سيكون الخرج على النحو التالي: The time is 2021-08-15 14:30:45.0000001 +0000 UTC Adding 24h0m0s The new time is 2021-08-16 14:30:45.0000001 +0000 UTC نلاحظ أن إضافة 24 ساعة إلى التاريخ ‎2021-08-15 سينتج عنه التاريخ الجديد ‎2021-08-16. يمكننا أيضًا استخدام التابع Add لطرح الوقت، إذ سنستخدم قيمة سالبة ببساطة، فيصبح الأمر كما لو أننا نستخدم التابع Sub. لنفتح ملف "main.go" ونطبق هذا الكلام، fحيث سنطرح هذه المرة 24 ساعة، أي يجب أن نستخدم قيمة "24-". ... func main() { theTime := time.Date(2021, 8, 15, 14, 30, 45, 100, time.UTC) fmt.Println("The time is", theTime) toAdd := -24 * time.Hour fmt.Println("Adding", toAdd) newTime := theTime.Add(toAdd) fmt.Println("The new time is", newTime) } ` شغّل البرنامج باستخدام go run: $ go run main.go سيكون الخرج كما يلي: The time is 2021-08-15 14:30:45.0000001 +0000 UTC Adding -24h0m0s The new time is 2021-08-14 14:30:45.0000001 +0000 UTC نلاحظ من الخرج أنه قد طُرح 24 ساعة من الوقت الأصلي. استخدمنا خلال هذا القسم التوابع time.Hour و time.Minute و time.Second لإنشاء قيم من النوع time.Duration. استخدمنا أيضًا قيم النوع time.Duration مع التابع Add للحصول على قيمة جديدة لمتغير من النوع time.Time. سيكون لدينا -من خلال التوابع time.Now و Add و Before و After- القدرة الكافية على التعامل مع التاريخ والوقت في التطبيقات. الخاتمة استخدمنا خلال هذا المقال الدالة time.Now للحصول على التاريخ والوقت المحلي الحالي على شكل قيم من النوع time.Time، ثم استخدمنا التوابع Year و Month و Hour ..إلخ، للحصول على معلومات محددة من التاريخ والوقت. استخدمنا بعد ذلك التابع Format لطباعة الوقت بالطريقة التي نريدها وفقًا لتنسيق مخصص نقدمه أو وفقًا لتنسيق جاهز مُعرّف مسبقًا. استخدمنا الدالة time.Parse لتحويل قيمة سلسلة نصية string تمثّل بيانات زمنية إلى قيمة من النوع time.Time لنتمكن من التعامل معها. تعلمنا أيضًا كيفية التبديل من المنطقة الزمنية المحلية إلى منطقة غرينتش UTC باستخدام التابع Local والعكس. تعلمنا استخدام توابع Sub و Add لإجراء عمليات جمع وطرح على البيانات الزمنية، لإيجاد الفرق بين مدتين زمنيتين أو لإضافة مدة زمنية إلى بيانات زمنية محددة. ترجمة -وبتصرف- للمقال How To Use Dates and Times in Go لصاحبه Kristin Davidson. اقرأ أيضًا المقال السابق كيفية إرفاق معلومات إضافية عن الأخطاء في لغة Go كتابة برنامجك الأول في جو Go بناء تطبيقات لغة Go على أنظمة التشغيل والمعماريات المختلفة
  19. لقد أوصلنا التطور السريع للتكنولوجيا إلى منعطف محوري، حيث أصبح الخيال العلمي حقيقة ملموسة. الذكاء الصناعي Artificial Intellegence، المجال الذي كان يقتصر في السابق على عالم الخيال، أصبح الآن قوة كبيرة تدعم الابتكار في مختلف الصناعات. بدءًا من السيارات ذاتية القيادة التي تتنقل في الشوارع المزدحمة إلى المساعدين الافتراضيين الذين يفهمون ويستجيبون لأوامرنا مثل سيري Siri وأليكسا Alexa، أصبح الذكاء الصناعي جزءًا لا يتجزأ من الحياة الحديثة. يكمن في قلب هذا التحوّل الفن المعقد لبرمجة الذكاء الصناعي، وهي عملية تتضمن صياغة خوارزميات ونماذج تُعالج وتحلّل كميات هائلة من البيانات، بغية إنشاء آلات ذكية يمكنها محاكاة الوظائف الإدراكية البشرية، مثل التعلم والاستدلال وحل المشكلات واتخاذ القرار. تتضمن برمجة الذكاء الصناعي تطوير البرامج التي تسمح للآلات بإظهار السلوك الذكي وحل المشكلات والتعلم من البيانات كما ذكرنا. إنه مجال متعدد التخصصات يجمع بين علوم الحاسب والرياضيات والعلوم المعرفية والهندسة. الهدف هو تزويد الآلات بالقدرة على تحليل البيانات المعقدة والتعلم من الأنماط والتكيّف مع الظروف المتغيرة، وحتى الانخراط في حل المشكلات التي تتطلّب حثًا إبداعيًّا. تتحدّث هذه المقالة عن برمجة الذكاء الصناعي وتستكشف مفاهيمها الأساسية وتقنياتها المتطورة وأساليب النشر والاعتبارات الأخلاقية والإمكانيات غير العادية التي تحملها لتشكيل المستقبل وأكثر. ما الفرق بين الذكاء الاصطناعي والبرمجة؟ الذكاء الصناعي والبرمجة هما مفهومان متمايزان ولكن مترابطان في مجال علوم الحاسب. تشير البرمجة إلى عملية إنشاء التعليمات البرمجية أو الشيفرة البرمجية التي تتبعها أجهزة الحاسب لأداء مهام محددة ومدروسة سابقًا ولا يمكن الخروج عنها أي لن يستجيب البرنامج إن حاولت استعماله في شيء ليس مبرمجًا عليه. يتضمن ذلك كتابة الخوارزميات والأوامر بلغات البرمجة المختلفة لتحقيق النتائج المرجوة. من الناحية الأخرى، يتجاوز الذكاء الصناعي البرمجة التقليدية من خلال تمكين الآلات من محاكاة الوظائف المعرفية بطريقة البشرية، مثل التعلم والاستدلال وحل المشكلات واتخاذ القرار. يمكن لأنظمة الذكاء الصناعي التكيف وتحسين أدائها بناءً على البيانات والخبرات، مما يسمح لها بالتعامل مع المواقف المعقدة وغير المؤكدة التي قد لا تكون مبرمجة بشكل واضح. من هنا نجد أن برمجة الذكاء الاصطناعي تختلف عن البرمجة التقليدية التي اعتدنا عليها. صحيح أن البرمجة تشكل الأساس لبناء أنظمة الذكاء الصناعي، إلا أن الذكاء الصناعي يتضمن تطوير خوارزميات ونماذج يمكنها التعلم من البيانات وتحسين أدائها بمرور الوقت، مما يمثل خروجًا كبيرًا عن نماذج البرمجة التقليدية. سأضرب مثالًا حديثًا يوضح الفرق بين الذكاء الاصطناعي والبرمجة، نشر إلون ماسك بتاريخ 26 أغسطس هذا العام فيديو على حسابه على تويتر وهو جالس في سيارة تسلا ذاتية القيادة متوجهًا من مكان إلى آخر دون أن يتدخل وقد علق قائلًا أثناء مرور السيارة بجانب دراجة أننا لم نكتب سطرًا من كود ولم نبرمج السيارة على التصرف في حال المرور بجانب شخص يقود دراجة هوائية، وقد مرت السيارة مرورًا سلسلًا بجانب الدراجة، ثم عند الوصول إلى تقاطع في إشارات مرورية علق قائلًا، نحن لم نبرمج السيارة على قراءة الإشارات المرورية ولم نكتب سطرًا برمجيًا يوضح كل اللافتات والإشارات المرورية بل الأمر متروك للسيارة باستنتاج معنى الإشارة وتطبيق ما يلزم، حيث مرت السيارة بجانب إشارة تطلب تخفيف السرعة فاستجابت لذلك، ويمكنك مشاهدة الفيديو كاملًا وهو بطول 45 دقيقة في سيارة تسلا ذاتية القيادة التي تقود مستعينة بالذكاء الاصطناعي. كان هذا أوضح مثال عن الفرق بين برمجة الذكاء الاصطناعي والبرمجة التقليدية. باختصار، تتضمن البرمجة كتابة تعليمات واضحة ليتبعها الحاسب، في حين تتضمن برمجة الذكاء الاصطناعي إنشاء أنظمة يمكنها التعلم من البيانات لإجراء تنبؤات أو قرارات دون أن تتم برمجتها بشكل صريح لكل موقف. مبرمج الذكاء الاصطناعي خلف كواليس تلك التطورات المذهلة التي يشهدها الذكاء الاصطناعي، يوجد محترفون ماهرون ينجزون هذا التطور، هم مبرمجي الذكاء الاصطناعي. يلعب هؤلاء الأفراد دورًا حاسمًا في تطوير الخوارزميات والنماذج والأنظمة التي تدعم تطبيقات الذكاء الاصطناعي. دور مبرمج الذكاء الاصطناعي مبرمج الذكاء الصناعي هو مطور برامج متخصص يركز على تصميم أنظمة الذكاء الصناعي وتنفيذها وتحسينها. هدفه الأساسي هو إنشاء خوارزميات ونماذج تمكن الآلات من أداء المهام التي تتطلب عادةً ذكاءًا بشريًّا. تتراوح هذه المهام من معالجة اللغة الطبيعية والتعرف على الصور إلى أنظمة التوصية والمركبات المستقلة. يقوم مبرمجي الذكاء الصناعي بسد الفجوة بين مفاهيم الذكاء الاصطناعي النظرية والتطبيقات العملية في العالم الحقيقي. تتضمن المهام الأساسية: تطوير الخوارزميات: يقوم مبرمجي الذكاء الاصطناعي بتصميم وتطوير خوارزميات تُمكّن الآلات من معالجة البيانات وتفسيرها وإنتاجها وإجراء التنبؤات وحتى توليد الأفكار. إنشاء النماذج: يقومون ببناء وتدريب نماذج التعلم الآلي والتعلم العميق باستخدام مجموعات بيانات ضخمة. تتعلم هذه النماذج الأنماط والعلاقات من البيانات لاتخاذ قرارات مستنيرة. المعالجة المسبقة للبيانات: يقوم مبرمجي الذكاء الاصطناعي بتنظيف البيانات ومعالجتها مسبقًا وتحويلها لجعلها مناسبة لتدريب نماذج الذكاء الاصطناعي. يتضمن ذلك مهام مثل تسوية البيانات واستخراج الميزات ومعالجة القيم المفقودة ..إلخ. تحسين الأداء: بمجرد تطوير النموذج، يقوم مبرمجو الذكاء الاصطناعي بتحسين أدائه من خلال ضبط المعلمات الخاصة بتدريب النموذج بدقة وتقليل المتطلبات الحسابية وتعزيز الكفاءة العامة. التكامل: يقوم مبرمجي الذكاء الاصطناعي بدمج نماذج وأنظمة الذكاء الاصطناعي في تطبيقات أكبر، مما يجعلها تتفاعل بسلاسة مع مكونات البرامج الأخرى. المهارات المطلوبة: إتقان البرمجة: يعد إتقان إحدى لغات البرمجة على الأقل مثل لغة بايثون أو لغة R أو جافا أمرًا أساسيًا. تحتاج عملية برمجة الذكاء الاصطناعي إلى كتابة التعليمات البرمجية لتنفيذ الخوارزميات ومعالجة البيانات وتطوير نماذج الذكاء الصناعي. التعلم الآلي: يعد الفهم القوي لمفاهيم التعلم الآلي أو تعلم الآلة، بما في ذلك التعلم الخاضع للإشراف وغير الخاضع للإشراف والانحدار والتصنيف والتجميع والشبكات العصبية، أمرًا ضروريًا. الرياضيات والإحصاء: تتضمن برمجة الذكاء الاصطناعي التعامل مع مفاهيم رياضية، مثل الجبر الخطي وحساب التفاضل والتكامل والاحتمالات. المعرفة الإحصائية السليمة أمر بالغ الأهمية لتقييم النماذج واختبار الفرضيات. أطر التعلم العميق: يعد الإلمام بمكتبات التعلم العميق مثل تنسرفلو TensorFlow وباي تورش PyTorch وكيراس Keras أمرًا ضروريًا لبناء الشبكات العصبية وتدريبها. التعامل مع البيانات: تعد المهارة في معالجة البيانات والمعالجة المسبقة وأدوات التصور ضرورية لإعداد البيانات للتدريب على نماذج الذكاء الصناعي. حل المشكلات: يجب أن يكون مبرمجي الذكاء الاصطناعي ماهرين في تقسيم المشكلات المعقدة إلى مكونات يمكن التحكم فيها واستنباط حلول فعالة. المعرفة بالمجال: قد يحتاج مبرمجي الذكاء الاصطناعي إلى معرفة خاصة بالمجال الذي يتم بناء النموذج للعمل عليه. على سبيل المثال، يجب على مبرمجي تطبيقات الذكاء الاصطناعي الطبية فهم المصطلحات والممارسات الطبية. الوعي بالأخلاق والتحيز: مع تأثير أنظمة الذكاء الاصطناعي على القرارات الحاسمة، يعد فهم الاعتبارات الأخلاقية وتخفيف التحيز أمرًا بالغ الأهمية. مبرمجو الذكاء الاصطناعي هم مهندسو المستقبل القائم على الذكاء الاصطناعي. تعتبر خبرتهم في الخوارزميات والتعلم الآلي ومعالجة البيانات مفيدة في إنشاء أنظمة يمكنها تحليل كميات هائلة من البيانات واتخاذ قرارات مستنيرة والتعلم المستمر من التجربة. ومع استمرار الذكاء الاصطناعي في إحداث ثورة في الصناعات وإعادة تعريف الإمكانيات، يظل دور مبرمجي الذكاء الاصطناعي محوريًا في تشكيل هذا المشهد التحويلي. سواء كان الأمر يتعلق بتعزيز تجارب العملاء أو إحداث ثورة في تشخيص الرعاية الصحية أو تمكين السيارات ذاتية القيادة، فإن مبرمجي الذكاء الاصطناعي هم في الطليعة، حيث يحولون الأفكار المتطورة إلى واقع عملي. المفاهيم الأساسية للذكاء الاصطناعي يشمل الذكاء الاصطناعي مجموعة واسعة من المفاهيم والتقنيات التي تهدف إلى إنشاء أنظمة قادرة على أداء المهام المختلفة. فيما يلي بعض المفاهيم الأساسية للذكاء الاصطناعي: التعلم الآلي Machine Learning التعلم الآلي هو مجموعة فرعية من الذكاء الاصطناعي الذي يركز على تطوير الخوارزميات والنماذج التي تمكن أجهزة الحاسب من التعلم والتنبؤ أو اتخاذ القرارات بناءً على البيانات. ويمكن تصنيفها إلى: التعلم الخاضع للإشراف Supervised Learning: تتعلم الخوارزميات من بيانات التدريب المصنفة لإجراء تنبؤات أو تصنيفات. وتشمل الأمثلة مهام الانحدار والتصنيف. التعلم غير الخاضع للإشراف Unsupervised Learning: تتعلم الخوارزميات من البيانات غير المسماة لاكتشاف الأنماط والعلاقات. ومن الأمثلة على ذلك التجميع وتقليل الأبعاد. التعلم المعزز Reinforcement Learning: يتعلم الوكلاء Agents تنفيذ الإجراءات ضمن بيئة محددة لتحقيق أقصى قدر من المكافآت. شائع في مهام مثل اللعب والروبوتات. الشبكات العصبية Neural Networks الشبكات العصبية هي نماذج حسابية مستوحاة من بنية الدماغ البشري. وهي تتكون من عقد مترابطة (خلايا عصبية) منظمة في طبقات. كل اتصال له وزن يمكن تعديله أثناء التدريب لتحسين أداء النموذج. التعلم العميق Deep Learning التعلم العميق هو مجال فرعي من التعلم الآلي يتضمن شبكات عصبية ذات طبقات متعددة (شبكات عصبية عميقة). لقد أتاح التعلم العميق تحقيق إنجازات كبيرة في مهام مثل التعرف على الصور والكلام نظرًا لقدرته على تعلم الأنماط المعقدة تلقائيًا من البيانات. معالجة اللغات الطبيعية Natural Language Processing تتضمن البرمجة اللغوية العصبية تمكين أجهزة الحاسب من فهم اللغة البشرية وتفسيرها وتوليدها. يتم استخدامه في مهام مثل تحليل المشاعر وترجمة اللغات وروبوتات الدردشة وإنشاء النصوص. الرؤية الحاسوبية Computer Vision تركز رؤية الحاسب على منح الآلات القدرة على تفسير المعلومات المرئية من البيئة المحيطة. يُستخدم في تحليل الصور والفيديو واكتشاف الأشياء والتعرف على الوجه والمركبات ذاتية القيادة. الأنظمة الخبيرة Expert Systems الأنظمة الخبيرة هي برامج الذكاء الاصطناعي التي تحاكي قدرات اتخاذ القرار للخبير البشري في مجال معين. ويستخدمون تقنيات تمثيل المعرفة والتفكير لحل المشكلات المعقدة. تمثيل المعرفة والاستدلال Knowledge Representation and Reasoning تحتاج أنظمة الذكاء الاصطناعي إلى تمثيل المعرفة بتنسيق منظم يمكن لأجهزة الحاسب فهمه. يتضمن الاستدلال استخدام القواعد المنطقية لإجراء الاستدلالات واستخلاص النتائج من المعرفة الممثلة. الحوسبة المعرفية Cognitive Computing تهدف الحوسبة المعرفية إلى إنشاء أنظمة يمكنها محاكاة عمليات التفكير البشري، مثل التفكير وحل المشكلات والتعلم. غالبًا ما يجمع بين تقنيات الذكاء الاصطناعي ومعالجة اللغة الطبيعية وتحليل البيانات. الإدراك الآلي Machine Perception يتضمن ذلك منح الآلات القدرة على إدراك العالم وفهمه من خلال المدخلات الحسية مثل الصور والصوت واللمس. يتضمن مهام مثل التعرف على الصور والتعرف على الكلام والتعرف على الإيماءات. توفر هذه المفاهيم فهمًا أساسيًا للذكاء الاصطناعي. ومع استمرار تطور هذا المجال، ستظهر تقنيات وأساليب جديدة، مما يساهم في تقدمه السريع. تجدر الإشارة إلى أنك كمبرمج للذكاء الصناعي لست مضطرًا لتعلّم كل هذه المفاهيم، وإنما يكفي فهم عام لها (فهم سطحي)، والتخصص في واحدة أو اثنتين منها فقط، لأن التخصص في كل هذه المفاهيم أو في كل مجالات الذكاء الصناعي هو أمر شبه مستحيل. خوارزميات الذكاء الاصطناعي خوارزميات الذكاء الاصطناعي عبارة عن مجموعات من التعليمات خطوة بخطوة أو الإجراءات الحسابية المصممة لحل مشكلات أو مهام محددة تتطلب عادةً ذكاءً بشريًا. تقع هذه الخوارزميات في قلب أنظمة الذكاء الاصطناعي، مما يمكّن الآلات من التعلم والتفكير واتخاذ القرارات.عمومًا، هذه الخوارزميات هي مجرد أفكار ونظريات تحتاج إلى تحويل إلى صيغة يمكن للآلات استغلالها، وهنا يتجلى دور برمجة الذكاء الاصطناعي في تحويل هذه الأفكار إلى شيفرات برمجية، بالتالي تصبح الآلات قادرة على تحليل البيانات بشكل ذكي، مما يتيح لها اكتساب المعرفة وتحديث أساليبها تلقائيًا. هناك مجموعة واسعة من الخوارزميات ذات الصلة بالذكاء الصناعي منها خوارزميات البحث والخوارزميات الجينية والشبكات العصبية وأشجار القرار وخوارزميات التجميع وخوارزميات معالجة اللغات الطبيعية وخوارزميات التعلم المعزز. يعتمد اختيار الخوارزمية على المشكلة التي تحاول حلها والبيانات المتوفرة لديك ومهمة الذكاء الاصطناعي المحددة التي تعمل عليها. لن نخوض في هذه الخوارزميات الآن، فقد كان لنا محطة عندها في مقالة خوارزميات الذكاء الاصطناعي. البدء في برمجة الذكاء الاصطناعي عند وجود أساس جيد للمفاهيم الأساسية للذكاء الاصطناعي، يكون قد حان الوقت للشروع في رحلتك إلى عالم برمجة الذكاء الصناعي. نوجّهك في هذا القسم إلى الخطوات الأولية لبدء برمجة تطبيقات الذكاء الصناعي. لغات برمجة الذكاء الاصطناعي تُستخدم العديد من لغات برمجة الذكاء الاصطناعي والمكتبات بشكل شائع لتطوير الذكاء الصناعي نذكر منها ما يلي: لغة بايثون Python لغة R لغة جافا Java لغة جوليا Julia 1. لغة بايثون Python لغة بايثون هي لغة شائعة لبرمجة الذكاء الصناعي نظرًا لبساطتها ونظامها الإيكولوجي الشامل لمكتبات الذكاء الصناعي مثل تنسرفلو TensorFlow وباي تورش PyTorch وسكايت ليرن scikit-Learn. لغة R تُستخدم لغة R على نطاق واسع في التحليل الإحصائي وتصوّر البيانات، مما يجعلها الخيار المفضل لمشاريع الذكاء الصناعي التي تتضمن تحليل البيانات. لغة جافا Java القدرات القوية للبرمجة غرضية التوجّه OOP في لغة جافا تجعلها مناسبة لبناء تطبيقات الذكاء الصناعي. لغة جوليا Julia تكتسب لغة جوليا قوة دفع لقدراتها عالية الأداء في الحوسبة العلمية والتعلم الآلي. سنتحدث عن استخدامات هذه اللغات -ولغات أخرى- في سياق الذكاء الصناعي بشيءٍ من التفصيل لاحقًا. تجدر الإشارة إلى أنه يمكنك الاطلاع على سلاسل مقالات كاملة عن مكتبة بايثون، ولغة جافا، ولغة R. استكشاف مكتبات وأطر عمل الذكاء الاصطناعي تصبح برمجة الذكاء الصناعي أكثر سهولة وكفاءة بمساعدة المكتبات والأطر المتخصصة. توفر هذه الأدوات دوال وتجريدات مختلفة لجعل تطوير الذكاء الصناعي أكثر كفاءة. فيما يلي بعض مكتبات وأطر عمل الذكاء الصناعي الشائعة: تنسرفلو TensorFlow تم تطوير إطار العمل تنسرفلو TensorFlow بواسطة شركة جوجل، وهي واحدة من مكتبات الذكاء الصناعي مفتوحة المصدر الأكثر استخدامًا. توفر نظامًا بيئيًا شاملاً للتعلم الآلي والتعلم العميق، بما في ذلك دعم الشبكات العصبية ومعالجة اللغة الطبيعية NLP والرؤية الحاسوبية. باي تورش PyTorch طوّر باي تورش بواسطة مختبر أبحاث الذكاء الصناعي FAIR التابع لشركة ميتا، وهو يتبع أسلوبًا ديناميكيًا في تنفيذ الشبكات العصبية، مما يجعله أكثر مرونة للبحث والتجريب. يستخدم على نطاق واسع في الأوساط الأكاديمية والصناعية لتطوير نماذج التعلم العميق. كيراس Keras في الأصل هي مكتبة مستقلة، لكن أصبحت كيراس الآن جزءًا من تنسرفلو باعتبارها واجهة برمجة تطبيقات عالية المستوى. يوفر واجهة سهلة الاستخدام لبناء الشبكات العصبية وتدريبها، مما يجعلها رائعة للمبتدئين. سكايت-ليرن Scikit-Learn هذه مكتبة شهيرة للتعلم الآلي، ولكن ليس لها أي علاقة بالتعلم العميق والشبكات العصبية. توفر هذه المكتبة عددًا كبيرًا من خوارزميات التعلم الآلي التقليدية. توفر مجموعة أدوات بسيطة وفعالة للمعالجة المسبقة للبيانات وهندسة الميزات وتقييم النموذج. توفر هذه المكتبات والأطر بيئة مناسبة ومريحة وآمنة وموثوقة، لتسهيل عملية بناء وتطوير مشاريع الذكاء الصناعي. نتحدث عن هذه المكتبات والأطر في المقالة القادمة بشكل أكبر أيضًا. أدوات تطوير وبرمجة تطبيقات الذكاء الاصطناعي قبل البدء في الخوض في عالم برمجة الذكاء الاصطناعي، من الضروري إنشاء بيئة تطوير قوية توفر الأدوات والموارد اللازمة لكتابة الشيفرات وإنشاء الاختبارات بسلاسة: اختر نظام التشغيل الخاص بك أولاً وقبل كل شيء، حدد نظام التشغيل الذي يناسب تفضيلاتك ومتطلباتك. يمكن إجراء تطوير الذكاء الصناعي على منصات مختلفة، إلا أن العديد من مكتبات وأدوات الذكاء الصناعي مدعومة جيدًا على الأنظمة المستندة إلى لينكس Linux مثل أبونتو Ubuntu. تجدر الإشارة أيضًا إلى أن نظامي التشغيل ويندوز Windows وماك macOS هما أيضًا خيارات يمكن الاعتماد عليها. يعتمد اختيارك إلى حد كبير على معرفتك بالنظام وراحتك، وممكن استعمال لينكس ضمن ويندوز عبر طريقتين إما عبر بيئة وهمية أو عبر نظام لينكس الداخلي WSL، أو يمكن تثبيت لينكس مع ويندوز. تثبيت بايثون Python بايثون هي لغة البرمجة المفضلة لبرمجة الذكاء الاصطناعي نظرًا لبساطتها وبيئتها الواسعة التي تتضمن العديد من المكتبات الداعمة للذكاء الصنعي. ابدأ بتثبيت بايثون على نظامك. يمكنك مراجعة مقال تثبيت لغة بايثون الشامل الذي يشرح تنزيل بايثون على كل أنظمة التشغيل. بعد التثبيت، ستتمكن من الوصول إلى مجموعة المكتبات والأدوات الثريّة في بايثون. اختر بيئة تطوير متكاملة IDE إن استخدام بيئة تطوير متكاملة يؤدي إلى تحسين تجربة البرمجة الخاصة بك من خلال توفير ميزات مثل إكمال التعليمات البرمجية وتصحيح الأخطاء وإدارة المشروع. تشمل الخيارات الشائعة PyCharm و Jupyter Notebook المخصصين لبايثون وكذلك Visual Studio Code الذي يُعتبر بيئة تطوير عامة. تُقدم بيئات التطوير المتكاملة مكونات إضافية وإضافات متخصصة لتطوير الذكاء الاصطناعي، مما يبسط سير عملك. تثبيت أطر ومكتبات الذكاء الصناعي لتسخير قوة الذكاء الاصطناعي، ستحتاج إلى تثبيت مكتبات وأطر تسهل مهام التعلم الآلي والتعلم العميق. تعد تنسرفلو TensorFlow وباي تورش PyTorch وكيراس Keras وسكايت ليرن scikit-Learn بعض المكتبات الأساسية التي يجب أخذها بعين الاعتبار. يمكنك تثبيت هذه المكتبات باستخدام مدير حزم بايثون pip، عن طريق تنفيذ أوامر بسيطة. إعداد البيئات الافتراضية يعد إنشاء بيئات افتراضية أفضل ممارسة لعزل مشاريعك وتبعياتها. تضمن البيئات الافتراضية أن المكتبات والحزم المثبتة لمشروع واحد لا تتداخل مع المشاريع والحزم الأخرى. استخدم أدوات مثل venv أو conda أو pipenv لإنشاء وإدارة بيئات افتراضية، وتحسين تنظيم مشروعك وتجنب تعارضات الإصدار. إعداد التحكم في الإصدار Version Control يعد التحكم في الإصدار أمرًا بالغ الأهمية لإدارة التغييرات في قاعدة التعليمات البرمجية الخاصة بك والتعاون مع الآخرين. جيت Git هو أكثر أنظمة التحكم في الإصدارات شيوعًا. أنشئ مستودع جيت لمشاريع الذكاء الاصطناعي الخاصة بك لتتبع التغييرات والتعاون مع أعضاء الفريق والاحتفاظ بسجل لقاعدة التعليمات البرمجية الخاصة بك. يمكنك الاطلاع على سلاسل مقالات كاملة للتعرّف على كيفية التعامل مع جيت من قسم Git التعليمي. استكشاف المنصات السحابية Cloud Platforms نظرًا لأن مشاريع الذكاء الاصطناعي أصبحت أكثر تعقيدًا، فقد تحتاج إلى قوة حسابية إضافية. توفر المنصات السحابية مثل خدمات أمازون ويب Amazon Web Services وسحابة جوجل Google Cloud ومايكروسوفت أزور Microsoft Azure موارد لتطوير الذكاء الاصطناعي ونشره. تعرّف على هذه المنصات لتسخير قدراتها في بناء مشاريعك. إن إنشاء بيئة تطوير قوية يضع الأساس لبرمجة ناجحة للذكاء الصناعي. مع بايثون ومكتبات الذكاء الصناعي و بيئة التطوير المتكاملة المناسبة والتحكم في الإصدار، فأنت مجهز جيدًا لبدء رحلة الذكاء الصناعي الخاصة بك. تجدر الإشارة أيضًا، إلا أن المبدتئين يمكنهم تجاوز الخطوات 6 و 7 مبدأيًا، والخوض بهما لاحقًا عند الحاجة. مراحل برمجة نموذج ذكاء اصطناعي؟ تتضمن عملية تطوير نماذج الذكاء الصناعي مرحلتين أساسيتين هما: عملية التدريب وعملية التقييم. إنهما مرحلتين حاسمتين في برمجة الذكاء الصناعي. مرحلة التدريب هي المرحلة التي يتعلم فيها نموذجك من البيانات ويضبط معلماته Parameters (العناصر القابلة للتعديل داخل النموذج، والتي تؤثر بشكل كبير على أداء النموذج وسلوكه) لعمل تنبؤات دقيقة في العالم الحقيقي. يتمثل دورك كمبرمج للذكاء الصناعي في هذه المرحلة في بناء النموذج وإعداد البيانات اللازمة للتدريب والتأكد من أن نموذجك يتعلم من البيانات بشكل فعّال وينتج تنبؤات دقيقة (أي التدريب ومراقبة صحة عملية التدريب). أما مرحلة التقييم، فهي المرحلة التي تلي مرحلة التدريب، حيث تتضمن سلسلة من الاختبارات اللازمة للتأكد من مدى فعالية النموذج عند تعريضه لبيانات العالم الحقيقي. من خلال فهم الفروق الدقيقة في تدريب النموذج والتحقق من صحة التدريب والتقييم، ستتمكن من ضبط تطبيقات الذكاء الصناعي الخاصة بك للحصول على الأداء الأمثل. ستمهد هذه المعرفة الطريق لك لإنشاء نماذج ذكاء اصطناعي أكثر تطوراً ودقة في المستقبل. نشر نماذج الذكاء الاصطناعي تظهر القيمة الحقيقية لنموذج الذكاء الصناعي عند نشره واستخدامه في سيناريوهات العالم الحقيقي، إنه الخطوة المحورية التي تحول الخوارزميات المدربة من تجارب إلى حلول عملية. إنها المرحلة التي يتم فيها إتاحة نماذج الذكاء الصناعي الخاصة بك للتفاعل مع العالم الحقيقي وإجراء تنبؤات أو تصنيفات أو قرارات. نتحدث في هذا القسم عن تعقيدات نشر نماذج الذكاء الصناعي ونغطي الاعتبارات المختلفة للنشر الناجح. اختيار نهج النشر الصحيح عند نشر نماذج الذكاء الاصطناعي، لديك عدة خيارات، لكل منها مزاياها ومقايضاتها. سنناقش طريقتين أساسيتين للنشر: النشر المحلي والنشر المستند إلى مجموعة النظراء. النشر المحلي أو النشر في أماكن العمل On-Premises Deployment: يوفر نشر نماذج الذكاء الصناعي في أماكن العمل درجة عالية من التحكم والأمان وخصوصية البيانات. يتضمن نهج النشر هذا استضافة نماذج الذكاء الصناعي الخاصة بك على الخوادم المحلية أو الأجهزة المتطورة داخل البنية التحتية لمؤسستك. يعد النشر المحلي مفيدًا بشكل خاص عند التعامل مع البيانات الحساسة أو عندما تكون التفاعلات ذات زمن الوصول المنخفض ضرورية. النشر المستند إلى السحابة Cloud-Based Deployment: أحدث النشر المستند إلى السحابة ثورة في طريقة إتاحة نماذج الذكاء الاصطناعي للمستخدمين، مما يوفر قابلية التوسع وإمكانية الوصول والتكلفة المنخفضة. هنا تحتاج إلى تحديد مزود الخدمة السحابية المناسب الذي يناسب متطلبات مشروعك، وأشهرها خدمات أمازون ويب Amazon Web Services ومنصة جوجل السحابية Google Cloud Platform ومايكروسوفت آزور Microsoft Azure. تحسين النموذج المستمر يعد تحسين النموذج للنشر خطوة مهمة لضمان أن تكون نماذج الذكاء الصناعي فعّالة وسريعة وقادرة على تقديم تنبؤات دقيقة في سيناريوهات العالم الحقيقي. نظرًا لأن نماذج الذكاء الصناعي أصبحت أكثر تعقيدًا، فإن حجمها ومتطلباتها الحسابية يمكن أن تشكل تحديات عند نشرها على أجهزة مختلفة، من الأجهزة المتطورة إلى الخوادم السحابية. تلعب تقنيات تحسين النموذج دورًا مهمًا لمواجهة هذه التحديات. أهم هذه التقنيات هم التكميم والتقليم والضغط. يمكن لممارسي الذكاء الاصطناعي تحقيق توازن بين أداء النموذج وكفاءة النشر من خلال الخوض في استراتيجيات التحسين هذه، مما يضمن أن حلول الذكاء الصناعي الخاصة بهم تقدم النتائج بسرعة وفعاليّة. التكميم Quantization: التكميم هو تقنية مهمة في مجال نشر نماذج الذكاء الاصطناعي. يتضمن تقليل دقة القيم العددية في معلمات النموذج، عادةً من عرض بت أعلى (على سبيل المثال، 32 بت) إلى عرض بت أقل (على سبيل المثال، 8 بتات). ينتج عن هذه العملية تمثيل أفضل عمليًا للنموذج، مما يقلل بشكل كبير من استخدام الذاكرة ويحسن سرعة الاستدلال. إذًا يتيح التكميم نشر نماذج الذكاء الصناعي بكفاءة على الأجهزة محدودة الموارد مثل الأجهزة المتطورة والهواتف المحمولة وأجهزة إنترنت الأشياء، وذلك من خلال التضحية بالقليل من الدقة. إنه يحقق توازنًا بين الكفاءة الحسابية والحفاظ على الأداء المعقول، مما يجعله أداة لا غنى عنها في ترسانة مهندسي الذكاء الصناعي الذين يسعون جاهدين للحصول على حلول النشر المثلى. التقليم Pruning: التقليم هو أسلوب آخر في مجال تحسين نموذج الذكاء الصناعي للنشر، يهدف إلى تبسيط التعقيد وتحسين كفاءة النماذج المُدرّبة. تتضمن هذه العملية القضاء الانتقائي على الاتصالات الزائدة أو العقد أو المعلمات من الشبكة العصبية، وبالتالي تقليل حجمها الكلي مع السعي للحفاظ على أدائها في نفس الوقت. لا ينتج عن التقليم نماذج أخف وأسرع أثناء الاستدلال فحسب، بل يساهم أيضًا في معالجة مشكلات الضبط المُفرط Overfitting. إذًا يعمل التقليم على تحسين قدرات التعميم من خلال إزالة المكونات الأقل تأثيرًا في النموذج، وغالبًا ما يؤدي إلى تحسين الدقة عمومًا. تُقدّم هذه التقنية إمكانية التوازن بين تعقيد النموذج والأداء، وتوضح كيف يمكن أن يؤدي التبسيط الاستراتيجي إلى حلول ذكاء اصطناعي أكثر مرونة وفعالية. الضغط Compression: يعد الضغط تقنية أخرى في عالم نشر نماذج الذكاء الصناعي. يهدف أيضًا إلى تحقيق توازن بين دقة النموذج وكفاءة الموارد. تلعب تقنيات الضغط دورًا مهمًا في تقليل الذاكرة التي يستهلكها النموذج وتسريع الاستدلال دون التضحية الكبيرة في الأداء التنبئي، وذلك من خلال إزالة المعلومات الزائدة واستغلال الأنماط داخل معلمات النموذج. لا يؤدي الضغط إلى تحسين متطلبات التخزين فحسب، بل يُقلل أيضًا الزمن اللازم للتنبؤ. من تقليم الاتصالات غير المهمة إلى تحديد قيم المعلمات، يمكّن الضغط مطوري الذكاء الاصطناعي من نشر نماذج قوية وفعّالة عمليًا، مما يضمن السلاسة عبر الأجهزة وسيناريوهات الاستخدام المحتملة. الأمان والخصوصية تعتبر اعتبارات الأمان والخصوصية في عملية نشر نماذج الذكاء الاصطناعي ذات أهمية قصوى. نظرًا لأن نماذج الذكاء الاصطناعي تتفاعل مع البيانات الحساسة وتقوم بالتنبؤات التي تؤثر على القرارات الحاسمة، فإن الحماية من الوصول غير المصرح به وضمان الاستخدام الأخلاقي يصبح أمرًا ضروريًا. يعد تنفيذ إجراءات أمنية قوية أمرًا ضروريًا للحفاظ على ثقة المستخدم والامتثال للوائح. تلعب تقنيات مثل التشفير دورًا محوريًا في تأمين البيانات أثناء النقل والتخزين. تساعد المصادقة المستندة إلى الرمز المميز والتحكم في الوصول المُستند إلى الدور -في تنظيم من يمكنه الوصول إلى واجهات برمجة تطبيقات الذكاء الاصطناعي، مما يضمن أن المستخدمين المُصرّح لهم فقط هم من يمكنهم إجراء تنبؤات. إضافةً إلى ذلك ونظرًا لأن أنظمة الذكاء الصناعي غالبًا ما تتعامل مع المعلومات الشخصية والسرية، فمن الضروري اعتماد أساليب الحفاظ على الخصوصية، مثل إخفاء البيانات وحماية الهويات الشخصية. إذًا من خلال تضمين اعتبارات الأمان والخصوصية عند نشر نماذج الذكاء الاصطناعي، يؤمّن المطورون بيئة أكثر أمانًا ومسؤولية لمستخدمي الذكاء الصناعي. المراقبة والصيانة المراقبة والصيانة هما شريان الحياة لنماذج الذكاء الصناعي المنشورة، حيث تضمنان استمرار دقتها وموثوقيتها وأدائها. بعد نشر نموذج الذكاء الصناعي الخاص بك، من المهم إنشاء نهج منظّم لمراقبة سلوكه ومعالجة أي مشكلات قد تنشأ لاحقًا. تتضمن المراقبة تتبع مقاييس مختلفة، مثل دقة التنبؤ وأوقات الاستجابة واستخدام الموارد لاكتشاف الأخطاء أو الانحرافات عن السلوك المتوقع. يجري ذلك من خلال إعداد التنبيهات والفحوصات الآلية، وبالتالي إمكانية تحديد أي مشكلات ومعالجتها على الفور قبل أن تؤثر على المستخدمين أو العمليات التجارية. بالنسبة للصيانة فهي تتجاوز موضوع الكشف عن الخلل وإصلاحه؛ إنها تنطوي على إدارة نماذج الذكاء الصناعي الخاصة بك بمرور الوقت، فمع تغيّر البيانات وتطور متطلبات المستخدم، قد يحتاج نموذجك إلى تحديثات دورية ليظل فعّالاً. تتضمن الصيانة أيضًا تحديد إصدارات النماذج وواجهات برمجة التطبيقات، مما يتيح لك إجراء تحسينات دون الإخلال بتجربة المستخدم. يضمن الاختبار المنتظم والتحقق من صحة سير العمل وقياس الأداء مقابل البيانات الجديدة، استمرار نماذجك في تقديم نتائج دقيقة. من خلال ضبط نماذجك باستمرار وإبقائها مواكبة لأحدث التطورات، فإنك تضمن أن حلول الذكاء الاصطناعي المنشورة تظل أصولًا قيّمة لمؤسستك. مستقبل برمجة الذكاء الاصطناعي يتطور مجال برمجة الذكاء الصناعي بسرعة، ويحمل المستقبل إمكانيات مثيرة للاهتمام. من المرجح أن تصبح الحلول التي تعمل بالذكاء الصناعي أكثر سهولة، مع الأدوات التي تسمح للمطورين بمستويات مختلفة من الخبرة لإنشاء تطبيقات الذكاء الصناعي. نستكشف الآن الاتجاهات والتحديات والإمكانيات التي تنتظر مستقبل برمجة الذكاء الصناعي. من الخوارزميات إلى أنظمة التعلم تتضمّن برمجة الذكاء الاصطناعي تقليديًا، صياغة خوارزميات معقدة لحل مشاكل محددة. إن مستقبل برمجة الذكاء الصناعي يتميز بالتحول نحو بناء أنظمة التعلم التي يمكن أن تتكيف وتتطور. لقد دفع التعلم الآلي وتقنيات التعلم العميق هذا التطور، مما مكّن أجهزة الحاسب من التعلم من البيانات وتحسين أدائها بمرور الوقت. نظرًا لأن برمجة الذكاء الصناعي تصبح أكثر اعتمادًا على البيانات، سيركز المطورون بشكل متزايد على إنشاء خوارزميات تعلم قوية وقابلة للتكيف والتطور وفقًا للبيانات. التعاون مع التخصصات الأخرى لم تعد برمجة الذكاء الصناعي مجالًا لعلماء الحاسب فقط. سيشهد المستقبل تعاونًا متزايدًا بين مبرمجي الذكاء الصناعي وخبراء المجال وعلماء الأخلاق وعلماء النفس وغيرهم من المتخصصين. سيتطلب بناء أنظمة ذكاء اصطناعي ناجحة اتباع نهج متعدد التخصصات لضمان توافق التكنولوجيا مع احتياجات وقيم العالم الحقيقي. الذكاء الاصطناعي القابل للتفسير عندما تصبح أنظمة الذكاء الاصطناعي أكثر تعقيدًا، يمكن أن تظهر عمليات اتخاذ القرار الخاصة بها على أنها "صناديق سوداء". هذا النقص في الشفافية يعيق التبني الأوسع للذكاء الاصطناعي في التطبيقات الهامة (عدم الثقة بقرار لاتعرف كيف أُنتج). سيشهد المستقبل طفرة في جهود البحث والتطوير لإنشاء أنظمة ذكاء اصطناعي قابلة للتفسير. ستوفر هذه الأنظمة رؤى حول كيفية توصل نماذج الذكاء الصناعي إلى قراراتهم، وبالتالي تعزيز الثقة بقرارات هذه النماذج وإمكانية التفسير. التعلم الآلي التلقائي AutoML أصبحت برمجة الذكاء الاصطناعي نفسها أكثر ذكاءً. يشهد التعلم الآلي التلقائي اهتمامًا كبيرًا بين الباحثين والشركات. إنه مجال يُركز على أتمتة المراحل المختلفة من خط أنابيب التعلم الآلي. سيستخدم المطورون أدوات AutoML لاختيار الخوارزميات وعمليات المعالجة المسبقة وتحسين النماذج تلقائيًا، مما يجعل من عملية تطوير نماذج الذكاء الصناعي أكثر سهولة. واجهات اللغة الطبيعية تتضمن برمجة الذكاء الاصطناعي التقليدية كتابة التعليمات البرمجية بلغات البرمجة. قد يتضمن المستقبل برمجة أنظمة الذكاء الاصطناعي باستخدام لغة طبيعية، أي أن مفهوم برمجة الذكاء الاصطناعي من الممكن أن يصبح أكثر تطورًا وسهولة. سيؤدي ذلك إلى إضفاء الطابع الديمقراطي على تطوير وبرمجة الذكاء الاصطناعي، مما يسمح للأفراد الذين ليس لديهم خبرة في كتابة الشيفرت البرمجية، بإنشاء تطبيقات ذكية من خلال محادثات بسيطة مع واجهات الذكاء الاصطناعي. الحوسبة المتطورة والذكاء الاصطناعي تستعد حوسبة الحافة Edge Computing (نموذج حوسبة يهدف إلى تنفيذ المعالجة والحوسبة قرب مصادر البيانات "الحواف")، لتشكيل مستقبل برمجة الذكاء الاصطناعي. ستحتاج خوارزميات الذكاء الاصطناعي إلى التحسين للنشر على الأجهزة محدودة الموارد مثل الهواتف الذكية وأجهزة إنترنت الأشياء والأنظمة المدمجة. سيتطلب ذلك مستوى جديدًا من الكفاءة والقدرة على التكيف في برمجة الذكاء الاصطناعي. التعلم المستمر مدى الحياة والذكاء الاصطناعي غالبًا ما يتم تدريب أنظمة الذكاء الاصطناعي التقليدية على مجموعات البيانات الثابتة. سيشهد المستقبل صعود التعلم المستمر والذكاء الاصطناعي مدى الحياة، حيث يمكن للأنظمة التعلم من البيانات الجديدة وتكييف معارفها دون نسيان تجارب التعلم السابقة. سيتطلب ذلك تقنيات خاصة لبرمجة الذكاء الاصطناعي تسهل التكامل السلس للمعلومات الجديدة في النماذج الحالية. برمجة الذكاء الاصطناعي الكمومية سيؤدي ظهور الحوسبة الكمومية Quantum computing إلى إحداث نقلة نوعية في برمجة الذكاء الاصطناعي. تمتلك أجهزة الحاسب الكمومية القدرة على إحداث ثورة في خوارزميات التعلم الآلي والعميق ومشكلات التحسين. سيشهد المستقبل قيام مبرمجي الذكاء الاصطناعي بالتعمق في برمجة الذكاء الاصطناعي الكمومي لتسخير قوة الحوسبة الكمومية من أجل حلول ذكاء اصطناعي أسرع وأكثر كفاءة. الذكاء الاصطناعي المرتكز على الإنسان إن مستقبل برمجة الذكاء الاصطناعي لا يقتصر فقط على بناء آلات أكثر ذكاءً؛ يتعلق الأمر بإنشاء ذكاء اصطناعي يكمل القدرات البشرية ويزيدها. ستصبح أنظمة الذكاء الاصطناعي أكثر تخصيصًا، بحيث تتكيف مع تفضيلات واحتياجات المستخدمين الفرديين. يتطلب هذا التحول من مبرمجي الذكاء الاصطناعي التأكيد على مبادئ التصميم التي تتمحور حول الإنسان، وإنشاء أنظمة تعزز الخبرات البشرية والإنتاجية. التعلم الموحد مفهوم تحولي آخر يأتي مع انتشار الأجهزة المتصلة عبر الشبكات، حيث يُمكّن نماذج الذكاء الاصطناعي من التدريب بطريقة تعاونية عبر العديد من الأجهزة اللامركزية المتصلة بالشبكة، دون الحاجة إلى نقل البيانات الأولية إلى خادم مركزي. الذكاء الاصطناعي وإنترنت الأشياء IoT يمثل تكامل الذكاء الاصطناعي مع إنترنت الأشياء تقاربًا بين تقنيتين تحويليتين تمتلكان القدرة على إعادة تشكيل الصناعات والحياة اليومية. يشير إنترنت الأشياء IoT إلى شبكة من الأجهزة المادية المترابطة والمجهزة بأجهزة استشعار وبرامج واتصالات، مما يمكنها من جمع البيانات وتبادلها. من الناحية الأخرى، يُمكّن الذكاء الاصطناعي الآلات من محاكاة الوظائف الإدراكية البشرية مثل التعلم والاستدلال واتخاذ القرار. عندما تتضافر هاتان القوتان، تظهر أوجه تآزر ملحوظة. يضفي الذكاء الاصطناعي براعته التحليلية على الكم الهائل من البيانات التي تنتجها أجهزة إنترنت الأشياء. يمكن لهذه الأجهزة جمع البيانات من البيئة أو تفاعلات المستخدم أو حتى من الأجهزة الأخرى. يمكن لخوارزميات الذكاء الاصطناعي بعد ذلك معالجة هذه البيانات لاستخراج الأنماط والاتجاهات والحالات الشاذة التي يصعب على المحللين البشريين تمييزها في الزمن الحقيقي. تخيل مدينة ذكية حيث تتعاون الأجهزة المترابطة في إشارات المرور والكاميرات والمركبات لتحسين تدفق حركة المرور استجابةً لظروف الوقت الفعلي. تعالج خوارزميات الذكاء الاصطناعي البيانات من هذه الأجهزة وتتنبأ بالازدحام المروري وتضبط توقيت إشارات المرور ديناميكيًا للتخفيف من الاختناقات. التحديات والقيود في برمجة الذكاء الاصطناعي في حين أن مجال برمجة الذكاء الاصطناعي يوفر فرصًا هائلة، إلا أنه مصحوب أيضًا بمجموعة من التحديات والقيود التي يجب على المطورين التعامل معها. أبرزها: جودة البيانات وكميتها الموارد الحسابية القابلية للتفسير والشرح الأخلاق والتحيز الافتقار إلى الفطرة السليمة وفهم السياق التعميم والقدرة على التكيف الأمان والخصوصية استهلاك الطاقة التعاون بين الإنسان والذكاء الاصطناعي التعلم المستمر والتحديثات 1. جودة البيانات وكميتها تعتمد أنظمة الذكاء الاصطناعي بشكل كبير على البيانات للتعلم واتخاذ القرارات. يمكن أن تؤثر جودة وكمية البيانات المتاحة بشكل كبير على أداء نماذج الذكاء الاصطناعي. يمكن أن تؤدي البيانات غير الكاملة أو المتحيزة أو التي تتضمّن أخطاءً إلى تنبؤات غير دقيقة ونتائج غير موثوقة. 2. الموارد الحسابية تتطلب خوارزميات الذكاء الاصطناعي، وخاصة نماذج التعلم العميق، موارد حسابية كبيرة. تتطلب نماذج التدريب المعقدة أجهزة قوية، وعلى الرغم من أن الحوسبة السحابية جعلت هذه الموارد أكثر سهولة، إلا أن التكلفة والتوافر لا يزالان من القيود المفروضة على بعض المشاريع. 3. القابلية للتفسير والشرح مع ازدياد تعقيد أنظمة الذكاء الاصطناعي، يصبح فهم كيفية التوصل إلى قرارات معينة تحديًا متزايدًا. يمكن أن يؤدي هذا الافتقار إلى الشفافية إلى إعاقة الثقة والتبني، خاصة في التطبيقات المهمة مثل الرعاية الصحية والتمويل. 4. الأخلاق والتحيز يمكن لنماذج الذكاء الاصطناعي أن تديم التحيزات الموجودة في بيانات التدريب دون قصد، مما يؤدي إلى نتائج غير عادلة أو تمييزية. يمثل تحقيق التوازن بين قدرات اتخاذ القرار في الذكاء الاصطناعي والاعتبارات الأخلاقية تحديًا مستمرًا. 5. الافتقار إلى الفطرة السليمة وفهم السياق تكافح أنظمة الذكاء الاصطناعي مع التفكير المنطقي وفهم السياق بنفس الطريقة التي يعمل بها البشر. قد يتفوقون في مهام محددة لكنهم يفتقرون إلى الفهم الأوسع الذي يمتلكه البشر. 6. التعميم والقدرة على التكيف بينما يمكن تدريب نماذج الذكاء الاصطناعي على أداء مهام محددة، قد يكون نقل تلك المعرفة إلى سيناريوهات أو مجالات جديدة أمرًا صعبًا. لا يزال تحقيق القدرة الحقيقية على التكيّف عبر مختلف السياقات يمثل تحديًا مستمرًا. 7. الأمان والخصوصية أنظمة الذكاء الاصطناعي التي تتعامل مع البيانات الحساسة عرضة للانتهاكات الأمنية. بالإضافة إلى ذلك، يشكل ضمان الخصوصية عند التعامل مع بيانات المستخدم تحديات أخلاقية وقانونية تتطلب دراسة متأنية. 8. استهلاك الطاقة يمكن أن يكون تدريب نماذج التعلم العميق مُستهلكًا كبيرًا للطاقة، مما يساهم في إثارة المخاوف بشأن التأثير البيئي لتطوير الذكاء الاصطناعي. 9. التعاون بين الإنسان والذكاء الاصطناعي إن دمج الذكاء الاصطناعي بسلاسة في سير العمل البشري يعتبر تحديًا، ويتطلب التأكد من أن الذكاء الاصطناعي يُكمل المهارات البشرية ويساعد بشكل فعال، وهذا يتطلب فهماً عميقاً لكلا المجالين. 10. التعلم المستمر والتحديثات تحتاج نماذج الذكاء الاصطناعي إلى التطور بمرور الوقت لتبقى ملائمة ودقيقة. يتطلب تحديث النماذج والتحسين المستمر لأدائها جهدًا مستمرًا. التعرف على هذه القيود أمر بالغ الأهمية لإنشاء أنظمة ذكاء اصطناعي مسؤولة وفعالة. تتطلب مواجهة هذه التحديات التعاون بين الباحثين والمطورين وواضعي السياسات والمجتمع ككل. بينما ندفع حدود برمجة الذكاء الاصطناعي، من المهم الحفاظ على منظور متوازن والعمل على التغلب على هذه العقبات من أجل تحسين مستقبلنا الذي يحركه الذكاء الاصطناعي. مصادر تعلم برمجة الذكاء الاصطناعي يعد التعرف على الذكاء الاصطناعي رحلة مثيرة تتطلب الوصول إلى مجموعة متنوعة من الموارد لتعميق فهمك ومهاراتك. فيما يلي بعض موارد تعلم الذكاء الاصطناعي القيمة لمساعدتك على البدء والتقدم في هذا المجال: الدورات التدريبية هناك العديد من الموارد المتاحة عبر الإنترنت لتعلم برمجة الذكاء الاصطناعي. تقدم أكاديمية حسوب دورة شاملة لتعلم برمجة الذكاء الاصطناعي باستخدام لغة بايثون، التي تعد كما ذكرنا اللغة الأساسية لكتابة تطبيقات ونماذج الذكاء الصناعي، حيث يمكنك تعلمها من خلال دورة الذكاء الاصطناعي التي تعد دورة شاملة لاحتراف برمجة الذكاء الاصطناعي وتحليل البيانات وتعلم كافة المعلومات المطلوبة لبناء نماذج ذكاء اصطناعي متخصصة مع مُدربين مُميزين يُرافقونك طوال الدورة للإجابة عن استفساراتك. تبقى الدورة مفتوحة لك مدى الحياة، كما أنها تطور باستمرار لمواكبة أحدث التطورات في المجال. الكتب يُمثل كتاب مدخل إلى الذكاء الاصطناعي وتعلم الآلة نقطة بدءٍ مناسبةٍ للمبتدئين الذين يرغبون الخوض في هذا المجال ويرغبون في فهم المفاهيم الأساسية للتعلم العميق والشبكات العصبية وتعلم الآلة، حيث يبدأ الكتاب معك من الأساسيات ليمهد الطريق أمامك لتعلم هذا العلم. يمكنك أيضًا الاطلاع على كتاب عشرة مشاريع عملية عن الذكاء الاصطناعي إما بعد الكتاب السابق أو يمكنك البدء به مباشرة إن كانت لديك خبرة أساسية. مؤتمرات وورش عمل الذكاء الاصطناعي يُعتبر مؤتمر NeurIPS أحد أكبر مؤتمرات الذكاء الاصطناعي مع العروض التقديمية وورش العمل. هناك أيضًا مؤتمر CVPR الذي يُركز على رؤية الحاسب والتعلم العميق و ICML يُغطي أبحاث وتطبيقات التعلم الآلي. المقالات والمدونات يمكنك أيضًا الاطلاع على عشرات المقالات المتعلقة بالذكاء الصناعي، والتي توفرها أكاديمية حسوب في قسم الذكاء الاصطناعي. المجتمعات عبر الإنترنت يوفر قسم أسئلة وأجوبة في أكاديمية حسوب إمكانية طرح أسئلة للإجابة عليها من قبل مختصين في كافة المجالات ومنها مجال برمجة الذكاء الاصطناعي. البرمجة والممارسة يُقدّم موقع كاغل Kaggle مسابقات في الذكاء الاصطناعي ومجموعات بيانات وموارد سحابية لتدريب النماذج. يمكنك أيضًا الذهاب إلى غيت هاب GitHub لاستكشف مشاريع ومستودعات الذكاء الاصطناعي مفتوحة المصدر. تُمكّنك أكاديمية حسوب أيضًا من البدء بإنشاء مشاريعك من خلال الاطلاع على مشاريع مرجعية مشروحة ومنظمة جيدًا. مثل كتاب عشرة مشاريع عملية عن الذكاء الاصطناعي. الوثائق الرسمية لأطر عمل الذكاء الاصطناعي اطلع على الوثائق الرسمية والبرامج التعليمية مثل توثيق باي تورش PyTorch وتوثيق تنسرفلو TensorFlow عبر موقعهم الرسمي. فهم يقدمون شروحات موجزة وأمثلة وحالات استخدام مختلفة إضافة إلى التوثيق الرسمي لكافة الأدوات المستخدمة في بناء وبرمجة الذكاء الاصطناعي. تذكر أن الذكاء الاصطناعي هو مجال سريع التطور، لذا فإن البقاء على اطلاع بأحدث الأوراق البحثية والتطورات أمر هام، كما أن البقاء على اطلاع على أحدث الأدوات المستخدمة في برمجة الذكاء الاصطناعي هو أمر هام أيضًا. استخدم مجموعة من هذه الموارد لبناء أساس قوي ومواصلة التفاعل مع مجتمع الذكاء الاصطناعي. خاتمة برز عالم الذكاء الاصطناعي كثورة ضمن المشهد التكنولوجي سريع التطور الذي ظهر في العقود القليلة الماضية، يقدم فرصًا وتحديات لا حدود لها على حد سواء. بينما كنا نتعمق في عالم برمجة الذكاء الاصطناعي، شرعنا في رحلة بدأت من التحدث عن لغات وأدوات برمجة الذكاء الاصطناعي إلى المكتبات والأطر المتطورة وإنشاء نماذج الذكاء الاصطناعي ونشر هذه النماذج. بالنظر إلى الأُفق، اكتشفنا المستقبل الذي ينتظر برمجة الذكاء الاصطناعي. إن مفاهيم مثل الذكاء الاصطناعي القابل للتفسير والتعلم الموحد ودمج الذكاء الاصطناعي مع إنترنت الأشياء هي المفتاح لفتح أبعاد جديدة للابتكار. بينما نرتقي إلى مستويات أعلى، يجب أن نعترف بالقيود والاعتبارات الأخلاقية التي تقدمها برمجة الذكاء الاصطناعي ونعالجها. أخيرًا، كان لابد لنا من ذكر بعض من المصادر الهامة لتعلّم الذكاء الصناعي. في الختام، مجال برمجة الذكاء الاصطناعي هو نسيج معقد منسوج بخيوط من الابتكار والأخلاق وإمكانات لا حدود لها. بينما نتنقل في هذا المشهد المعقد، فإننا لسنا مجرد مبرمجي آلات؛ نحن نشكل مستقبل الأنظمة الذكية التي لديها القدرة على إحداث ثورة في الصناعات وتعزيز حياة البشر وإعادة تعريف نسيج وجودنا التكنولوجي. إن سمفونية جهودنا في برمجة الذكاء الاصطناعي تتماشى مع الوعد بمستقبل حيث تشرع الآلات، مسترشدةً ببراعتنا، في رحلة من الذكاء والفهم. انظر أيضًا تعلم الذكاء الاصطناعي تعلم الآلة: التحديات الرئيسية وكيفية التوسع في المجال
  20. عندما تفشل دالة في لغة جو، فإنها تُعيد قيمةً باستخدام الواجهة error للسماح للمُستدعي بمعالجة الخطأ. في كثير من الأحيان يستخدم المطورون الدالة fmt.Errorf من الحزمة fmt لإعادة هذه القيم. قبل الإصدار 1.13 من لغة جو، كان الجانب السلبي لاستخدام هذه الدالة هو أننا سنفقد المعلومات المتعلقة بالأخطاء. لحل هذه المشكلة استخدم المطورون حزمًا توفر أساليب لتغليف wrap هذه الأخطاء داخل أخطاء أخرى، أو إنشاء أخطاء مخصصة من خلال تنفيذ التابع Error() string على أحد أنواع خطأ struct الخاصة بهم. أحيانًا، يكون إنشاء هذه الأنواع من struct مملًا إذا كان لديك عدد من الأخطاء التي لا تحتاج إلى المعالجة الصريحة من قبل من المُستدعي. جاء إصدار جو 1.13 مع ميزات لتسهيل التعامل مع هذه الحالات، وتتمثل إحدى الميزات في القدرة على تغليف الأخطاء باستخدام دالة fmt.Errorf بقيمة خطأ error يمكن فكها لاحقًا للوصول إلى الأخطاء المغلفة. بالتالي، أنهى تضمين دالة تغليف للأخطاء داخل مكتبة جو القياسية الحاجة إلى استخدام طرق ومكتبات خارجية. إضافةً إلى ذلك، تجعل الدالتان errors.Is و errors.As من السهل تحديد ما إذا كان خطأ محدد مُغلّف في أي مكان داخل خطأ ما، ويمنحنا الوصول إلى هذا الخطأ مباشرةً دون الحاجة إلى فك جميع الأخطاء يدويًا. سننشئ في هذا المقال برنامجًا يستخدم هذه الدوال لإرفاق معلومات إضافية مع الأخطاء التي تُعاد من الدوال، ثم سننشئ بنية struct للأخطاء المخصصة والتي تدعم دوال التغليف وفك التغليف. المتطلبات لتتابع هذا المقال، يجب أن تستوفي الشروط التالية: إصدار مُثبّت من جو 1.13 أو أعلى، ويمكنك الاستعانة بمقال تثبيت لغة جو Go وإعداد بيئة برمجة محلية على أبونتو Ubuntu لإعداده. تثبيت لغة جو وإعداد بيئة برمجة محلية على نظام ماك macOS. تثبيت لغة جو وإعداد بيئة برمجة محلية على ويندوز. (اختياري) قراءة مقال معالجة الأخطاء في لغة جو Go ، قد يكون مفيدًا للحصول على شرح أكثر تعمقًا لمعالجة الأخطاء. عمومًا نحن نغطي بعض الموضوعات منها في هذا المقال، لكن بشرح عالي المستوى. (اختياري) هذ المقال نوعًا ما هو امتداد لمقال معالجة حالات الانهيار في لغة جو Go مع الإشارة إلى بعض الميزات. قراءة المقالة السابقة مهم أو مفيد، لكن ليس ضروري. إعادة ومعالجة الأخطاء في لغة جو تُعد معالجة الأخطاء من الممارسات الجيدة التي تحدث أثناء تنفيذ البرامج حتى لا يراها المستخدمون أبدًا، لكن لا بُد من التعرّف عليها أولًا لمعالجتها. يمكننا في لغة جو معالجة الأخطاء في البرامج من خلال إعادة معلومات متعلقة بهذه الأخطاء من الدوال التي حدث فيها الخطأ باستخدام واجهة interface من نوع خاص هو error، إذ يسمح استخدام هكذا واجهة لأي نوع بيانات في لغة جو أن يُعاد مثل قيمة من نوع error طالما أن ذلك النوع لديه تابع Error() string معرّف. توفر مكتبة جو القياسية دوالًا، مثل fmt.Errorf للتعامل مع هذا الأمر وإعادة خطأ error. سننشئ في هذا المقال برنامجًا مع دالة تستخدم fmt.Errorf لإعادة خطأ error، وسنضيف أيضًا معالج خطأ للتحقق من الأخطاء التي يمكن أن ترجعها الدالة. يمكنك العودة للمقال معالجة الأخطاء في لغة جو Go . لدى معظم المطورين مجلد يضعون داخله مشاريعهم، وسنستخدم في هذا المقال مجلدًا باسم "projects". لننشئ هذا المجلد وننتقل إليه: $ mkdir projects $ cd projects سننشئ داخل مجلد "projects" مجلدًا باسم "errtutorial" لوضع البرنامج داخله: $ mkdir errtutorial سننتقل إليه: $ cd errtutorial سنستخدم الآن الأمر go mod init لإنشاء وحدة errtutorial: $ go mod init errtutorial نفتح الآن ملف "main.go" من مجلد errtutorial باستخدام محرر نانو nano أو أي محرر آخر تريده: $ nano main.go الآن، سنكتب البرنامج، الذي سيمر ضمن حلقة على الأرقام من 1 إلى 3 ويحاول أن يحدد ما إذا كان أحد هذه الأرقام صالحًا أم لا باستخدام دالة تُدعى validateValue؛ فإذا لم يكن الرقم صالحًا، سيستخدم البرنامج الدالة fmt.Errorf لتوليد قيمة من النوع error تُعاد منها، إذ تسمح هذه الدالة بإنشاء قيمة error، بحيث تكون رسالة الخطأ هي الرسالة التي تقدمها للدالة، وهي تعمل بطريقة مشابهة للدالة fmt.Printf، لكن تُعاد مثل خطأ بدلًا من طباعة الرسالة على الشاشة. الآن، ستكون هناك عملية تحقق من قيمة الخطأ في الدالة الرئيسية main، لمعرفة ما إذا كانت القيمة هي nil أو لا؛ فإذا كانت nil ستكون الدالة نجحت في التنفيذ دون أخطاء وسنرى الرسالة !valid، وإلا سيُطبع الخطأ الحاصل. لنضع الآن هذه الشيفرة داخل ملف "main.go": package main import ( "fmt" ) func validateValue(number int) error { if number == 1 { return fmt.Errorf("that's odd") } else if number == 2 { return fmt.Errorf("uh oh") } return nil } func main() { for num := 1; num <= 3; num++ { fmt.Printf("validating %d... ", num) err := validateValue(num) if err != nil { fmt.Println("there was an error:", err) } else { fmt.Println("valid!") } } } تأخذ الدالة validateValue في البرنامج السابق رقمًا وتعيد خطأً اعتمادًا على ما إذا كانت القيمة المُمررة صالحة أم لا. في مثالنا العدد 1 غير صالح، وبالتالي تُعيد الدالة خطأ مع رسالة that's odd. الرقم 2 غير صالح ويؤدي إلى إرجاع خطأ مع رسالة uh oh. تستخدم الدالة validateValue الدالة fmt.Errorf لتوليد قيمة الخطأ المُعادة، إذ تُعد الدالة fmt.Errorf ملائمة لإعادة الأخطاء، لأنها تسمح بتنسيق رسالة خطأ باستخدام تنسيق مشابه للدالة fmt.Printf أو fmt.Sprintf دون الحاجة إلى تمرير هذه السلسلة string إلى errors.New. ستبدأ حلقة for داخل الدالة main بالمرور على الأرقام من 1 إلى 3 وتخزّن القيمة ضمن المتغير num. سيؤدي استدعاء fmt.Printf داخل متن الحلقة إلى طباعة الرقم الذي يتحقق منه البرنامج حاليًا. بعد ذلك، سيجري استدعاء الدالة validateValue مع تمرير المتغير num (الذي نريد التحقق من صلاحيته)، وتخزين نتيجة الاستدعاء هذا في المتغير err. أخيرًا، إذا كانت قيمة err ليست nil، فهذا يعني أن خطًأ ما قد حدث وستُطبع رسالة الخطأ باستخدام fmt.Println، أما إذا كانت قيمته nil، فهذا يعني أن الرقم صالح وسنرى على الخرج "!valid". بعد حفظ التغييرات الأخيرة يمكننا تشغيل ملف البرنامج "main.go" باستخدام الأمر go run من المجلد "errtutorial": $ go run main.go سيُظهر الخرج أن البرنامج قد تحقق من صحة كل رقم، وأن الرقم 1 والرقم 2 أديا إلى إظهار الأخطاء المناسبة لهما: validating 1... there was an error: that's odd validating 2... there was an error: uh oh validating 3... valid! نلاحظ أن البرنامج يحاول التحقق من الأرقام الثلاثة من خلال الدالة validateValue؛ ففي المرة الأولى قد حصل على قيمة غير صالحة هي 1 فطبع رسالة الخطأ that's odd؛ وحصل في المرة الثانية على قيمة غير صالحة أيضًا هي 2 فطبع رسالة خطأ مختلفة هي uh oh؛ وحصل في المرة الثالثة على الرقم 3 وهي قيمة صالحة فأعاد قيمة !valid وفي هذه الحالة تكون قيمة الخطأ المُعادة هي nil إشارةً إلى عدم حدوث أي مشاكل وأن الرقم صالح. وفقًا للطريقة التي كُتبت فيها الدالة validateValue، يمكننا أن نقول أنها ستُعيد nil من أجل أي قيمة باستثناء الرقمين 1 و2. استخدمنا في هذا القسم الدالة fmt.Errorf لإنشاء قيم خطأ مُعادة من دالة، وأضفنا مُعالج خطأ يطبع رسالة الخطأ عند حصوله من الدالة. قد يكون من المفيد أحيانًا معرفة معنى الخطأ، وليس مجرد حدوث خطأ. سنتعلم في القسم التالي كيفية تخصيص معالجة الأخطاء لحالات محددة. معالجة أخطاء محددة باستخدام أخطاء الحارس Sentinel errors عندما نستقبل قيمة خطأ من دالة، فإن أبسط طريقة لمعالجة هذا الخطأ هي التحقق من قيمته إذا كانت nil أو لا، إذ يخبرنا هذا ما إذا كانت الدالة تتضمن خطأً، ولكن قد نرغب أحيانًا في تخصيص معالجة الأخطاء لحالة خطأ محددة. لنتخيل أن لدينا شيفرة مرتبطة بخادم بعيد، وأن معلومات الخطأ الوحيدة التي نحصل عليها هي "لديك خطأ". قد نرغب في معرفة ما إذا كان الخطأ بسبب عدم توفر الخادم، أو إذا كانت بيانات اعتماديات الاتصال connection credentials الخاصة بنا غير صالحة. إذا كنا نعلم أن الخطأ يعني أن بيانات اعتماد المستخدم خاطئة، فقد نرغب في إعلام المستخدم فورًا بذلك، ولكن إذا كان الخطأ يعني أن الخادم غير متاح، فقد نرغب في محاولة إعادة الاتصال عدة مرات قبل إعلام المستخدم. سنتمكن من خلال تحديد الاختلاف بين هذه الأخطاء من كتابة برامج أكثر قوة وسهولة في الاستخدام. تتمثل إحدى الطرق التي يمكن من خلالها التحقق من نوع محدد من الخطأ في استخدام التابع Error على النوع error للحصول على الرسالة من الخطأ ومقارنة هذه القيمة بنوع الخطأ الذي نبحث عنه. لنتخيل أننا نريد إظهار رسالة غير الرسالة there was an error: uh oh التي رأيناها سابقًا عند الحصول على الخطأ uh oh، وإحدى الطرق لمعالجة هذه الحالة هي التحقق من القيمة المعادة من التابع Error كما يلي: if err.Error() == "uh oh" { // Handle 'uh oh' error. fmt.Println("oh no!") } سيجري في الشيفرة أعلاه التحقق من قيمة السلسلة المُعادة من ()err.Error لمعرفة ما إذا كانت القيمة هي uh oh وستجري الأمور على نحو سليم، لكن لن تعمل الشيفرة السابقة إذا كانت السلسلة النصية التي تُعبّر عن الخطأ uh oh مختلفة قليلًا في مكان آخر في البرنامج. يمكن أن يؤدي التحقق من الأخطاء بهذه الطريقة أيضًا إلى تحديثات مهمة على الشيفرة إذا كانت رسالة الخطأ نفسها بحاجة إلى التحديث، إذ يجب تحديث كل مكان يجري فيه التحقق من الخطأ. خذ على سبيل المثال الشيفرة التالية: func giveMeError() error { return fmt.Errorf("uh h") } err := giveMeError() if err.Error() == "uh h" { // "uh h" error code } تتضمن رسالة الخطأ هنا خطأً إملائيًا، إذ يغيب الحرف oعن السلسلة uh oh. إذا حدث ولاحظنا هذا الخطأ وأردنا إصلاحه، يتعين علينا إصلاحه في جميع أجزاء الشيفرة الأخرى التي تتضمن جملة التحقق "err.Error() == "uh oh، وإذا نسينا أحدهم وهذا محتمل لأنه تغيير في حرف واحد فقط، فلن يعمل معالج الخطأ المخصص لأنه يتوقع uh h وليس uh oh. قد نرغب في مثل هذه الحالات بمعالجة خطأ محدد بطريقة مختلفة، إذ من الشائع إنشاء متغير يكون الغرض منه الاحتفاظ بقيمة خطأ. يمكن للشيفرة بهذه الطريقة أن تتحقق من حصول هذا المتغير بدلًا من السلسلة. تبدأ أسماء هذه المتغيرات عادةً بالعبارة Err أو err للإشارة إلى أنها أخطاء. استخدم err، إذا كان الهدف استخدام الخطأ فقط داخل الحزمة المعرّف فيها، أما إذا أردت استخدامه خارج الحزمة، استخدم البادئة Err ليصبح قيمة مُصدّرة exported value على غرار ما نفعله مع الدوال أو البنى struct. لنفترض الآن أنك كنت تستخدم إحدى قيم الخطأ هذه في المثال السابق الذي تضمن أخطاء إملائية: var errUhOh = fmt.Errorf("uh h") func giveMeError() error { return errUhOh } err := giveMeError() if err == errUhOh { // "uh oh" error code } جرى هنا تعريف المتغير errUhOh على أنه قيمة الخطأ للخطأ "uh oh" (على الرغم من أنه يحتوي على أخطاء إملائية). تُعيد الدالة giveMeError قيمة errUhOh لأنها تريد إعلام المُستدعي بحدوث خطأ uh oh. تقارن شيفرة معالجة الخطأ بعد ذلك قيمة err التي أُعيدت من giveMeError مع errUhOh لمعرفة ما إذا كان الخطأ "uh oh" هو الخطأ الذي حدث. حتى إذا جرى العثور على الخطأ الإملائي وجرى إصلاحه، فستظل جميع التعليمات البرمجية تعمل، لأن التحقق من الخطأ يجري من خلال المقارنة مع القيمة errUhOh، وقيمة errUhOh هي قيمة ثابتة تُعاد من giveMeError. تُعرّف قيمة الخطأ المُراد فحصها ومقارنتها بهذه الطريقة بخطأ الحارس sentinel error، وهو خطأ مُصمّم ليكون قيمة فريدة يمكن مقارنتها دائمًا بمعنًى معين. ستحمل قيمة errUhOh السابقة دائمًا نفس المعنى (حدوث خطأ uh oh)، لذلك يمكن للبرنامج الاعتماد على مقارنة خطأ مع errUhOh لتحديد ما إذا كان هذا الخطأ قد حدث أم لا. تتضمن مكتبة جو القياسية عددًا من أخطاء الحارس لتطوير برامج جو. أحد الأمثلة على ذلك هو خطأ sql.ErrNoRows، الذي يُعاد عندما لا يُعيد استعلام قاعدة البيانات أية نتائج، لذلك يمكن معالجة هذا الخطأ بطريقة مختلفة عن خطأ الاتصال. بما أنه خطأ حارس، بالتالي يمكن استخدامه في شيفرة التحقق من الأخطاء لمعرفة متى لا يُعيد الاستعلام أية صفوف rows، ويمكن للبرنامج التعامل مع ذلك بطريقة مختلفة عن الأخطاء الأخرى. عند إنشاء قيمة خطأ حارس، تُستخدم الدالة errors.New من حزمة errors بدلًا من دالة fmt.Errorf التي كنا نستخدمها. لا يؤدي استخدام errors.New بدلًا من fmt.Errorf إلى إجراء أي تغييرات أساسية في كيفية عمل الخطأ، ويمكن استخدام كلتا الدالتين بالتبادل في معظم الأوقات. أكبر فرق بين الاثنين هو أن errors.New تنشئ خطأ مع رسالة ثابتة، بينما تسمح دالة fmt.Errorf بتنسيق الرسالة مع القيم بطريقة مشابهة لآلية تنسيق السلاسل في fmt.Printf أو fmt.Sprintf. نظرًا لأن أخطاء الحارس هي أخطاء أساسية بقيم لا تتغير، يُعد استخدام errors.New لإنشائها شائعًا. لنحدّث الآن البرنامج السابق من أجل استخدام خطأ الحارس مع الخطأ uh oh بدلًا من fmt.Errorf. نفتح ملف "main.go" لإضافة خطأ الحارس errUhOh الجديد وتحديث البرنامج لاستخدامه. تُحدَّث دالة validateValue بحيث تعيد خطأ الحارس بدلًا من استخدام fmt.Errorf. تُحدّث دالة main بحيث تتحقق من وجود خطأ حارس errUhOh وطباعة oh no عندما يواجهها خطأ بدلًا من رسالة :there was an error التي تظهر لأخطاء أخرى. package main import ( "errors" "fmt" ) var ( errUhOh = errors.New("uh oh") ) func validateValue(number int) error { if number == 1 { return fmt.Errorf("that's odd") } else if number == 2 { return errUhOh } return nil } func main() { for num := 1; num <= 3; num++ { fmt.Printf("validating %d... ", num) err := validateValue(num) if err == errUhOh { fmt.Println("oh no!") } else if err != nil { fmt.Println("there was an error:", err) } else { fmt.Println("valid!") } } } نحفظ الشيفرة ونشغّل البرنامج كالعادة باستخدام الأمر go run: $ go run main.go سيُظهر الخرج هذه المرة ناتج الخطأ العام للقيمة 1، لكنه يستخدم الرسالة المخصصة !oh no عندما تصادف الخطأ errUhOh الناتج من تمرير 2 إلى validateValue: validating 1... there was an error: that's odd validating 2... oh no! validating 3... valid! يؤدي استخدام أخطاء الحارس داخل شيفرة فحص الأخطاء إلى تسهيل التعامل مع حالات الخطأ الخاصة، إذ يمكن لأخطاء الحارس مثلًا المساعدة في تحديد ما إذا كان الملف الذي نقرأه قد فشل لأننا وصلنا إلى نهاية الملف، والذي يُشار إليه بخطأ الحارس io.EOF، أو إذا فشل لسبب آخر. أنشأنا في هذا القسم برنامج جو يستخدم خطأ حارس باستخدام errors.New للإشارة إلى حدوث نوع معين من الأخطاء. بمرور الوقت ومع نمو البرنامج، قد نصل إلى النقطة التي نريد فيها تضمين مزيدٍ من المعلومات في الخطأ الخاص بنا بدلًا من مجرد قيمة الخطأ uh oh. لا تقدم قيمة الخطأ هذه أي سياق حول مكان حدوث الخطأ أو سبب حدوثه، وقد يكون من الصعب تعقب تفاصيل الخطأ في البرامج الأكبر حجمًا. للمساعدة في استكشاف الأخطاء وإصلاحها ولتقليل وقت تصحيح الأخطاء، يمكننا الاستفادة من تغليف الأخطاء لتضمين التفاصيل التي نحتاجها. تغليف وفك تغليف الأخطاء يُقصد بتغليف الأخطاء أخذ قيمة خطأ معينة ووضع قيمة خطأ أخرى داخلها، وكأنها هدية مغلفة، وبما أنها مغلفة، فهذا يعني أنك بحاجة لفك غلافها لمعرفة ما تحتويه. يمكننا من خلال تغليف الخطأ تضمين معلومات إضافية حول مصدر الخطأ أو كيفية حدوثه دون فقدان قيمة الخطأ الأصلية، نظرًا لوجودها داخل الغلاف. قبل الإصدار 1.13 كان من الممكن تغليف الأخطاء، إذ كان بالإمكان إنشاء قيم خطأ مخصصة تتضمن الخطأ الأصلي، ولكن سيتعين علينا إما إنشاء أغلفة خاصة، أو استخدام مكتبة تؤدي الغرض نيابةً عنا. بدءًا من الإصدار 1.13 أضافت لغة جو دعمًا لعملية تغليف الأخطاء وإلغاء التغليف بمثابة جزء من المكتبة القياسية عن طريق إضافة الدالة errors.Unwrap والعنصر النائب w% لدالة fmt.Errorf. سنحدّث خلال هذا القسم برنامجنا لاستخدام العنصر النائب w% لتغليف الأخطاء بمزيد من المعلومات، وبعد ذلك ستستخدم الدالة errors.Unwrap لاسترداد المعلومات المغلفة. تغليف الأخطاء مع الدالة fmt.Errorf كانت الدالة fmt.Errorf تُستخدم سابقًا لإنشاء رسائل خطأ منسقة بمعلومات إضافية باستخدام عناصر نائبة مثل v% للقيم المعمّمة و s% للسلاسل النصية، أما حديثًا (بدءًا من الإصدار 1.13) أُضيف عنصر نائب جديد هو w%. عندما يجري تضمين هذا العنصر ضمن تنسيق السلسلة وتتوفر قيمة للخطأ، ستتضمّن قيمة الخطأ المُعادة من fmt.Errorf قيمة الخطأ error المُغلّف في الخطأ المُنشأ. افتح ملف "main.go" وحدّثه ليشمل دالةً جديدةً تسمى runValidation. ستأخذ هذه الدالة الرقم الذي يجري التحقق منه حاليًا وستُشغّل أي عملية تحقق مطلوبة على هذا الرقم، وفي حالتنا ستحتاج إلى تنفيذ الدالة runValidation فقط. إذا واجه البرنامج خطأً في التحقق من القيمة، سيغلِّف الخطأ باستخدام fmt.Errorf والعنصر النائب w% لإظهار حدوث خطأ في التشغيل، ثم يعيد هذا الخطأ الجديد. ينبغي أيضًا تحديث الدالة main، فبدلًا من استدعاء validateValue مباشرةً، نستدعي runValidation: ... var ( errUhOh = errors.New("uh oh") ) func runValidation(number int) error { err := validateValue(number) if err != nil { return fmt.Errorf("run error: %w", err) } return nil } ... func main() { for num := 1; num <= 3; num++ { fmt.Printf("validating %d... ", num) err := runValidation(num) if err == errUhOh { fmt.Println("oh no!") } else if err != nil { fmt.Println("there was an error:", err) } else { fmt.Println("valid!") } } } يمكننا الآن -بعد حفظ التحديثات- تشغيل البرنامج: $ go run main.go سيظهر خرج مشابه لما يلي: validating 1... there was an error: run error: that's odd validating 2... there was an error: run error: uh oh validating 3... valid! هناك عدة أشياء يمكن ملاحظتها من هذا الخرج. إذ نرى أولًا رسالة الخطأ للقيمة 1 مطبوعةً الآن ومتضمنة run error: that's odd في رسالة الخطأ. يوضح هذا أن الخطأ جرى تغليفه بواسطة الدالة fmt.Errorf الخاصة بالدالة runValidation وأن قيمة الخطأ التي جرى تغليفها that's odd مُضمّنة في رسالة الخطأ. هناك مشكلة، إذ أن معالج الخطأ الخاص الذي أضفناه إلى خطأ errUhOh لم يُنفّذ، وسنرى في السطر الثاني من الخرج والذي يتحقق من صلاحية الرقم 2 -رسالة الخطأ الافتراضية للقيمة 2 وهي: there was an error: run error: uh oh بدلًا من الرسالة المتوقعة !oh no. نحن نعلم أن الدالة ValidateValue لا تزال تُعيد الخطأ uh oh، إذ يمكننا رؤية ذلك في نهاية الخطأ المُغلف، لكن معالج الخطأ في errUhOh لم يعد يعمل. يحدث هذا لأن الخطأ الذي أُعيد من الدالة runValidation لم يعد الخطأ errUhOh، وإنما الخطأ المغلف الذي أُنشئ بواسطة الدالة fmt.Errorf. عندما تحاول الجملة الشرطية if مقارنة متغير err مع errUhOh، فإنها تُعيد خطأ لأن errUhOh لم يعد مساويًا للخطأ الذي يُغلّفerrUhOh، ولحل هذه المشكلة يجب الحصول الخطأ من داخل الغلاف عن طريق فك التغليف باستخدام دالة errors.Unwrap. فك تغليف الأخطاء باستخدام errors.Unwrap إضافةً إلى العنصر النائب w% في الإصدار 1.13، أُضيفت بعض الدوال الجديدة إلى حزمة الأخطاء errors. واحدة من هذه الدوال هي الدالة errors.Unwrap، التي تأخذ خطأ error مثل معامل، وإذا كان الخطأ المُمرّر مُغلّف خطأ، ستعيد الخطأ المُغلّف، وإذا لم يكن الخطأ المُمرّر غلافًا تُعيد nil. نفتح الآن ملف "main.go"، وباستخدام الدالة errors.Unwrap سنحدّث آلية التحقق من خطأ errUhOh لمعالجة الحالة التي يجري فيها تغليف errUhOh داخل مغلّف خطأ: func main() { for num := 1; num <= 3; num++ { fmt.Printf("validating %d... ", num) err := runValidation(num) if err == errUhOh || errors.Unwrap(err) == errUhOh { fmt.Println("oh no!") } else if err != nil { fmt.Println("there was an error:", err) } else { fmt.Println("valid!") } } } شغّل البرنامج: $ go run main.go لتكون النتيجة على النحو التالي: validating 1... there was an error: run error: that's odd validating 2... oh no! validating 3... valid! سنرى الآن في السطر الثاني من الخرج أن خطأ !oh no للقيمة 2 قد ظهر. يسمح الاستدعاء الإضافي للدالة errors.Unwrap الذي أضفناه إلى تعليمة if باكتشاف errUhOh عندما تكون err هي قيمة خطأ errUhOh وكذلك إذا كان err هو خطأ يُغلّف خطأ errUhOh مباشرةً. استخدمنا في هذا القسم العنصر w% المضاف إلى fmt.Errorf لتغليف الخطأ errUhOh داخل خطأ آخر وإعطائه معلومات إضافية. استخدمنا بعد ذلك errors.Unwrap للوصول إلى الخطأ errorUhOh المُغلّف داخل خطأ آخر. يُعد تضمين أخطاء داخل أخطاء أخرى مثل قيم ضمن سلسلة string أمرًا مقبولًا بالنسبة للأشخاص الذين يقرؤون رسائل الخطأ، ولكن قد ترغب أحيانًا في تضمين معلومات إضافية مع غلاف الأخطاء لمساعدة البرنامج في معالجة الخطأ، مثل رمز الحالة status code في خطأ طلب HTTP، وفي هكذا حالة يمكنك إنشاء خطأ مخصص جديد لإعادته. يمكنك الاطلاع على مقال كيفية استكشاف وإصلاح رموز أخطاء HTTP الشائعة على أكاديمية حسوب لمزيدٍ من المعلومات حول رموز حالة أخطاء HTTP الشائعة. أخطاء مغلفة مخصصة بما أن القاعدة الوحيدة للواجهة error في لغة جو هي أن تتضمن تابع Error، فمن الممكن تحويل العديد من أنواع لغة جو إلى خطأ مخصص، وتتمثل إحدى الطرق في تعريف نوع بنية struct مع معلومات إضافية حول الخطأ وتضمين تابع Error. بالنسبة لخطأ التحقق Validation error، سيكون من المفيد معرفة القيمة التي تسببت بالخطأ. لننشئ الآن بنيةً جديدة اسمها ValueError تحتوي على حقل من أجل القيمة Value التي تسبب الخطأ وحقل Err يحتوي على الخطأ الفعلي. تستخدم عادةً أنواع الأخطاء المخصصة اللاحقة Error في نهاية اسم النوع، للإشارة إلى أنه نوع يتوافق مع الواجهة error. افتح الآن ملف "main.go" وضِف البنية الجديدة ValueError، إضافةً إلى دالة newValueError لتُنشئ نسخًا من هذه البنية. نحتاج أيضًا إلى إنشاء تابع يُسمى Error من أجل البنية ValueError لكي تُعد من النوع error. يجب أن يُعيد التابع Error القيمة التي تريد عرضها عندما يجري تحويل الخطأ إلى سلسلة نصية. نستخدم في حالتنا الدالة fmt.Sprintf لإعادة سلسلة نصية تعرض :value error ثم الخطأ المُغلّف. حدِّث الدالة ValidateValue، فبدلًا من إعادة الخطأ الأساسي فقط، ستستخدم الدالة newValueError لإعادة خطأ مخصص: ... var ( errUhOh = fmt.Errorf("uh oh") ) type ValueError struct { Value int Err error } func newValueError(value int, err error) *ValueError { return &ValueError{ Value: value, Err: err, } } func (ve *ValueError) Error() string { return fmt.Sprintf("value error: %s", ve.Err) } ... func validateValue(number int) error { if number == 1 { return newValueError(number, fmt.Errorf("that's odd")) } else if number == 2 { return newValueError(number, errUhOh) } return nil } ... لنُشغّل البرنامج الآن: $ go run main.go ستكون النتيجة على النحو التالي: validating 1... there was an error: run error: value error: that's odd validating 2... there was an error: run error: value error: uh oh validating 3... valid! يظهر الناتج الآن أن الأخطاء مُغلّفة داخل ValueError من خلال عرض :value error قبلها، لكن نلاحظ أن خطأ uh oh لم يُكتشف مرةً أخرى لأن errUhOh داخل طبقتين من الأغلفة الآن، هما: ValueError وغلاف fmt.Errorf من runValidation. تُستخدم الدالة errors.Unwrap مرةً واحدةً فقط على الخطأ، لذلك ينتج عن استخدام (errors.Unwrap(err القيمة ValueError* وليس errUhOh. تتمثل إحدى الحلول في تعديل عملية التحقق من errUhOh بإضافة استدعاء ()errors.Unwrap مرتين لفك كلتا الطبقتين. نفتح الآن ملف "main.go" ونُعدّل الدالة main لإضافة التعديل: ... func main() { for num := 1; num <= 3; num++ { fmt.Printf("validating %d... ", num) err := runValidation(num) if err == errUhOh || errors.Unwrap(err) == errUhOh || errors.Unwrap(errors.Unwrap(err)) == errUhOh { fmt.Println("oh no!") } else if err != nil { fmt.Println("there was an error:", err) } else { fmt.Println("valid!") } } } لنُشغّل البرنامج الآن: $ go run main.go ستكون النتيجة على النحو التالي: validating 1... there was an error: run error: value error: that's odd validating 2... there was an error: run error: value error: uh oh validating 3... valid! نلاحظ أن معالجة الأخطاء الخاصة errUhOh لا تعمل، إذ كنا نتوقع ظهور خرج معالج الخطأ الخاص !oh no من أجل سطر التحقق الثاني لكن ما زالت الرسالة الافتراضية . . .:there was an error: run error تظهر بدلًا منها. هذا يحدث لأن errors.Unwrap لا تعرف كيفية فك تغليف الخطأ المخصص ValueError. يجب أن يكون لدى الخطأ المخصص تابع فك تغليف Unwrap خاص، يعيد الخطأ الداخلي مثل قيمة error.عندما كنا نُنشئ أخطاءً باستخدام fmt.Errorf مع العنصر النائب w%، كان مُصرّف جو يُنشئ خطأ مع تابع تغليف Unwrap تلقائيًا، لذلك لم نكن بحاجة إلى إضافة التابع يدويًا، لكننا هنا نستخدم دالةً خاصة، وبالتالي يجب أن نُضيف هذا التابع يدويًا. إذًا، لإصلاح مشكلة errUhOh نفتح ملف "main.go" ونضيف التابع Unwrap إلى ValueError التي تُعيد الحقل Err الذي يحتوي على الخطأ الداخلي المغلف: ... func (ve *ValueError) Error() string { return fmt.Sprintf("value error: %s", ve.Err) } func (ve *ValueError) Unwrap() error { return ve.Err } ... لنُشغّل البرنامج الآن: $ go run main.go ستكون النتيجة كما يلي: validating 1... there was an error: run error: value error: that's odd validating 2... oh no! validating 3... valid! نلاحظ أن المشكلة قد حُلت وظهرت رسالة الخطأ !oh no التي تُشير إلى الخطأ errUhOh هذه المرة، لأن errors.Unwrap أصبح قادرًا على فك تغليف ValueError. أنشأنا في هذا القسم خطأ جديد مخصص ValueError، لتزويدنا والمستخدمين بمعلومات إضافية عن عملية التحقق في جزء من رسالة الخطأ. أضفنا أيضًا دعمًا (الدالة Unwrap) لعملية فك تغليف الأخطاء إلى ValueError، بحيث يمكن استخدام errors.Unwrap للوصول إلى الخطأ المغلف. يمكننا أن نلاحظ عمومًا أن معالجة الأخطاء أصبحت صعبة نوعًا ما ويصعب الحفاظ عليها، إذ يجب علينا إضافة دالة فك تغليف errors.Unwrap من أجل كل طبقة تغليف جديدة. لحسن حظنا هناك دوال جاهزة errors.Is و errors.As تجعل العمل مع الأخطاء المغلفة أسهل. التعامل مع الأخطاء المغلفة Wrapped Errors عند الحاجة إلى إضافة استدعاء جديد للدالة errors.Unwrap من أجل كل طبقة تغليف في البرنامج، سيستغرق الأمر وقتًا طويلًا ويصعب الحفاظ عليه. لهذا الأمر أُضيفت الدالتان errors.Is و errors.As إلى حزمة الأخطاء errors بدءًا من الإصدار 1.13 من لغة جو، إذ تعمل هاتان الدالتان على تسهيل التعامل مع الأخطاء من خلال السماح لك بالتفاعل مع الأخطاء بغض النظر عن مدى عمق تغليفها داخل الأخطاء الأخرى. تسمح الدالة errors.Is بالتحقق ما إذا كانت قيمة خطأ حارس معين موجودةً في أي مكان داخل خطأ مغلف؛ بينما تتيح الدالة errors.As الحصول على مرجع لنوع معين من الأخطاء من أي مكان داخل خطأ مغلّف. فحص قيمة خطأ باستخدام الدالة errors.Is يؤدي استخدام الدالة errors.Is للتحقق من وجود خطأ معين إلى جعل معالجة الخطأ الخاص errUhOh أقصر بكثير، لأنه يعالج جميع الأخطاء المتداخلة تلقائيًّا بدل إجرائها يدويًا من قبلنا. تأخذ الدالة معاملين كل منهما خطأ error، المعامل الأول هو الخطأ الذي تلقيناه والثاني هو الخطأ الذي نريد التحقق منه. لإزالة التعقيدات من عملية معالجة الخطأ errUhOh، سنفتح ملف "main.go"، ونحدّث عملية التحقق من errUhOh في دالة main لنستخدم الدالة errors.Is: ... func main() { for num := 1; num <= 3; num++ { fmt.Printf("validating %d... ", num) err := runValidation(num) if errors.Is(err, errUhOh) { fmt.Println("oh no!") } else if err != nil { fmt.Println("there was an error:", err) } else { fmt.Println("valid!") } } } لنُشغّل البرنامج الآن: $ go run main.go سنحصل على النتيجة التالية: validating 1... there was an error: run error: value error: that's odd validating 2... oh no! validating 3... valid! يظهر الخرج رسالة الخطأ !oh no، وهذا يعني أنه على الرغم من وجود عملية فحص خطأ واحدة من أجل errUhOh، سيظل بالإمكان فك سلسلة الأخطاء والوصول إلى الخطأ المُغلّف. تستفيد الدالة errors.Is من التابع Unwrap لمواصلة البحث العميق في سلسلة من الأخطاء، حتى تعثر على قيمة الخطأ المطلوبة أو خطأ حارس أو تُصادف تابع Unwrap يعيد قيمة nil. بعد إضافة دالة errors.Is في إصدار 1.13، أصبح استخدامها موصى به للتحقق من وجود أخطاء. تجدر الإشارة إلى أنه يمكن استخدام هذه الدالة مع قيم الخطأ الأخرى، مثل خطأ sql.ErrNoRows سالف الذكر. استرداد نوع الخطأ باستخدام errors.As الدالة الثانية التي سنتحدث عنها هي errors.As، والتي تُستخدم عندما نريد الحصول على مرجع لنوع معين من الأخطاء للتفاعل معه بتفصيل أكبر. مثلًا يتيح الخطأ المخصص ValueError الذي أضفناه سابقًا؛ الوصول إلى القيمة الفعلية التي يجري التحقق من صحتها في حقل Value الخاص بالخطأ، ولكن لا يمكن الوصول إليه إلا إذا كان لدينا مرجع لهذا الخطأ أولًا. هنا يأتي دور errors.As التي تأخذ معاملين، الأول خطأ والثانية متغير لنوع الخطأ، إذ يجري المرور على سلسلة الأخطاء لمعرفة ما إذا كان أي من الأخطاء المغلفة يتطابق مع النوع المُقدم، فإذا حصل تطابق مع أحدها فسيُضبط المتغير المُمرر لنوع الخطأ بالخطأ الذي عثر عليه errors.As، وستعيد الدالة true وإلا ستعيد false. إذًا، أصبح بإمكاننا باستخدام هذه الدلة الاستفادة من نوع ValueError لإظهار معلومات إضافية عن الخطأ في معالج الأخطاء. لنفتح ملف "main.go" للمرة الأخيرة ونحدّث الدالة main لإضافة حالة جديدة لمعالجة الأخطاء من نوع ValueError، بحيث تطبع قيمة الخطأ والرقم غير الصالح وخطأ التحقق: ... func main() { for num := 1; num <= 3; num++ { fmt.Printf("validating %d... ", num) err := runValidation(num) var valueErr *ValueError if errors.Is(err, errUhOh) { fmt.Println("oh no!") } else if errors.As(err, &valueErr) { fmt.Printf("value error (%d): %v\n", valueErr.Value, valueErr.Err) } else if err != nil { fmt.Println("there was an error:", err) } else { fmt.Println("valid!") } } } صرّحنا في الشيفرة السابقة عن متغير جديد اسمه valueErr واستخدمنا errors.As، للحصول على مرجع إلى ValueError في حال كان مغلفًا داخل قيمة err. بمجرد الوصول إلى الخطأ الخاص ValueError، سنتمكن من الوصول إلى أية حقول إضافية يوفرها هذا النوع، مثل القيمة الفعلية التي فشل التحقق منها. قد يكون هذا مفيدًا إذا كانت عملية التحقق عميقة في البرنامج ولم يكن لدينا إمكانية الوصول إلى القيم لمنح المستخدمين تلميحات حول مكان حدوث خطأ ما. مثال آخر على المكان الذي يمكن أن يكون مفيدًا فيه هو في حال كنا في صدد برمجة شبكة وواجهنا خطأ net.DNSError. يمكنك -من خلال الحصول على مرجع للخطأ- معرفة ما إذا كان الخطأ ناتجًا عن عدم القدرة على الاتصال، أو ما إذا كان الخطأ ناتجًا عن القدرة على الاتصال، ولكن لم يُعثر على المورد المطلوب، وهذا يمكّننا من التعامل مع الخطأ بطرق مختلفة. لرؤية كيف تجري الأمور مع الدالة errors.As دعونا نُشغّل البرنامج: $ go run main.go ستكون النتيجة على النحو التالي: validating 1... value error (1): that's odd validating 2... oh no! validating 3... valid! لن ترى رسالة الخطأ الافتراضي … :there was an error هذه المرة في الخرج، لأن جميع الأخطاء تُعالج بواسطة معالجات الأخطاء الأخرى. يظهر السطر اﻷول من الخرج الخاص بالتحقق من صحة القيمة 1 أن الدالة errors.As تُعيد true لأن رسالة الخطأ ... value error عُرضت. بما أن الدالة errors.As تُعيد true، يُضبط المتغير valueErr ليكون خطأ ValueError ويمكن استخدامه لطباعة القيمة التي فشلت في التحقق من الصحة من خلال الوصول إلى valueErr.Value (لاحظ كيف طُبعت القيمة 1 والتي فشلت في اختبار التحقق من الصحة). يظهر أيضًا السطر الثاني من الخرج، والذي يشير إلى اختبار التحقق من صحة الرقم 2، أنه على الرغم من تغليف errUhOh داخل غلاف ValueError، ما زالت رسالة الخطأ !oh no تظهر (بالرغم من وجود طبقتي تغليف)، وهذا لأن معالج الأخطاء الخاص الذي يستخدم الدالة errors.Is مع errUhOh تأتي أولًا في مجموعة تعليمات اختبار if لمعالجة الأخطاء. بما أن هذا المعالج يُعيد true قبل تنفيذ errors.As، يُنفّذ معالج !oh no. لو كانت الدالة errors.As تظهر قبل الدالة errors.Is في البرنامج لرأينا خرجًا مشابهًا لحالة القيمة 1، أي أن رسالة !oh no ستكون value error (2): uh oh. حدّثنا خلال هذا القسم البرنامج لنتمكن من استخدام errors.Is لإزالة الاستدعاءات الكثيرة والإضافية للدالة errors.Unwrap وجعل شيفرة معالجة الأخطاء أكثر متانة وسهولة للتعديل. استخدمنا أيضًا الدالة errors.As للتحقق ما إذا كان أي من الأخطاء المغلّفة هو ValueError، ثم استخدمنا حقولها في حال توافرها. الخاتمة تعرّفنا خلال هذا المقال على كيفية تغليف خطأ باستخدام العنصر النائب w% وكيفية فكه باستخدام الدالة errors.Unwrap. أنشأنا أيضًا نوع خطأ مخصص يدعم الدالة errors.Unwrap، واستخدمناه لاستكشاف دوال مُساعدة جديدة errors.Is و errors.As تُسهّل علينا عملية إرفاق معلومات أعمق وأكثر تفصيلًا عن الأخطاء التي نُنشئها، أو نتعامل معها وتضمن استمرارية عملية فحص الأخطاء حتى في حالة الأخطاء العميقة. ترجمة -وبتصرف- للمقال How to Add Extra Information to Errors in Go لصاحبه Kristin Davidson. اقرأ أيضًا المقال السابق كيفية تنفيذ عدة دوال عبر ميزة التساير Concurrency في لغة جو Go معالجة الأخطاء في لغة جو Go
  21. واحدة من أهم الميزات التي تدعمها لغة جو هي القدرة على إجراء أكثر من عملية في وقت واحد، وهذا ما يُسمى "بالتساير Concurrency"، وقد أصبحت فكرة تشغيل التعليمات البرمجية بطريقة متسايرة جزءًا مهمًا في تطبيقات الحاسب نظرًا لما تتيحه من استثمار أكبر للموارد الحاسوبية المتاحة والسرعة في إنهاء تنفيذ البرامج، فبدلًا من تنفيذ كتلة واحدة من التعليمات البرمجية في وقت واحد، يمكن تنفيذ عدة كتل من التعليمات البرمجية. مفهوم التساير هو فكرة يمكن دعم البرامج بها من خلال تصميم البرنامج بطريقة تسمح بتنفيذ أجزاء منه باستقلالية عن الأجزاء الأخرى. تمتلك لغة جو ميزتين، هما: خيوط معالجة جو Goroutines والقنوات Channels تُسهّلان إجراء عمليات التساير؛ إذ تُسهّل الأولى عملية إعداد الشيفرة المتسايرة للبرنامج، وتجعل الثانية عملية التواصل بين أجزاء البرنامج المتساير آمنة. سنتعرّف في هذا المقال على كل من خيوط معالجة جو والقنوات، إذ سننشئ برنامجًا يستخدم خيوط معالجة جو لتشغيل عدة دوال في وقت واحد، ثم سنضيف قنوات لتحقيق اتصال آمن بين أجزاء البرنامج المتساير. أخيرًا، سنضيف عدة خيوط معالجة (كل منها يُسمّى عامل Worker، إشارةً إلى أدائه مهمة ما)، لمحاكاة حالات أكثر تعقيدًا. الفرق بين التزامن Synchronous وعدم التزامن Asynchronous والتساير Concurrency والتوازي Parallelism التزامن وعدم التزامن هما نموذجان مختلفان للبرمجة، يشيران إلى أنماط البرمجة. تُكتب التعليمات البرمجية في النموذج الأول مثل خطوات، إذ تُنفّذ التعليمات البرمجية من الأعلى إلى الأسفل، خطوةً بخطوة، وتنتقل إلى الخطوة الثانية فقط عندما تنتهي من الخطوة الأولى، ويمكن هنا التنبؤ بالخرج: func step1() { print("1") } func step2() { print("2") } func main() { step1() step2() } // result -> 12 تُكتب التعليمات البرمجية في النموذج الثاني مثل مهمات، ويجري تنفيذها بعد ذلك على التساير. التنفيذ المتساير يعني أنه من المحتمل أن تُنفّذ جميع المهام في نفس الوقت، وهنا الخرج لايمكن التنبؤ به: func task1() { print("1") } func task2() { print("2") } func main() { task1() task2() } // result -> 12 or 21 في نموذج البرمجة غير المتزامن، يكون التعامل مع المهام على أنها خطوة واحدة تُشغّل مهام متعددة، ولا يؤخذ بالحسبان كيفية ترتيب هذه المهام. يمكن تشغيلها في وقت واحد أو في بعض الحالات، ستكون هناك بعض المهام التي تتُنفّذ أولًا ثم تتوقف مؤقتًا وتأتي مهام أخرى لتحل محلها بالتناوب وهكذا. يُطلق على هذا السلوك اسم متساير. لفهم الأمر أكثر تخيل أنّه طُلب منك تناول كعكة ضخمة وغناء أغنية، وستفوز إذا كنت أسرع من يغني الأغنية وينهي الكعكة. القاعدة هي أن تغني وتأكل في نفس الوقت، ويمكنك أن تأكل كامل الكعكة ثم تغني كامل الأغنية، أو أن تأكل نصف كعكة ثم تغني نصف أغنية ثم تفعل ذلك مرةً أخرى، إلخ. ينطبق الأمر نفسه على علوم الحاسب؛ فهناك مهمتان تُنفذان بصورةٍ متسايرة، ولكن تُنفذان على وحدة معالجة مركزية أحادية النواة، لذلك ستقرر وحدة المعالجة المركزية تشغيل مهمة أولًا ثم المهمة الأخرى، أو تشغيل نصف مهمة ونصف مهمة أخرى، إلخ. يجعلنا هذا التقسيم نشعر بأن جميع المهام تُُنفّذ بنفس الوقت. حسنًا تبقى لنا معرفة التنفيذ المتوازي. بالعودة إلى مثال الكعكة والأغنية تخيل أن يُسمح لك بالاستعانة بصديق، وبالتالي يمكن أن يغني بينما أنت تأكل. هذا يقابل أن يكون لدينا وحدة معالجة مركزية بنواتين، إذ يمكن تنفيذ المهمة -التي يمكن تقسيمها لمهمتين فرعيتين- على نواتين مختلفتين، وهذا ما يسمى بالتوازي، وهو نوع معين من التساير، إذ تُنفّذ المهام فعلًا في وقت واحد. لا يمكن تحقيق التوازي إلا في بيئات متعددة النواة. المتطلبات لتتابع هذه المقالة، يجب أن تستوفي الشروط التالية: إصدار مُثبّت من جو 1.13 أو أعلى، ويمكنك الاستعانة بمقال تثبيت لغة جو Go وإعداد بيئة برمجة محلية على أبونتو Ubuntu لإعداده. على درايةٍ بكيفية عمل واستخدام الدوال في لغة جو. يمكنك الاطلاع على مقالة كيفية تعريف واستدعاء الدوال في لغة جو Go. تشغيل عدة دوال في وقت واحد باستخدام خيوط معالجة جو Goroutines تُصمّم المعالجات الحديثة أو وحدة المعالجة المركزية CPU لأجهزة الحواسيب بحيث يمكنها تنفيذ أكبر عدد من المجاري التدفقية Streams من الشيفرة (أي كتل من التعليمات البرمجية) في نفس الوقت. لتحقيق هذه الإمكانية تتضمن المعالجات نواة أو عدة أنوية (يمكنك التفكير بكل نواة على أنها معالج أصغر) كل منها قادر على تنفيذ جزء من تعليمات البرنامج في نفس الوقت. بالتالي، يمكننا أن نستنتج أنّه يمكن الحصول على أداء أسرع بازدياد عدد الأنوية التي تُشغّل البرنامج. لكن مهلًا، لا يكفي وجود عدة أنوية لتحقيق الأمر؛ إذ يجب أن يدعم البرنامج إمكانية التنفيذ على نوى متعددة وإلا لن يُنفّذ البرنامج إلا على نواة واحدة في وقت واحد ولن نستفيد من خاصية النوى المتعددة والتنفيذ المتساير. للاستفادة من وجود النوى المتعددة، يقع على عاتق المبرمج تصميم شيفرة البرنامج، بحيث يمكن تقسيم هذه الشيفرة إلى كتل تُنفّذ باستقلالية عن بعضها. ليس تقسيم شيفرة البرنامج إلى عدة أجزاء أو كتل برمجية أمرًا سهلًا، فهو يمثّل تحدٍ للمبرمج. لحسن الحظ أن لغة جو تجعل الأمر أسهل من خلال الأداة goroutine أو "تنظيم جو". تنظيم جو هو نوع خاص من الدوال يمكنه العمل في نفس الوقت الذي تعمل به خيوط المعالجة الأخرى. نقول عن برنامج أنّه مُصمّم ليُنفّذ على التساير عندما يتضمن كتل من التعليمات البرمجية التي يمكن تنفيذها باستقلالية في وقت واحد. بالحالة العادية عندما تُستدعى دالة ما، ينتظر البرنامج انتهاء تنفيذ كامل الدالة قبل أن يكمل تنفيذ الشيفرة. يُعرف هذا النوع من التنفيذ باسم التشغيل في "المقدمة Foreground" لأنه يمنع البرنامج من تنفيذ أي شيء آخر قبل أن ينتهي. من خلال "تنظيم جو"، ستُستدعى الدالة وتنفّذ في "الخلفية Background" بينما يُكمل البرنامج تنفيذ باقي الشيفرة. نقول عن شيفرةً -أو جزء من شيفرة- أنها تُنفّذ في الخلفية عندما لا يمنع تنفيذها باقي أجزاء الشيفرة من التنفيذ، أي عندما لا تكون باقي أجزاء الشيفرة مُضطرة لانتظارها حتى تنتهي. تأتي قوة خيوط معالجة جو -وهي عبارة عن خيط معالجة lightweight thread يديره مشغل جو الآني Go runtime)- من فكرة أن كل تنظيم يمكنه أن يشغل نواة معالج واحدة في نفس الوقت. إذا كان لديك معالج بأربع نوى ويتضمن برنامجك 4 خيوط معالجة، فإن كل تنظيم يمكنه أن يُنفّذ على نواة في نفس الوقت. عندما تُنفّذ عدة أجزاء من الشيفرة البرمجيّة في نفس الوقت على نوى مختلفة (كما في الحالة السابقة) -نقول عن عملية التنفيذ أنها تفرعيّة (أو متوازية). يمكنك الاطلاع على مقال تنفيذ المهام بالتوازي في dot NET على أكاديمية حسوب لمزيدٍ من المعلومات حول تنفيذ المهام على التوازي. لتوضيح الفرق بين التساير concurrency والتوازي parallelism، ألقِ نظرةً على المخطط التالي. عندما يُنفّذ المعالج دالةً ما، فهو عادةً لا يُنفّذها من البداية للنهاية دفعةً واحدة، إذ يعمل نظام التشغيل أحيانًا على التبديل بين الدوال أو خيوط المعالجة أو البرامج الأخرى التي تُنفّذ على نواة المعالج عندما تنتظر الدالة حدوث شيءٍ ما (مثلًا قد يتطلب تنفيذ الدالة استقبال مُدخلات من المستخدم أو قراءة ملف). يعرض لنا المخطط كيف يمكن للبرنامج المصمّم للعمل على التساير التنفيّذ على نواة واحدة أو عدة نوى، كما يوضح أيضًا أنه من الملائم أكثر تنفيّذ خيوط معالجة جو على التوازي من التشغيل على نواة واحدة. يوّضح المخطط على اليسار والمُسمّى "تساير Concurrency" كيف يمكن تنفيذ برنامج متساير التصميم على نواة معالج وحيدة من خلال تشغيل أجزاء من goroutine1ثم دالة أو تنظيم أو برنامج ثم goroutine2 ثم goroutine1 مرةً أخرى وهكذا. هنا يشعر المستخدم بأن البرنامج ينفِّذ كل الدوال أو خيوط المعالجة في نفس الوقت، على الرغم من أنها تُنفّذ على التسلسل. يوضّح العمود الأيمن من المخطط والمسمّى "توازي Parallelism" كيف أن نفس البرنامج يمكن أن يُنفّذ على التوازي على معالج يملك نواتين، إذ يُظهر المخطط أن النواة الأولى تنفِّذ goroutine1 وهناك دوال أو خيوط معالجة أو برامج أخرى تُنفّذ معها أيضًا، نفس الأمر بالنسبة للنواة الثانية التي تنفِّذ goroutine1. نلاحظ أحيانًا أن goroutine1 و goroutine2 تُنفّذان في نفس الوقت لكن على نوى مختلفة. يظهر هذا المخطط أيضًا سمات أخرى من سمات جو القوية، وهي قابلية التوسع Scalability، إذ يكون البرنامج قابلًا للتوسع عندما يمكن تشغيله على أي شيء بدءًا من جهاز حاسوب صغير به عدد قليل من الأنوية وحتى خادم كبير به عشرات الأنوية والاستفادة من هذه الموارد الإضافية، أي كلما أعطيته موارد أكبر، يمكنه الاستفادة منها. يوضّح المخطط أنه من خلال استخدام خيوط معالجة جو، يمكن لبرنامج متساير أن يُنفّذ على نواة وحيدة، لكن مع زيادة عدد الأنوية سيكون البرنامج قابلًا للتنفيذ المتوازي، وبالتالي يمكن تنفيّذ أكثر من تنظيم في نفس الوقت وتسريع الأداء. للبدء بإنشاء برنامج متساير، علينا أولًا إنشاء مجلد "multifunc" في المكان الذي تختاره. قد يكون لديك مجلد مشاريع خاص بك، لكن هنا سنعتمد مجلدًا اسمه "projects". يمكنك إنشاء المجلد إما من خلال بيئة تطوير متكاملة IDE أو سطر الأوامر. إذا كنت تستخدم سطر الأوامر، فابدأ بإنشاء مجلد "projects" وانتقل إليه: $ mkdir projects $ cd projects من المجلد "projects" استخدم الأمر mkdir لإنشاء مجلد المشروع "multifunc" وانتقل إليه: $ mkdir multifunc $ cd multifunc افتح الآن ملف "main.go" باستخدام محرر نانو nano أو أي محرر آخر تريده: $ nano main.go ضع بداخله الشيفرة التالية: package main import ( "fmt" ) func generateNumbers(total int) { for idx := 1; idx <= total; idx++ { fmt.Printf("Generating number %d\n", idx) } } func printNumbers() { for idx := 1; idx <= 3; idx++ { fmt.Printf("Printing number %d\n", idx) } } func main() { printNumbers() generateNumbers(3) } يعرّف هذا البرنامج الأولي دالتين generateNumbers و printNumbers، إضافةً إلى الدالة الرئيسية main التي تُستدعى هذه الدوال ضمنها. تأخذ الدالة الأولى معامًلا يُدعى total يُمثّل عدد الأعداد المطلوب توليدها، وفي حالتنا مررنا القيمة 3 لهذه الدالة وبالتالي سنرى الأعداد من 1 إلى 3 على شاشة الخرج. لا تأخذ الدالة الثانية أيّة معاملات، فهي تطبع دومًا الأرقام من 0 إلى 3. بعد حفظ ملف main.go، يمكننا تشغيله باستخدام الأمر التالي: $ go run main.go سيكون الخرج كما يلي: Printing number 1 Printing number 2 Printing number 3 Generating number 1 Generating number 2 Generating number 3 نلاحظ أن الدالة printNumbers نُفّذت أولًا، ثم تلتها الدالة generateNumbers، وهذا منطقي لأنها استُدعيت أولًا. تخيل الآن أن كل من هاتين الدالتين تحتاج 3 ثوان حتى تُنفّذ. بالتالي عند تنفيذ البرنامج السابق بطريقة متزامنة synchronously (خطوةً خطوة)، سيتطلب الأمر 6 ثوانٍ للتنفيذ (3 ثوان لكل دالة). الآن، لو تأملنا قليلًا سنجد أن هاتين الدالتين مستقلتان عن بعضهما بعضًا، أي لا تعتمد أحدهما على نتيجة تنفيذ الأخرى، وبالتالي يمكننا الاستفادة من هذا الأمر والحصول على أداء أسرع للبرنامج من خلال تنفيذ الدوال بطريقة متسايرة باستخدام خيوط معالجة جو. نظريًّا: سنتمكن من تنفيذ البرنامج في نصف المدة (3 ثوان)، وذلك لأن كل من الدالتين تحتاج 3 ثوان لتُنفّذ، وبما أنهما سيُنفذان في نفس الوقت، بالتالي يُفترض أن ينتهي تنفيذ البرنامج خلال 3 ثوان. يختلف حقيقةً الجانب النظري هنا عن التجربة، فليس ضروريًا أن يكتمل التنفيذ خلال 3 ثوان، لأن هناك العديد من العوامل الأخرى الخارجية، مثل البرامج الأخرى التي تؤثر على زمن التنفيذ. إن تنفيذ دالة على التساير من خلال تنظيم جو مُشابه لتنفيّذ دالة بطريقة متزامنة. لتنفيذ دالة من خلال تنظيم جو (أي بطريقة متسايرة) يجب وضع الكلمة المفتاحية go قبل استدعاء الدالة. لجعل البرنامج يُنفّذ جميع خيوط المعالجة على التساير، يتعين علينا إضافة تعديل بسيط للبرنامج، بحيث نجعله ينتظر انتهاء تنفيذ كل خيوط معالجة جو. هذا ضروري لأنه إذا انتهى تنفيذ دالة main ولم تنتظر انتهاء خيوط معالجة جو، فقد لا يكتمل تنفيذ خيوط معالجة جو (تحدث مقاطعة لتنفيذها إن لم تكن قد انتهت). لتحقيق عملية الانتظار هذه نستخدم WaitGroup من حزمة sync التابعة لجو، والتي تتضمّن الأدوات الأولية للتزامن synchronization primitives، مثل WaitGroup المُصممة لتحقيق التزامن بين عدة أجزاء من البرنامج. ستكون مهمة التزامن في مثالنا هي تعقب اكتمال تنفيذ الدوال السابقة وانتظارها حتى تنتهي لكي يُسمح بإنهاء البرنامج. تعمل الأداة الأولية WaitGroup عن طريق حساب عدد الأشياء التي تحتاج إلى انتظارها باستخدام دوال Add و Done و Wait. تزيد الدالة Add العداد من خلال الرقم المُمرر إلى الدالة، بينما تنقص الدالة Done العداد بمقدار واحد. تنتظر الدالة Wait حتى تصبح قيمة العداد 0، وهذا يعني أن الدالة Done استُدعيت بما يكفي من المرات لتعويض استدعاءات Add. بمجرد وصول العداد إلى الصفر، ستعود دالة الانتظار وسيستمر البرنامج في العمل. لنعدّل ملف "main.go" لتنفيذ الدوال من خلال خيوط معالجة جو باستخدام الكلمة المفتاحية go، ولنضِف sync.WaitGroup إلى البرنامج: package main import ( "fmt" "sync" ) func generateNumbers(total int, wg *sync.WaitGroup) { defer wg.Done() for idx := 1; idx <= total; idx++ { fmt.Printf("Generating number %d\n", idx) } } func printNumbers(wg *sync.WaitGroup) { defer wg.Done() for idx := 1; idx <= 3; idx++ { fmt.Printf("Printing number %d\n", idx) } } func main() { var wg sync.WaitGroup wg.Add(2) go printNumbers(&wg) go generateNumbers(3, &wg) fmt.Println("Waiting for goroutines to finish...") wg.Wait() fmt.Println("Done!") } سنحتاج -بعد التصريح عن WaitGroup- إلى معرفة عدد الأشياء التي يجب انتظارها. سيدرّك wg عند وضع التعليمة (wg.Add(2 داخل الدالة main قبل بدء تنفيذ خيوط معالجة جو، أن عليه انتظار استدعائين Done حتى يُنهي عملية الانتظار. إذا لم نفعل ذلك قبل بدء تنفيذ خيوط معالجة جو، فمن المحتمل أن تحدث حالات تعطّل في البرنامج أو قد تحدث حالة هلع Panic في الشيفرة، لأن wg لا يعرف أنه يجب أن ينتظر أية استدعاءات Done. ستستخدم كل دالة defer بعد ذلك، لاستدعاء Done بهدف تخفيض العداد بواحد بعد انتهاء تنفيذ الدالة. تُحدَّث الدالة main أيضًا لتضمين استدعاء Wait من WaitGroup، لذا ستنتظر الدالة main حتى تُستدعى الدالة Done مرتين قبل إنهاء البرنامج. بعد حفظ ملف main.go، يمكننا تشغيله باستخدام الأمر التالي: $ go run main.go ويكون الخرج على النحو التالي: Printing number 1 Waiting for goroutines to finish... Generating number 1 Generating number 2 Generating number 3 Printing number 2 Printing number 3 Done! قد يختلف الخرج عما تراه أعلاه (تذكر أن التنفيذ المتساير لا يمكن التنبؤ بسلوكه)، وقد يختلف في كل مرة تُنفّذ فيها الشيفرة. سيعتمد الخرج الناتج عن تنفيذ الدالتين السابقتين على مقدار الوقت الذي يمنحه نظام التشغيل ومُصرّف لغة جو لكل دالة وهذا أمر يصعب معرفته؛ فمثلًا قد تُمنح كل دالة وقتًا كافيًا لتُنفّذ كل تعليماتها دفعةً واحدة قبل أن يقاطع نظام التشغيل تنفيذها، وبالتالي سيكون الخرج كما لو أن التنفيذ كان تسلسيًا، وفي بعض الأحيان لن تحصل الدالة على ما يكفي من الوقت دفعةً واحدة، أي تطبع سطر ثم يتوقف تنفيذها ثم تطبع الدالة الثانية سطر ثم يتوقف تنفيذها ثم نعود للدالة الأولى فتطبع باقي الأسطر وهكذا، وسنرى عندها خرجًا مشابهًا للخرج أعلاه. على سبيل التجربة، يمكننا حذف تعليمة استدعاء ()wg.Wait في الدالة الرئيسية main ثم تنفيذ الشيفرة عدة مرات باستخدام go run. اعتمادًا على جهاز الحاسب المُستخدم، قد ترى بعض النتائج من دالتي generateNumbers و printNumbers، ولكن من المحتمل أيضًا ألا ترى أي خرج منهما إطلاقًا، وذلك لأنه عند حذفنا لاستدعاء الدالة Wait، لن ينتظر البرنامج اكتمال تنفيذ الدالتين حالما ينتهي من تنفيذ باقي تعليماته؛ أي بدلًا من استخدام مبدأ "ضعهم في الخلفية وأكمل عملك وانتظرهم"، سيعتمد مبدأ "ضعهم في الخلفية وأكمل عملك وانساهم"، وبما أن الدالة الرئيسية تنتهي بعد وقت قصير من دالة Wait، فهناك فرصة جيدة لأن يصل برنامجك إلى نهاية الدالة main ويخرج قبل انتهاء تنفيذ خيوط معالجة جو. عند حصول ذلك قد ترى بعضًا من الأرقام تُطبع من خلال الدالتين، لكن غالبًا لن ترى الخرج المتوقع كاملًا. أنشأنا في هذا القسم برنامجًا يستخدم الكلمة المفتاحية go لتنفيذ دالتين على التساير وفقًا لمبدأ خيوط معالجة جو وطباعة سلسلة من الأرقام. استخدمنا أيضًا sync.WaitGroup لجعل البرنامج ينتظر هذه خيوط المعالجة حتى تنتهي قبل الخروج من البرنامج. ربما نلاحظ أن الدالتين generateNumbers و printNumbers لا تعيدان أية قيم، إذ يتعذر على خيوط معالجة جو إعادة قيم مثل الدوال العادية، على الرغم من أنه بمقدورنا استخدام الكلمة المفتاحية go مع الدوال التي تُعيد قيم، ولكن سيتجاهل المُصرّف هذه القيم ولن تتمكن من الوصول إليها. هذا يطرح تساؤلًا؛ ماذا نفعل إذا كنا بحاجة إلى تلك القيم المُعادة (مثلًا نريد نقل بيانات من تنظيم إلى تنظيم آخر)؟ يكمن الحل باستخدام قنوات جو التي أشرنا لها في بداية المقال، والتي تسمح لخيوط معالجة جو بالتواصل بطريقة آمنة. التواصل بين خيوط معالجة جو بأمان من خلال القنوات أحد أصعب أجزاء البرمجة المتسايرة هو الاتصال بأمان بين أجزاء البرنامج المختلفة التي تعمل في وقت واحد، فإذا لم تكن حذرًا قد تتعرض لمشاكل من نوع خاص أثناء التنفيذ. على سبيل المثال، يمكن أن يحدث ما يسمى سباق البيانات Data race عندما يجري تنفيذ جزأين من البرنامج على التساير، ويحاول أحدهما تحديث متغير بينما يحاول الجزء الآخر قراءته في نفس الوقت. عندما يحدث هذا، يمكن أن تحدث القراءة أو الكتابة بترتيب غير صحيح، مما يؤدي إلى استخدام أحد أجزاء البرنامج أو كليهما لقيمة خاطئة. مثلًا، كان من المفترض الكتابة ثم القراءة، لكن حدث العكس، وبالتالي نكون قد قرأنا قيمة خاطئة. يأتي اسم "سباق البيانات" من فكرة أن عاملين Worker يتسابقان للوصول إلى نفس المتغير أو المورد. على الرغم من أنه لا يزال من الممكن مواجهة مشكلات التساير مثل سباق البيانات في لغة جو، إلا أن تصميم اللغة يُسهّل تجنبها. إضافةً إلى خيوط معالجة جو، تُعد قنوات جو ميزةً أخرى تهدف إلى جعل التساير سهلًا وأكثر أمنًا للاستخدام. يمكن التفكير بالقناة على أنها أنبوب pipe بين تنظيمين أو أكثر، ويمكن للبيانات العبور خلالها. يضع أحد خيوط معالجة البيانات في أحد طرفي الأنبوب ليتلقاه تنظيم آخر في الطرف الآخر، وتجري معالجة الجزء الصعب المتمثل في التأكد من انتقال البيانات من واحد إلى آخر بأمان نيابةً عنّا. إنشاء قناة في جو مُشابه لإنشاء شريحة slice، باستخدام الدالة المُضمّنة ()make. يكون التصريح من خلال استخدام الكلمة المفتاحية chan متبوعةً بنوع البيانات التي تريد إرسالها عبر القناة؛ فمثلًا لإنشاء قناة تُرسل قيمًا من الأعداد الصحيحة، يجب أن تستخدم النوع chan int؛ وإذا أردت قناة لإرسال قيم بايت byte[]، سنكتب chan []byte: bytesChan := make(chan []byte) يمكنك -بمجرد إنشاء القناة- إرسال أو استقبال البيانات عبر القناة باستخدام العامل ->، إذ يحدد موضع العامل -> بالنسبة إلى متغير القناة ما إذا كنت تقرأ من القناة أو تكتب إليها؛ فللكتابة إلى قناة نبدأ بمتغير القناة متبوعًا بالعامل ->، ثم القيمة التي نريد كتابتها إلى القناة: intChan := make(chan int) intChan <- 10 لقراءة قيمة من قناة: نبدأ بالمتغير الذي نريد وضع القيمة فيه، ثم نضع إما = أو =: لإسناد قيمة إلى المتغير متبوعًا بالعامل ->، ثم القناة التي تريد القراءة منها: intChan := make(chan int) intVar := <- intChan للحفاظ على هاتين العمليتين في وضع سليم، تذكر أن السهم -> يشير دائمًا إلى اليسار (عكس <-)، ويشير السهم إلى حيث تتجه القيمة. في حالة الكتابة إلى قناة، يكون السهم منطلقًا من القيمة إلى القناة. أما عند القراءة من قناة، فيكون السهم من القناة إلى المتغير. على غرار الشريحة: يمكن قراءة القناة باستخدام الكلمة المفتاحية range في حلقة for. عند قراءة قناة باستخدام range، ستُقرأ القيمة التالية من القناة في كل تكرار للحلقة وتُوضع في متغير الحلقة، وستستمر القراءة من القناة حتى إغلاق القناة، أو الخروج من حلقة for بطريقة ما، مثل استخدام تعليمة break: intChan := make(chan int) for num := range intChan { // Use the value of num received from the channel if num < 1 { break } } قد نرغب أحيانًا في السماح لدالة فقط بالقراءة أو الكتابة من القناة وليس كلاهما. لأجل ذلك يمكننا أن نضيف العامل -> إلى تصريح القناة chan. يمكننا استخدام المعامل -> بطريقة مشابهة لعملية القراءة والكتابة من قناة للسماح فقط بالقراءة أو الكتابة أو القراءة والكتابة معًا. على سبيل المثال، لتعريف قناة للقراءة فقط لقيم int، سيكون التصريح chan int->: func readChannel(ch <-chan int) { // ch is read-only } لجعل القناة للكتابة فقط، سيكون التصريح chan<- int: func writeChannel(ch chan<- int) { // ch is write-only } لاحظ أن السهم يُشير إلى خارج القناة للقراءة ويشير إلى القناة من أجل الكتابة. إذا لم يكن للتصريح سهم، كما في حالة chan int، فهذا يعني أنه يمكن استخدام القناة للقراءة والكتابة. أخيرًا، عند انتهاء الحاجة من استخدام القناة، يمكن إغلاقها باستخدام الدالة ()close، وتًعد هذه الخطوة ضرورية، فقد يؤدي إنشاء القنوات وتركها دون استخدام عدة مرات في أحد البرامجما يُعرف باسم تسرب الذاكرة Memory leak، إذ يحدث تسرب للذاكرة عندما يُنشئ برنامج ما شيئًا ما يستخدم ذاكرة الحاسب، لكنه لا يحرّر تلك الذاكرة بعد الانتهاء من استخدامها، وهذا من شأنه أن يُبطئ تنفيذ البرنامج والحاسب عمومًا؛ فعند إنشاء قناة باستخدام ()make، يخصص نظام التشغيل جزءًا من ذاكرة الحاسب للقناة، وتحرّر هذه الذاكرة عند استدعاء الدالة ()close ليُتاح استخدامها من قبل شيء آخر. لنحدّث الآن ملف "main.go" لاستخدام قناة chan int للتواصل بين خيوط معالجة جو. ستنشئ الدالة generateNumbers أرقامًا وتكتبها على القناة بينما ستقرأ الدالة printNumbers هذه الأرقام من القناة وتطبعها على الشاشة. سننشئ في الدالة main قناةً جديدة لتمريرها مثل معامل لكل دالة من الدوال الأخرى، ثم تستخدم ()close على القناة لإغلاقها لعدم الحاجة لاستخدامها بعد ذلك. يجب ألا تكون دالة generateNumbers تنظيم جو بعد ذلك، لأنه بمجرد الانتهاء من تنفيذ هذه الدالة، سينتهي البرنامج من إنشاء جميع الأرقام التي يحتاجها. تُستدعى دالة ()close بهذه الطريقة على القناة فقط قبل انتهاء تشغيل كلتا الدالتين. package main import ( "fmt" "sync" ) func generateNumbers(total int, ch chan<- int, wg *sync.WaitGroup) { defer wg.Done() for idx := 1; idx <= total; idx++ { fmt.Printf("sending %d to channel\n", idx) ch <- idx } } func printNumbers(ch <-chan int, wg *sync.WaitGroup) { defer wg.Done() for num := range ch { fmt.Printf("read %d from channel\n", num) } } func main() { var wg sync.WaitGroup numberChan := make(chan int) wg.Add(2) go printNumbers(numberChan, &wg) generateNumbers(3, numberChan, &wg) close(numberChan) fmt.Println("Waiting for goroutines to finish...") wg.Wait() fmt.Println("Done!") } تستخدم أنواع chan في معاملات الدالتين generateNumbers و printNumbers أنواع القراءة فقط والكتابة فقط، إذ تحتاج الدالة generateNumbers إلى القدرة على الكتابة في القناة وبالتالي نجعل chan للكتابة فقط من خلال جعل السهم -> يشير إلى القناة، بينما printNumbers تحتاج للقراءة فقط من خلال جعل السهم -> يشير إلى خارج القناة. على الرغم من أنه كان بإمكاننا جعل الدوال تستطيع القراءة والكتابة وليس فقط واحدًا منهما من خلال استخدام chan int، إلا أنه من الأفضل تقييدهم وفقًا لما تحتاجه كل دالة، وذلك لتجنب التسبب بطريق الخطأ في توقف برنامجك عن العمل والوقوع في حالة الجمود أو التوقف التام deadlock. تحدث حالة الجمود عندما ينتظر ينتظر جزء A من البرنامج جزءًا آخر B لفعل شيء ما، لكن الجزء B هذا ينتظر أيضًأ الجزء A لفعل شيء ما. هنا أصبح كل منهما ينتظر الآخر وبالتالي فإن كلاهما سيبقى منتظرًا ولن ينتهي من التنفيذ، ولن يكتمل تنفيذ البرنامج أيضًا. قد يحدث الجمود بسبب الطريقة التي تعمل بها اتصالات القنوات في لغة جو، فعندما يكتب جزء من البرنامج إلى قناة، فإنه سينتظر حتى يقرأ جزءًا آخر من البرنامج من تلك القناة قبل المتابعة، وبالمثل، إذا كان البرنامج يقرأ من قناة، فإنه سينتظر حتى يكتب جزءًا آخر من البرنامج على تلك القناة قبل أن يستمر. عندما يكتب جزء A على القناة، سينتظر الجزء B أن يقرأ ما كتبه على القناة قبل أن يستمر في عمله. بطريقة مشابهة إذا كان الجزء A يقرأ من قناة، سينتظر حتى يكتب الجزء B قبل أن يستمر في عمله. يُقال أن جزءًا من البرنامج "محظور Blocking" عندما ينتظر حدوث شيء آخر، وذلك لأنه ممنوع من المتابعة حتى يحدث ذلك الشيء. تُحظر القنوات عند الكتابة إليها أو القراءة منها، لذلك إذا كانت لدينا دالة نتوقع منها أن تكتب إلى القناة ولكنها عن طريق الخطأ تقرأ من القناة، فقد يدخل البرنامج في حالة الجمود لأن القناة لن يُكتب فيها إطلاقًا. لضمان عدم حدوث ذلك نستخدم أسلوب القراءة فقط chan int-> أو الكتابة فقط chan<- int بدلًًا من القراءة والكتابة معًا chan int. أحد الجوانب المهمة الأخرى للشيفرة المحدّثة هو استخدام ()close لإغلاق القناة بمجرد الانتهاء من الكتابة عليها عن طريق generateNumbers، إذ يؤدي استدعاء الدالة ()close في البرنامج السابق إلى إنهاء حلقة for ... range في دالة printNumbers. نظرًا لأن استخدام range للقراءة من القناة يستمر حتى تُغلق القناة التي يُقرأ منها، بالتالي إذا لم تُستدعى close على numberChan فلن تنتهي printNumbers أبدًا، وفي هذه الحالة لن يُستدعى التابع Done الخاص بـ WaitGroup إطلاقًا من خلال defer عند الخروج من printNumbers، وإذا لم يُستدعى، لن ينتهي تنفيذ البرنامج، لأن التابع Wait الخاص بـ WaitGroup في الدالة main لن يستمر. هذا مثال آخر على حالة الجمود، لأن الدالة main تنتظر شيئًا لن يحدث أبدًا. نفّذ الآن ملف "main.go" باستخدام الأمر go run: $ go run main.go قد يختلف الخرج قليلًا عما هو معروض أدناه، ولكن يجب أن يكون متشابهًا: sending 1 to channel sending 2 to channel read 1 from channel read 2 from channel sending 3 to channel Waiting for functions to finish... read 3 from channel Done! يُظهر خرج البرنامج السابق أن الدالة generateNumbers تولّد الأرقام من واحد إلى ثلاثة أثناء كتابتها على القناة المشتركة مع printNumbers. حالما تستقبل printNumbers الرقم تطبعه على شاشة الخرج، وبعد أن تولّد generateNumbers الأرقام الثلاثة كلها، سيكون قد انتهى تنفيذها وستخرج، سامحةً بذلك للدالة main بإغلاق القناة والانتظار ريثما تنتهيprintNumbers. بمجرد أن تنتهي printNumbers من طباعة الأرقام، تستدعي Done الخاصة بـ WaitGroup وينتهي تنفيذ البرنامج. على غرار نتائج الخرج التي رأيناها سابقًا، سيعتمد الخرج الذي تراه على عوامل خارجية مختلفة، مثل عندما يختار نظام التشغيل أو مُصرّف لغة جو تشغيل تنظيم أو عامل معين قبل الآخر أو يبدّل بينهما، ولكن يجب أن يكون الخرج متشابهًا عمومًا. تتمثل فائدة تصميم البرامج باستخدام خيوط معالجة جو والقنوات في أنه بمجرد تصميم البرامج بطريقة تقبل التقسيم، يمكنك توسيع نطاقه ليشمل المزيد من خيوط معالجة جو. بما أن generateNumbers يكتب فقط على القناة، فلا يهم عدد الأشياء الأخرى التي تقرأ من تلك القناة، إذ سيرسل فقط أرقامًا إلى أي شيء يقرأ القناة. يمكنك الاستفادة من ذلك عن طريق تشغيل أكثر من دالة printNumbers مثل تنظيم جو، بحيث يقرأ كل منها من نفس القناة ويتعامل مع البيانات في نفس الوقت. الآن، بعد أن استخدم البرنامج قنوات للتواصل، نفتح ملف "main.go" مرةً أخرى لنُحدّث البرنامج بطريقة تمكننا من استخدام عدة دوال printNumbers على أنها خيوط معالجة جو. سنحتاج إلى تعديل استدعاء wg.Add، بحيث نضيف واحدًا لكل تنظيم نبدأه، ولا داعٍ لإضافة واحد إلىWaitGroup من أجل استدعاء generateNumbers بعد الآن، لأن البرنامج لن يستمر دون إنهاء تنفيذ كامل الدالة، على عكس ما كان يحدث عندما كنا ننفذه مثل تنظيم. للتأكد من أن هذه الطريقة لا تقلل من عدد WaitGroup عند انتهائها، يجب علينا إزالة سطر ()defer wg.Done من الدالة. تسهّل إضافة رقم التنظيم إلى printNumbers رؤية كيفية قراءة القناة بواسطة كل منهم. تُعد زيادة كمية الأرقام التي تُنشأ فكرةً جيدة أيضًا بحيث يسهل تتبعها: func generateNumbers(total int, ch chan<- int, wg *sync.WaitGroup) { for idx := 1; idx <= total; idx++ { fmt.Printf("sending %d to channel\n", idx) ch <- idx } } func printNumbers(idx int, ch <-chan int, wg *sync.WaitGroup) { defer wg.Done() for num := range ch { fmt.Printf("%d: read %d from channel\n", idx, num) } } func main() { var wg sync.WaitGroup numberChan := make(chan int) for idx := 1; idx <= 3; idx++ { wg.Add(1) go printNumbers(idx, numberChan, &wg) } generateNumbers(5, numberChan, &wg) close(numberChan) fmt.Println("Waiting for goroutines to finish...") wg.Wait() fmt.Println("Done!") } بعد تحديث ملف "main.go"، يمكنك تشغيل البرنامج مرةً أخرى باستخدام go run. ينبغي أن يبدأ البرنامج بإنشاء ثلاثة خيوط معالجة للدالة printNumbers قبل المتابعة وتوليد الأرقام، كما ينبغي أن يُنشئ البرنامج أيضًا خمسة أرقام بدلًا من ثلاثة لتسهيل رؤية الأرقام موزعة بين كل من خيوط المعالجة الثلاثة للدالة printNumbers: $ go run main.go قد يبدو الخرج مشابهًا لهذا (قد يختلف الخرج قليلًا): sending 1 to channel sending 2 to channel sending 3 to channel 3: read 2 from channel 1: read 1 from channel sending 4 to channel sending 5 to channel 3: read 4 from channel 1: read 5 from channel Waiting for goroutines to finish... 2: read 3 from channel Done! بالنظر إلى الخرج في هذه المرة، هناك احتمال كبير ليختلف الخرج عن الناتج الذي تراه أعلاه، لأنه هناك 3 خيوط معالجة من الدالة printNumbers تُنفّذ، وأيٌّ منها قد يقرأ أحد الأرقام المولّدة، وبالتالي هناك احتمالات خرج عديدة. عندما يتلقى أحد خيوط معالجة الدالة printNumbers رقمًا، فإنه يقضي وقتًا قصيرًا في طباعة هذا الرقم على الشاشة، وفي نفس الوقت يكون هناك تنظيم آخر يقرأ الرقم التالي من القناة ويفعل الشيء نفسه. عندما ينتهي تنظيم من قراءة الرقم الذي استقبله وطباعته على الشاشة، سيذهب للقناة مرةً أخرى ويحاول قراءة رقم آخر وطباعته، وإذا لم يجد رقمًا جديدًا، فسيبدأ الحظر حتى يمكن قراءة الرقم التالي. بمجرد أن تنتهي الدالة generateNumbers من التنفيذ وتستدعى الدالة ()close على القناة، ستغلِق كل خيوط معالجة الدالة printNumbers حلقاتها وتخرج، وعندما تخرج جميع خيوط المعالجة الثلاثة وتستدعي Done من WaitGroup، يصل عدّاد WaitGroup إلى الصفر وينتهي البرنامج. يمكنك أيضًا تجربة زيادة أو إنقاص عدد خيوط المعالجة أو الأرقام التي تُنشأ لمعرفة كيف يؤثر ذلك على الخرج. عند استخدام خيوط معالجة جو، تجنب أن تُكثر منها؛ فمن الناحية النظرية يمكن أن يحتوي البرنامج على مئات أو حتى الآلاف من خيوط المعالجة، وهذا ما قد يكون له تأثير عكسي على الأداء، فقد يُبطئ برنامجك والحاسب والوقوع في حالة مجاعة الموارد Resource Starvation. في كل مرة تُنفّذ فيها لغة جو تنظيمًا، يتطلب ذلك وقتًا إضافيًا لبدء التنفيذ من جديد، إضافةً إلى الوقت اللازم لتشغيل الشيفرة في الدالة التالية، وبالتالي من الممكن أن يستغرق الحاسب وقتًا أطول في عملية التبديل بين خيوط المعالجة مقارنةً تشغيل التنظيم نفسه، وهذا ما نسميه مجاعة الموارد، لأن البرنامج وخيوط المعالجة لا تأخذ الموارد الكافية للتنفيذ أو ربما لا تحصل على أية موارد، وفي هذه الحالة يكون من الأفضل تخفيض عدد خيوط المعالجة (أجزاء الشيفرة التي تعمل بالتساير) التي ينفذها البرنامج، لتخفيض عبء الوقت الإضافي المُستغرق في التبديل بينها، ومنح المزيد من الوقت لتشغيل البرنامج نفسه. يُفضّل غالبًا أن يكون عدد خيوط المعالجة مساوٍ لعدد النوى الموجودة في المعالج أو ضعفها. يتيح استخدام مزيج من خيوط معالجة جو والقنوات إمكانية إنشاء برامج قوية جدًا وقابلة للتوسع من أجل أجهزة حواسيب أكبر وأكبر. رأينا في هذا القسم أنه يمكن استخدام القنوات للتواصل بين عدد قليل من خيوط معالجة جو أو حتى آلاف دون الحاجة للكثير من التغييرات. إذا أخذنا هذا في الحسبان عند كتابة البرامج، سنتمكن من الاستفادة من التساير المتاح في لغة جو لتزويد المستخدمين بتجربة شاملة أفضل. الخاتمة أنشأنا في هذا المقال برنامجًا يطبع أرقامًا على الشاشة باستخدام الكلمة المفتاحية go وخيوط معالجة جو التي تتيح لنا التنفيذ المتساير. بمجرد تشغيل البرنامج أنشأنا قنواتًا جديدةً تمرِّر قيمًا صحيحة int عبرها باستخدام (make(chan int، ثم استخدمنا القناة من خلال إرسال أرقام من تنظيم جو إلى تنظيم جو آخر عبرها، ليطبعها الأخير على الشاشة بدوره. أخيرًا وسّعنا البرنامج من خلال إنشاء عدة خيوط معالجة تؤدي نفس المهمة (تستقبل أرقام من القناة وتطبعها)، وكان مثالًا على كيفية استخدام القنوات وخيوط المعالجة لتسريع البرامج على أجهزة الحواسيب متعددة النوى. ترجمة -وبتصرف- للمقال How To Run Multiple Functions Concurrently in Go لصاحبه Kristin Davidson. اقرأ أيضًا المقال السابق كيفية استخدام وحدة خاصة Private Module ضمن مشروعك بلغة Go. تعرف على لغة البرمجة Go.
  22. نشأ الذكاء الصناعي في الخمسينيات من القرن الماضي عندما بدأ بعض الباحثين في مجال علوم الحاسب بالتساؤل عما إذا كان من الممكن جعل أجهزة الحاسب "تفكر" -وهو سؤال ما زلنا نستكشف تداعياته حتى اليوم. إن التعريف المختصر لمجال الذكاء الصناعي على النحو التالي: هو الجهد المبذول لأتمتة المهام الفكرية التي يؤديها البشر عادة. تشكل خوارزميات الذكاء الصناعي أساس الأنظمة الذكية الحديثة، حيث تمكّن الآلات من التعلم والاستنتاج واتخاذ القرارات باستقلالية. أحدثت هذه الخوارزميات ثورة في مختلف المجالات، بدءًا من الرؤية الحاسوبية ومعالجة اللغة الطبيعية إلى الروبوتات وأنظمة التوصية. يعد فهم المفاهيم والأنواع الأساسية لخوارزميات الذكاء الصناعي أمرًا بالغ الأهمية لفهم تعقيدات الذكاء الصناعي وتطبيقاته الواسعة. لقد حققت خوارزميات الذكاء الصناعي في السنوات الأخيرة تقدمًا كبيرًا، وذلك بفضل توفر كميات هائلة من البيانات وزيادة القدرة الحسابية وتحقيق اختراقات في البحث الخوارزمي. تتمتع هذه الخوارزميات بالقدرة على معالجة مجموعات البيانات الضخمة وتحليلها واكتشاف الأنماط المخفية واستخلاص رؤى قيّمة من البيانات. لقد أصبحوا فعّالين في حل المشكلات المعقدة التي كانت تعتبر ذات يوم تتجاوز قدرات الآلات. يشمل مجال الذكاء الاصطناعي العديد من مناهج الخوارزميات، كل منها مصمم لمعالجة مهام ومجالات مشكلة محددة، ويمكن فرز أنواع هذه الخوارزميات بطرق عديدة: حسب طريقة التدريب أو نوع البيانات التي تتعامل معها أو حجم البيانات التي يمكن أن تستفيد منها، لكن التصنيف الأشيع يكون وفقًا للمهمة التي يمكن أن تؤديها. بينما توفر خوارزميات الذكاء الاصطناعي إمكانات هائلة، فإنها تطرح أيضًا تحديات واعتبارات أخلاقية. يجب معالجة قضايا مثل تحيز البيانات وقابلية التفسير والإنصاف والخصوصية لضمان النشر المسؤول والأخلاقي لأنظمة الذكاء الاصطناعي. علاوة على ذلك، يستمر مجال الذكاء الاصطناعي في التطور بسرعة، مع استمرار البحث والتقدم في تقنيات الخوارزميات. تعد مواكبة أحدث التطورات وفهم نقاط القوة والقيود الخاصة بالخوارزميات المختلفة أمرًا ضروريًا للممارسين والباحثين وصناع السياسات. سوف نستكشف في هذا المقال الأنواع الرئيسية لخوارزميات الذكاء الاصطناعي وفقًا للمهمة التي تؤديها ونناقش خصائصها وكيفية تطبيقها ونفحص التحديات التي تطرحها هذه الخوارزميات. من خلال اكتساب فهم شامل لخوارزميات الذكاء الاصطناعي، يمكننا تقدير أهميتها في قيادة الابتكار وحل المشكلات المعقدة وتشكيل مستقبل الذكاء الاصطناعي والحياة. تمهيد إلى خوارزميات الذكاء الاصطناعي لابد من التنويه لبعض النقاط المهمة لكي نضمن لك قراءةً مستنيرة خلال هذه المقالة. المتطلبات لتكون على بينةٍ أثناء قراءة هذه المقالة، لابد وأن يكون لديك: معرفة مسبقة بالذكاء الصناعي وفروعه وأساسياته، سيكون من الأفضل أن تقرأ المقالة الأولى في هذه السلسلة على الأقل. معرفة بمجالات الذكاء الصناعي. يمكنك قراءة مقالة مجالات الذكاء الاصطناعي. (اختياري) أن يكون لديك اطلاع على تعلم الآلة. يمكنك قراءة مقالة تعلم الآلة. هل يجب أن أعرف جميع أنواع خوارزميات الذكاء الاصطناعي؟ لكي تصبح مبرمجًا للذكاء الاصطناعي، ليس من الضروري معرفة جميع أنواع خوارزميات الذكاء الاصطناعي. مجال الذكاء الاصطناعي واسع ويشمل مجموعة واسعة من الخوارزميات والتقنيات. بصفتك مبرمجًا للذكاء الاصطناعي، يمكنك اختيار التخصص في مجالات أو خوارزميات محددة بناءً على اهتماماتك وأهدافك المهنية ومتطلبات المشاريع التي تعمل عليها. ومع ذلك فإن وجود فهم عام للمفاهيم الأساسية والخوارزميات شائعة الاستخدام في الذكاء الاصطناعي أمر مفيد. يتضمن ذلك معرفة خوارزميات التعلم الآلي مثل التعلم الخاضع للإشراف والتعلم غير الخاضع للإشراف والتعلم المعزز. إضافةً إلى ذلك يمكن أن يكون الإلمام بالشبكات العصبية وتقنيات التعلم العميق مفيدًا للغاية، حيث يتم استخدامها على نطاق واسع في تطبيقات الذكاء الاصطناعي المختلفة. خلال قراءتك للمقالة سترى مراجعة شاملة للمناهج والخوارزميات وسياق تطورها، لكن ليس من الضروري أن تتعرّف عن قرب عن جميعها أو حتى نصفها، فكما ذكرت؛ هذا يعتمد على المجال الذي ستختص فيه. مثلًا إن كان اهتمامك في معالجة اللغات الطبيعية أو الرؤية الحاسوبية، فلن تحتاج إلى الخوارزميات التطورية أو التعلم المعزز كقاعدة عامة. ما الفرق بين خوارزميتن الذكاء الاصطناعي ونموذج الذكاء الاصطناعي خوارزمية الذكاء الاصطناعي هي مجموعة التعليمات التي تحدد كيفية تنفيذ مهمة حسابية، بينما نموذج الذكاء الاصطناعي هو التمثيل المكتسب الناتج عن تطبيق الخوارزمية على بيانات التدريب. لتوضيح الفرق ضع في اعتبارك السيناريو التالي: تريد إنشاء نموذج لتصنيف صور القطط والكلاب. يمكن أن تكون الخوارزمية التي تختارها عبارة عن شبكة عصبية تلافيفية CNN، والتي تحدد بنية الشبكة وعملية التدريب عليها. النموذج في هذه الحالة سيكون CNN المُدرّبة أي بعد أن تعلّمت من مجموعة البيانات. يمكن بعد ذلك استخدام النموذج لتصنيف الصور الجديدة غير المرئية كقطط أو كلاب. باختصار النموذج هو ناتج الخوارزمية، أي يمكن القول أن النموذج هو خوارزمية مُدرّبة جاهزة للاستخدام والتطبيق الفعلي. تجدر الإشارة إلى أنهما مصطلحان يُستخدمان غالبًا بالتبادل، لكن يجب معرفة الفرق بينهما. ما المقصود بالأنماط تشير الأنماط في سياق الذكاء الاصطناعي إلى الهياكل أو العلاقات المتكررة في البيانات التي يمكن تحديدها واستخدامها لعمل تنبؤات أو استخلاص رؤى. من خلال تحليل كميات كبيرة من البيانات، يمكن لخوارزميات الذكاء الاصطناعي تحديد الأنماط والتعلم منها لتحسين الأداء أو اتخاذ قرارات ذكية. يمكن أن تظهر الأنماط في أشكال مختلفة، مثل السلاسل الرقمية أو السلاسل النص أو الصور أو الإشارات الصوتية. صُمّمت خوارزميات الذكاء الاصطناعي للتعرف على الأنماط داخل أنواع البيانات هذه واستخراج الخصائص ذات الصلة التي تساعد في حل مهام محددة. على سبيل المثال، في التعرف على الصور، تتعلم خوارزميات الذكاء الاصطناعي أنماطًا من قيم البكسل تتوافق مع كائنات أو ميزات مختلفة. في معالجة اللغة الطبيعية، تتعلم نماذج الذكاء الاصطناعي أنماطًا في تراكيب اللغة والسياق لفهم وإنشاء نص يشبه الإنسان. باختصار، النمط هو شكل معيّن تتكرر فيه المعلومات، وهي صفات وخصائص تُميّز الأشياء، وهو مصطلح أساسي في تعلّم الآلة. مثلًا: كل التفاحات لها شكل شبه كروي (نمط)، العرب ينطقون لغة خاصة هي اللغة العربية (نمط)، يمكن تمييز الطبيب في المستشفى من لباسه (نمط). تطور خوارزميات الذكاء الاصطناعي: من الأنظمة المعتمدة على القواعد إلى أعجوبة التعلم العميق شهدت خوارزميات الذكاء الصناعي تطورًا ملحوظًا على مر السنين، مما دفع مجال الذكاء الصناعي إلى آفاق جديدة للابتكار والتطبيق. من البدايات المتواضعة إلى أحدث التطورات، تُظهر رحلة خوارزميات الذكاء الصناعي التقدم الملحوظ الذي تم إحرازه في تطوير الأنظمة الذكية. يستكشف هذا القسم من المقالة تطور خوارزميات الذكاء الصناعي وتتبع أسسها والمعالم الرئيسية والاتجاهات الناشئة التي تشكل مستقبل الذكاء الصناعي. الأيام الأولى: الذكاء الاصطناعي المعتمد على القواعد والأنظمة الخبيرة يمكن إرجاع جذور خوارزميات الذكاء الاصطناعي إلى الأيام الأولى من المجال عندما ركز الباحثون على الذكاء الصناعي المعتمد على القواعد والأنظمة الخبيرة. تضمنت هذه الأساليب تمثيل المعرفة البشرية في أنظمة قائمة على القواعد، وتمكين أجهزة الحاسب من محاكاة التفكير البشري وحل مشكلات محددة ضمن مجالات محددة مسبقًا. كان التطور الملحوظ خلال هذه الفترة هو إنشاء أنظمة خبيرة يمكن أن تحاكي قدرات صنع القرار للخبراء البشريين في المجالات المتخصصة. ثورة التعلم الآلي: الانتقال من القواعد الصريحة إلى التعلم من البيانات كان ظهور التعلم الآلي بمثابة تحول كبير في خوارزميات الذكاء الاصطناعي، والانتقال من البرمجة الصريحة القائمة على القواعد إلى الخوارزميات التي تتعلم من البيانات. اكتسبت المناهج الإحصائية بدايةً مكانة بارزة، مما سمح للآلات بالتعرف على الأنماط وإجراء التنبؤات بناءً على الأمثلة المرصودة. شهد لاحقًا هذا العصر ظهور خوارزميات مثل الانحدار الخطي والانحدار اللوجستي وأشجار القرار، مما مكّن الآلات من التعلم من البيانات المصنفة واتخاذ قرارات مستنيرة. كما أدى تطوير الشبكات العصبية وخوارزمية الانتشار العكسي في هذه الفترة إلى دفع المجال بشكل أكبر من خلال تقديم مفهوم تدريب الشبكات العميقة. التعلم المعزز: التعلم من خلال التفاعل مع البيئة اكتسبت خوارزميات التعلم المعزز مكانة بارزة من خلال تمكين الآلات من التعلم من خلال التفاعلات مع البيئة، وهي مستوحاة من علم النفس السلوكي. تعمل هذه الخوارزميات على تحسين عمليات اتخاذ القرار من خلال تعظيم المكافآت والعقوبات وبشكل تراكمي. تشمل المعالم الرئيسية لهذه المرحلة تطوير خوارزمية Q-Learning وتمهيد الطريق للتعلم المعزز الحديث والتطورات اللاحقة مثل شبكات كيو العميقة DQN وغيرهم. حقق التعلم المعزز نجاحًا ملحوظًا في مجالات مثل لعب الألعاب والروبوتات، حيث أُظهرت قوة التعلّم من خلال التجربة والخطأ. الخوارزميات التطورية: التحسين والبحث تستمد الخوارزميات التطورية الإلهام من التطور الطبيعي والمبادئ الجينية لتحسين المشكلات المعقدة والبحث عن الحلول المثلى. تعد الخوارزميات الجينية وخوارزميات أسراب الطيور وخوازميات مستعمرات النمل -أمثلة على الخوارزميات التطورية التي أثبتت فعاليتها في حل بعض مشكلات التحسين المعقدة. وجدت هذه الخوارزميات تطبيقات في مجالات مثل التصميم الهندسي والجدولة واستخراج البيانات. التعلم العميق: إطلاق العنان لقوة الشبكات العصبية ظهرت خوارزميات التعلم العميق كقوة مهيمنة في الذكاء الصناعي، تغذيها التطورات في القوة الحسابية وتوافر كميات هائلة من البيانات. من خلال الاستفادة من فكرة الطبقات المتعددة للشبكات العصبية، تفوقت خوارزميات التعلم العميق على خوارزميات التعلّم الآلي الأخرى في جميع المهام الحسية والإدراكية. أحدثت الشبكات العصبية التلافيفية CNNs ثورة في مهام الرؤية الحاسوبية، بينما أظهرت الشبكات العصبية المتكررة RNNs أداءً استثنائيًا في تحليل البيانات النصية والزمنية. يتيح عمق وتعقيد هذه الشبكات استخراج معلومات عالية الدقة من البيانات، وإطلاق العنان لإمكانيات جديدة في مجالات مثل التعرف على الصور ومعالجة اللغة الطبيعية والتعرّف على الكلام. ظهور النهج الهجين مع استمرار تطور خوارزميات الذكاء الاصطناعي، اكتسبت الأساليب الهجينة التي تجمع بين تقنيات متعددة مكانة بارزة. مثلًا أدى دمج التعلم العميق مع التعلم المعزز إلى حدوث تطورات في مجالات مثل القيادة المستقلة ولعب الألعاب. أتاح اندماج الخوارزميات التطورية مع تقنيات التعلم الآلي إنشاء استراتيجيات تحسين أكثر قوة. تستفيد المناهج الهجينة من نقاط القوة في النماذج الحسابية المختلفة لمواجهة تحديات العالم الحقيقي المعقدة. يعد تطوّر خوارزميات الذكاء الصناعي عملية مستمرة، مع العديد من الاتجاهات الناشئة التي تشكل مستقبل المجال. دورة الذكاء الاصطناعي احترف برمجة الذكاء الاصطناعي AI وتحليل البيانات وتعلم كافة المعلومات التي تحتاجها لبناء نماذج ذكاء اصطناعي متخصصة. اشترك الآن أنواع خوارزميات الذكاء الاصطناعي وفقًا لنواع المهمة هناك عدة أنواع من خوارزميات الذكاء الصناعي وفقًا لنوع المهمة، وهي تندرج عمومّا تحت 6 أنواع هي: الانحدار والتصنيف والعنقدة والتجميع والتوليد والتفاعل. خوارزميات الانحدار أو التوقع Regression يُعد هذا النوع من الخوارزميات من خوارزميات تعلم الآلة الخاضعة للإشراف، وتستخدم لعمل تنبؤات. يتضمن التطبيق الرئيسي لخوارزميات الانحدار التنبؤ بسعر سوق الأسهم والتنبؤ بالطقس، وما إلى ذلك. هناك أنواع مختلفة من الانحدار مثل الانحدار الخطي والانحدار متعدد الحدود وما إلى ذلك. الخوارزميات الأكثر شيوعًا في هذا القسم هي خوارزمية الانحدار الخطي Linear Regression. تُستخدم خوارزمية الانحدار الخطي لتحليل العلاقة بين متغيرين: متغير إدخال (غالبًا ما يسمى المتغير المستقل) ومتغير الإخراج (غالبًا ما يسمى المتغير التابع). يهدف إلى العثور على علاقة خطية أو اتجاه يناسب نقاط البيانات بشكل أفضل. تعمل الخوارزمية من خلال إيجاد معادلة مستقيم تساهم في تقليل الفرق بين القيم المتوقعة والقيم الحقيقية لمتغير الإخراج. يمكن استخدام هذه المعادلة لاحقًا لعمل تنبؤات لقيم الإدخال الجديدة بناءً على العلاقة التي تم تعلمها. غالبًا ما يستخدم الانحدار الخطي في مهام مثل التنبؤ بأسعار المنازل بناءً على عوامل مثل الحجم والموقع ، أو تقدير المبيعات بناءً على نفقات الإعلان ، أو تحليل تأثير المتغيرات على نتيجة معينة. يوفر طريقة بسيطة وقابلة للتفسير لفهم العلاقة بين المتغيرات وعمل تنبؤات بناءً على تلك العلاقة. خوارزميات التصنيف Classification خوارزميات تُستخدم لتصنيف البيانات إلى فئتين أو أكثر، وتعتبر خوارزميات التصنيف جزءًا من التعلم الخاضع للإشراف. على سبيل المثال، يمكن استخدام خوارزميات التصنيف لتصنيف رسائل البريد الإلكتروني كرسائل عادية أو عشوائية Spam. هناك نوعان أساسيان من التصنيف: تصنيف الثنائي Binary classification: هنا يكون لدينا فئتين. مثلًا في مهمة تصنيف المرضى المصابين بكوفيد 19، يجب على الخوارزمية أن تحدد فيما إذا كان الشخص مريضًا أو غير مريض. تصنيف متعدد الفئات Multiclass classification: هنا يكون لدينا أكثر من فئة. مثلًا في مهمة تصنيف مراجعات الأفلام. يجب على الخوارزمية أن تحدد فيما إذا كان الشخص معجبًا بالفيلم أو غير معجب أو محايد. من أشهر خوارزميات التصنيف خوارزمية الانحدار اللوجستي Logistic regression والغابات العشوائية Random Forest و SVM إضافةً إلى الشبكات العصبية والتعلم العميق. غالبًا ما يستخدم الانحدار اللوجستي لمشاكل التصنيف الثنائي، بينما تُعرف الغابات العشوائية و SVM بقدرتهما على التعامل مع مهام التصنيف الثنائية ومتعددة الفئات. اكتسبت الشبكات العصبية، بما في ذلك نماذج التعلم العميق، شعبية هائلة بسبب قدرتها على تعلم الأنماط المعقدة في البيانات، مما يجعلها مناسبة لمجموعة واسعة من مشاكل التصنيف. توفر هذه الخوارزميات أدوات قوية للباحثين والممارسين لتحليل البيانات وتصنيفها، مما يتيح التقدم في مجالات مثل التعرف على الصور ومعالجة اللغة الطبيعية والتشخيص الطبي ..إلخ. خوارزميات العنقدة Clustering تُستخدم لتجميع العناصر المتشابهة بغية فرزها أو تصنيفها أو اختزالها. تعتمد هذه الخوارزميات على تقسيم البيانات إلى مجموعات مميزة من العناصر المتشابهة. خذ مثلًا عملية تحميل الصور على أحد مواقع التواصل الاجتماعي كمثال. هنا قد يرغب الموقع في تجميع الصور التي تُظهر الشخص نفسه مع بعضها بغية تنظيم صورك. إلا أن الموقع لا يعرف من يظهر في الصور ولا يعرف عدد الأشخاص المختلفين الذين يظهرون في مجموعة الصور خاصتك. تتمثل الطريقة المعقولة لحل المشكلة في استخراج كل الوجوه وتقسيمها إلى مجموعات من الوجوه المتشابهة، ومن أجل كل صورة جديدة تحملها يضعها في المجموعة الأكثر شبهًا لها. هذا مايُسمّى بالعنقدة. أبرز أمثلتها هي الخوارزمية التصنيفية K-Means. خوارزميات التعلم الجماعي Ensemble learning يشير التعلم الجماعي إلى الخوارزميات التي تجمع التنبؤات من نموذجين أو أكثر. على الرغم من وجود عدد غير محدود تقريبًا من الطرق التي يمكن من خلالها تحقيق ذلك، ربما توجد ثلاث فئات من تقنيات التعلم الجماعي التي تتم مناقشتها بشكل شائع واستخدامها في الممارسة العملية: التعبئة Bagging: يعتمد على استخدام عدة نماذج لإنشاء التوقعات، ثم أخذ قرار الغالبية. أي مثلًا كان هناك 3 نماذج تتوقع أن الشخص مريض ونموذج واحد يتوقع أنه غير مريض، فتكون النتيجة أنه غير مريض بتصويت الغالبية. التكديس Stacking: هنا يُستخدم نموذج آخر لمعرفة أفضل طريقة للجمع بين تنبؤات النماذج. التعزيز Boosting: هنا يكون لدينا عدة نماذج وكل منها يُصحح أخطاء الآخر على التسلسل، وصولًا إلى نموذج نهائي قوي (هذا هو النوع الأنجح والأكثر استخدامًا). أبرز الأمثلة على هذه الخوارزميات هي خوارزمية تعزيز التدرج الشديد XGBoost، وتعتبر من خوارزميات التعلم الآلي المستخدمة في مهام التصنيف أو الانحدار، وتندرج تحت مفهوم أساليب التعلم الجماعي التي تجمع بين العديد من النماذج الأضعف (غالبًا أشجار القرار) لإنشاء نموذج تنبؤي قوي. تعزيز التدرج هو مصطلح عام يشير إلى فئة من الخوارزميات حيث يركز كل نموذج لاحق في المجموعة على تصحيح الأخطاء التي ارتكبتها النماذج السابقة. إنه يعمل عن طريق إضافة نماذج جديدة باستمرار، مع تدريب كل نموذج جديد لتقليل الأخطاء التي ارتكبتها النماذج السابقة. الخوارزميات التوليدية Generative هي فئة من خوارزميات الذكاء الاصطناعي التي تهدف إلى إنشاء عينات بيانات جديدة تشبه مجموعة بيانات تدريب معينة. تتعلم هذه النماذج التوزيع الأساسي لبيانات التدريب وتستخدمها لتوليد عينات جديدة تظهر خصائص متشابهة. تُستخدم النماذج التوليدية على نطاق واسع في العديد من التطبيقات، بما في ذلك إنشاء الصور وتوليد النصوص (مثل ChatGPT) وتوليف البيانات. فيما يلي بعض الأنواع الشائعة من النماذج التوليدية: شبكات الخصومة التوليدية GANs: تتكون شبكات GAN من مكونين رئيسيين: شبكة المولدات وشبكة التمييز. يهدف المولد إلى إنتاج عينات بيانات واقعيّة، بينما يحاول المميّز التمييز بين العينات الحقيقية والمولدة، ومن خلال عملية التدريب تتعلم شبكات GAN إنشاء عينات بيانات واقعية. أحد أمثلتها هو توليد صور أشخاص غير حقيقيين. النماذج التوليدية لمعالجة اللغة الطبيعية: يمكن لنماذج مثل شبكات الخصومة التوليدية والشبكات العصبية المتكررة RNN ونماذج المحولات Transformers إنشاء نصوص بيانات واقعية (مثل ChatGPT). تحتوي النماذج التوليدية على مجموعة واسعة من التطبيقات، بما في ذلك تركيب الصور وزيادة حجم مجموعات البيانات وإنشاء النصوص واكتشاف الشذوذ. إنها ذات قيمة في السيناريوهات التي يكون الهدف فيها هو إنشاء عينات بيانات جديدة تلتقط خصائص بيانات التدريب وتوزيعها. خوارزميات التفاعل أو التعلم المعزز RL خوارزميات التعلم المعزز هي فئة من خوارزميات الذكاء الاصطناعي التي تمكّن الوكيل Agents (يمكنك اعتباره الآلة) من تعلم كيفية اتخاذ القرارات أو اتخاذ الإجراءات في بيئة ما لزيادة المكافأة التراكمية. تتعلم خوارزميات RL من خلال التجربة والخطأ، حيث يتفاعل الوكيل مع البيئة ويتلقى تعليقات في شكل مكافآت أو عقوبات بناءًا على صحة الإجراء المُتخذ أو عدم صحته، ويقوم بتعديل سلوكه لتحقيق أعلى مكافأة ممكنة. هناك العديد من المكونات الرئيسية في خوارزميات RL. أولاً، هناك وكيل يتخذ إجراءات في البيئة. هدف الوكيل هو تعلم "سياسة Policy"، وهي رسم خرائط بين الحالات التي يمكن أن يمر بها الوكيل في البيئة والإجراءات التي يمكن أن يقوم بها عند كل حالة، والتي تزيد من المكافأة المتوقعة على المدى الطويل. البيئة هي النظام الخارجي الذي يتفاعل معه الوكيل، والتي تُقدّم ملاحظات للوكيل من خلال المكافآت. تستخدم خوارزميات RL عادةً دالة قيمة أو دالة Q لتقدير المكافأة التراكمية المتوقعة لزوج معين من إجراءات الحالة (مثلا إذا كنت في الحالة S5 واتخذت الإجراء A1 ستحصل على مكافأة 10+). تمثل دالة القيمة المكافأة طويلة الأجل التي يمكن أن يتوقعها الوكيل من كونه في حالة معينة واتباع سياسة معينة. تقدر دالة Q المكافأة التراكمية المتوقعة على وجه التحديد لأزواج (إجراء، حالة). تستخدم خوارزميات RL تقديرات القيمة هذه لتوجيه عملية اتخاذ القرار لدى الوكيل. هناك خوارزميات RL مختلفة مثل Q-Learning وتدرجات السياسة Policy gradients ..إلخ، ولكل منها نهجها الخاص في التعلم والتحسين. تم تطبيق خوارزميات التعلم المعزز بنجاح في مجالات مختلفة، مثل الروبوتات والأنظمة المستقلة ولعب الألعاب وأنظمة التحكم. إنها تمكن الوكلاء من التعلم من التجربة والتكيف مع البيئات المتغيرة واكتشاف الاستراتيجيات المثلى لحل المشكلات المعقدة. تطبيق خوارزميات الذكاء الاصطناعي في الذكاء الصناعي عمومًا ومجال تعلم الآلة خصوصًا، يختلف نهج الخوارزمية عن الخوارزمية التقليدية. السبب هو أن الخطوة الأولى هي معالجة البيانات -وبعد ذلك، سيبدأ الحاسوب في التعلّم. أي عند الحديث عن الخوارزميات في سياق الذكاء الصناعي وتعلم الآلة نتحدث عن الإجراءات والعمليات التي تُطبّق على البيانات لاستكشاف وفهم الأنماط Patterns والعلاقات السائدة في البيانات والتعلّم منها، ويكون نتاج هذه الخوارزميات هو نماذج Models يمكن تطبيقها على أرض الواقع. ملاحظة 1: النموذج هو ناتج الخوارزمية، أي يمكن القول أن النموذج هو خوارزمية مُدرّبة جاهزة للاستخدام والتطبيق الفعلي. تجدر الإشارة إلى أنهما مصطلحان يُستخدمان غالبًا بالتبادل (لافرق في أن تقول نموذج أو خوارزمية)، لكن وجب التنويه للفرق بينهما. ملاحظة 2: النمط هو شكل معيّن تتكرر فيه المعلومات، وهي صفات وخصائص تُميّز الأشياء، وهو مصطلح أساسي في تعلّم الآلة. مثلًا: كل التفاحات لها شكل شبه كروي (نمط)، العرب ينطقون لغة خاصة هي اللغة العربية (نمط)، يمكن تمييز الطبيب في المستشفى من لباسه (نمط). من السهل بناء وتطبيق بعض الخوارزميات، إلا أن بعضها الآخر يتطلّب خطوات برمجية ورياضيات معقدة. الخبر السار هو أنه لا يتعين عليك في معظم الأوقات أن تبني هذه الخوارزميات من الصفر، لأن هناك مجموعة متنوعة من اللغات البرمجية مثل Python و R والعديد من أطر العمل كتنسرفلو Tensorflow وكيراس Keras وباي تورش Pytorch وسكايت ليرن Sklearn التي تجعل العملية سهلة ومباشرة. هناك المئات من خوارزميات تعلم الآلة المتاحة، إلا أنّه يمكن تقسيمها فعليًا إلى أربع فئات رئيسية: التعلم الخاضع للإشراف supervised learning، والتعلم غير الخاضع للإشراف unsupervised learning، والتعلم المعزز reinforcement learning، والتعلم شبه الخاضع للإشراف semi-supervised learning، وقد ألقينا نظرة عن هذه الأنواع في مقالة تعلم الآلة. خطوات تدريب خوارزميات الذكاء الاصطناعي إن الهدف من عملية تدريب الخوارزمية هو إنشاء نموذج يعتمد على تلك الخوارزمية لإعطاء قرارات في أرض الواقع. بالتالي لتحقيق النجاح في تطبيق خوارزميات الذكاء الصناعي على مشكلة ما، من المهم اتباع نهج منظّم، وإلا قد تكون النتائج بعيدة عن المتوقع. إذًا يجب أن نختار خوارزمية مناسبة وأن ندربها بأفضل شكل ممكن لكي نحصل على قرارات مثالية قدر الإمكان. بدايةً تحتاج إلى إجراء عمليات معالجة البيانات، ثم من الجيد (إن أمكن) إجراء تمثيل مرئي لهذه البيانات، حيث يفيدنا التمثيل المرئي في الإجابة على أسئلة مثل: هل هناك بعض الأنماط؟ إذا كانت الإجابة بنعم، فقد تكون البيانات سهلة التعلّم وواضحة للخوارزمية. الخطوة 1: تحديد المشكلة وجمع البيانات: بدايةً يجب أن تحدد المشكلة التي تعمل عليها، وهذا يتضمن الإجابة على أسئلة مثل: ماذا ستكون البيانات التي تعطيها للخوارزمية (أي ما هو الدخل)؟ ما هي المشكلة التي تحاول حلها (أي ماهو الخرج)؟ لا يمكنك الانتقال إلى المرحلة التالية حتى تعرف ما هي المدخلات والمخرجات، والبيانات التي ستستخدمها. تتضمن هذه المرحلة أيضًا فحص البيانات التي تم جمعها وتنظيفها وإصلاح أية مشاكل فيها. الخطوة 2: اختيار الخوارزمية المناسبة: تحتاج الآن إلى تحديد خوارزمية مناسبة لحل هذه المشكلة، وسيكون الأمر أشبه بالتخمين المستنير، أي يجب أن تستفيد من النظريات وتجارب الآخرين، وهذا سيتضمن عملية تجربة وخطأ (قد تجرب عدة خورازميات). الخطوة 3: تحضير البيانات للتدريب: بمجرد أن تعرف ما الذي تريد أن تُدرّب الخوارزمية عليه، تكون جاهزًا تقريبًا لبدء نماذج التدريب. إن تحضير البيانات يعني تنسيقها بطريقة يمكن إدخالها في خوارزمية التعلّم. الخطوة 4: تدريب النموذج: سيتم استخدام حوالي 70% من البيانات المتاحة من أجل تدريب الخوارزمية وإنتاج النموذج. افترض أنك تبني نظامًا للتعلم الآلي للتنبؤ بقيمة السيارة المستعملة. ستشمل البيانات عوامل مثل: سنة التصنيع والطراز والمسافة المقطوعة والحالة. من خلال معالجة بيانات التدريب هذه، ستحسب الخوارزمية أوزان كل من هذه العوامل (وزن تأثيرها في قيمة السيارة) لتعطي نموذجًا يتوقع القيمة بناءًا على هذه العوامل. الخطوة 5: تقييم أداء النموذج: سيتم استخدام حوالي 30% من البيانات المتاحة من أجل تقييم أداء النموذج. في هذه المرحلة يمكنك معرفة ما إذا كان النموذج دقيقًا في قراراته. في مثالنا على السيارات المستعملة، سيكون السؤال: هل تتوافق توقعات النموذج مع قيم السوق في أرض الواقع؟ الخطوة 6: تحسين أداء النموذج: من خلال إضافة بيانات جديدة أو التلاعب بالخوارزمية ومعاملاتها أو حتى تغيير الخورازمية. في القسم التالي سنتحدث عن أنواع خوارزميات الذكاء الاصطناعي. هذه الأنواع تتضمن خوارزميات مختلفة وكلها تندرج تحت الفئات الرئيسية الأربعة سالفة الذكر. خوارزميات الذكاء الاصطناعي والمجتمع تسلط هذه الفقرة الضوء على الأهمية المتزايدة للاعتبارات الأخلاقية والتفسير والإنصاف في خوارزميات الذكاء الصناعي، حيث يستمر تأثير الذكاء الاصطناعي على المجتمع في التوسع. دعونا نتعمق في كل جانب! الاعتبارات الأخلاقية تشمل الاعتبارات الأخلاقية في الذكاء الصناعي معالجة الآثار الأخلاقية والتأثير المجتمعي المحتمل لأنظمة الذكاء الصناعي. نظرًا لأن خوارزميات الذكاء الاصطناعي أصبحت أكثر تكاملًا في جوانب مختلفة من حياتنا، فمن المهم ضمان تطويرها ونشرها بطريقة أخلاقية. يتضمن ذلك اعتبارات مثل حماية الخصوصية وأمن البيانات والتحيّز الخوارزمي والشفافية والمساءلة. تظهر المخاوف الأخلاقية عندما تتخذ خوارزميات الذكاء الصناعي قرارات قد يكون لها عواقب وخيمة على الأفراد أو المجموعات، ومن المهم وضع مبادئ توجيهية وأطر لتقليل الضرر المحتمل وضمان ممارسات الذكاء الاصطناعي الأخلاقية. قابلية التفسير يشير التفسير إلى القدرة على فهم وشرح القرارات أو التنبؤات التي تتخذها خوارزميات الذكاء الصناعي. نظرًا لأن الذكاء الصناعي يصبح أكثر تعقيدًا مع خوارزميات مثل شبكات التعلم العميق، يصبح تفسير النتائج التي تعطيها أصعب. من الضروري تطوير أساليب وتقنيات تمكّن البشر من فهم الأسباب الكامنة وراء قرارات خوارزميات الذكاء الاصطناعي من أجل الثقة بها. يساعد التفسير في تحديد التحيزات المحتملة وضمان المساءلة وبناء ثقة المستخدم. بالإضافة إلى ذلك تسمح القابلية للتفسير لخبراء المجال بالتحقق من صحة نماذج الذكاء الاصطناعي وتحسينها، مما يجعلها أكثر موثوقية وفعالية. الإنصاف يشير الإنصاف في خوارزميات الذكاء الصناعي إلى المعاملة غير المنحازة للأفراد أو المجموعات، بغض النظر عن سماتهم الشخصية مثل الجنس أو العرق أو الحالة الاجتماعية والاقتصادية. يمكن أن يظهر التحيّز عن غير قصد في أنظمة الذكاء الاصطناعي بسبب بيانات التدريب المتحيزة أو الافتراضات الأساسية. يعد تعزيز العدالة في خوارزميات الذكاء الاصطناعي أمرًا بالغ الأهمية لتجنب استمرار عدم المساواة الاجتماعية وضمان تكافؤ الفرص للجميع. تنبع الأهمية المتزايدة لهذه الجوانب من الاعتراف بأن أنظمة الذكاء الاصطناعي لديها القدرة على التأثير بشكل كبير على الأفراد والمجتمعات والمجتمعات ككل. تتم معالجة هذه الاعتبارات بنشاط من قبل الباحثين وصانعي السياسات والمتخصصين في الصناعة لوضع مبادئ توجيهية أخلاقية وتطوير مقاييس الإنصاف وتعزيز الشفافية في خوارزميات الذكاء الاصطناعي. من خلال دمج هذه الاعتبارات في عمليات التطوير والنشر، يمكننا أن نسعى جاهدين من أجل بناء بيئة للذكاء الاصطناعي أكثر أخلاقية وشمولية، ليعود بالنفع على المجتمع ككل. خاتمة خوارزميات الذكاء الاصطناعي مجال واسع يتكون من خوارزميات التعلم الآلي وخوارزميات التعلم العميق وغيرها من الخورازميات. تمنح خوارزميات الذكاء الصناعي الآلات القدرة على اتخاذ القرارات والتعرّف على الأنماط واستخلاص استنتاجات مفيدة والتي تعني أساسًا تقليد الذكاء البشري. في المقالة السابقة ناقشنا تطوّر هذه الخوارزميات منذ ولادة هذا المجال وكيفية تصنيف هذه الخوارزميات إلى 6 فئات. هي: الانحدار والتصنيف والعنقدة والتجميع والتوليد والتفاعل. تعرّفنا على كيفية تطبيق هذه الخوارزميات والخطوات العامة لذلك. لضمان النشر المسؤول والمفيد للذكاء الاصطناعي، تحدثنا عن ضرورة مراعاة الآثار الأخلاقية وضمان القابلية للتفسير من أجل الشفافية والمساءلة والسعي لتحقيق العدالة لتجنب التمييز والتحيز. إن تطوّر خوارزميات الذكاء الصناعي مثير للاهتمام ومحفّز للتفكير. لقد كان لها نصيب من الإخفاقات والاختراقات الهائلة. مع تطبيقات مثل ChatGPT و Dalle.E وغيرهما، نكون قد خدشنا سطح التطبيقات الممكنة للذكاء الصناعي. هناك تحديات أيضًا، وهناك بالتأكيد المزيد في المستقبل. اقرأ أيضًا أنواع الذكاء الاصطناعي الذكاء الاصطناعي: مراحل البدء والتطور والأسس التي نشأ عليها تطبيقات الذكاء الاصطناعي أهمية الذكاء الاصطناعي المرجع الشامل إلى تعلم الخوارزميات للمبتدئين
  23. بدت تقنية الذكاء الصناعي قبل عقد من الزمان وكأنها شيء منبثق من قصص الخيال العلمي ليس إلا، لكن مع تطور العتاد Hardware وتوافر كميات هائلة من البيانات على الإنترنت وجهود آلاف الباحثين عبر العالم، بدأت تتحوّل تلك القصص شيئًا فشيئًا إلى واقع نعيشه. لقد أصبحنا نستخدم تقنيات الذكاء الصناعي في حياتنا اليومية بدرايةٍ منا أو بدون دراية. لقد أصبح جزءًا روتينيًّا في أعمالنا أو تصفحنا على الإنترنت، وميزة هامة لأي موقع الإلكتروني. إحصائيات حول مستقبل الذكاء الاصطناعي في مقالةٍ نشرها بيل غيتس مؤخرًا على مدونته الخاصة بعنوان "لقد بدأ عصر الذكاء الاصطناعي"، أعرب عن مدى تعجّبه من التطوّر الذي وصل إليه الذكاء الاصطناعي في الأعوام القليلة الماضية، واصفًا إياه بالثوري، وبأنه ثاني أكبر مفاجأة تكنولوجيا رآها في حياته بعد واجهة المستخدم الرسومية. وبناءًا على ذلك توقع أن مستقبل الذكاء الصناعي سيحمل العديد من المفاجآت والتأثيرات والتغيرات في السنوات الخمس إلى العشر القادمة. وفقًا لرؤيته، فإن الذكاء الاصطناعي سيقدّم طرقًا جديدة للتعامل مع المشكلات البيئية (مثل تغير المناخ)، وسيغيّر الطريقة التي يعمل ويتعلّم بها الناس، وسيحسّن من الرعاية الصحية ويساعد في الشفاء من العديد من الأمراض، وأن صناعات بأكملها ستتجه إلى إدخال تقنيات الذكاء الصناعي في أعمالها، وسيكون سبيلًا لتحقيق التقدّم والتميّز والمنافسة بين الشركات. لكن كان هناك جانب مظلم أيضًا، ففي مقال آخر، تحدّث بيل غيتس عن إمكانية أن تكون التكنولوجيا حدثًا سيئًا، فربما تُحدث بعض المشاكل مثل البطالة الجماعية نتيجة استحواذه على جميع وظائفنا، أو ربما يقرر أن "البشر يشكلون تهديدًا" وينقلب علينا. لهذا السبب، حث وغيره من روّاد التكنولوجيا على طرق للتحكم في الذكاء الاصطناعي. أفكار بيل غيتس ليست مجرد كلام على الهامش بالتأكيد، فقد أعرب العديد من رواد أعمال بارزين في مجال التكنولوجيا مثل إيلون ماسك وعلماء مثل هوكينج مرارًا وتكرارًا عن قلقهم العميق بشأن الذكاء الاصطناعي. على الجانب الآخر كان هناك العديد من رواد الأعمال الذين ينظرون لهذه القضية بإيجابية، ماسايوشي سون، الرئيس التنفيذي لشركة SoftBank ومدير صندوق Vision هو واحد منهم. في مقابلة مع CNBC، أعلن أنه في غضون 30 عامًا، سيكون لدينا سيارات طائرة وسيعيش الناس لفترة أطول وسنكون قد عالجنا العديد من الأمراض. أشار أيضًا إلى أن التركيز الرئيسي لصندوقه ينصب على الذكاء الصناعي. حسنًا، من منهم على حق؟ من منهم نصدّق؟ هل سيكون المستقبل بائسًا أم رائعًا أم سيكون بينهما؟ هل هو تقنية عظيمة أم أنه تهديد للإنسان؟ سنناقش في هذه المقالة مستقبل الذكاء الصناعي وتأثيره على حياة الإنسان من عدة جوانب، لكن دعونا نتفق على أن التنبؤ بالتقنيات الجديدة أمر صعب للغاية، ويكاد يكون مستحيلاً. لأثبت لكم ذلك، ها هنا بعض التوقعات العلمية من شخصيات بارزة كانت خاطئة: توقع توماس أديسون أن التيار المتناوب سيفشل. في كتاب "طريق المستقبل The Road Ahead" لبيل غيتس 1995، لم يُشير إلى الإنترنت! في عام 2007، قال الرئيس التنفيذي المشارك لمجموعة "البحث في الحركة Research in Motion" جيم بالسيلي ومبتكر جهاز BlackBerry، إن آيفون iPhone لن يكون ناجحًا. دعونا نبدأ مع بعض الإحصائيات المتنوعة عن مستقبل الذكاء الاصطناعي التي تكشف لنا جدية الأمر: أرباح شركة إنفيديا Nvidia اليوم بتاريخ 26 أيار 2023، (واحدة من أكبر الشركات إنتاجًا لمعالجات الرسومات وبطاقات العرض المرئي GPUs) عادلت كامل قيمة شركتي أنتل و كوالكوم (شركات عالمية منافسة لها). ارتفاع أسهم شركة انفيديا اليوم هو أكبر ارتفاع سجّلته وول ستريت Wall street بتاريخها. أحداث صادمة شهدها اليوم سوق الأسهم الأميركية مع ارتفاع أسهم شركة إنفيديا بقيمة 24% لتصبح الشركة أول شركة رقاقات قيمتها السوقية قريبة من 1 ترليون دولار. يعود سبب ذلك إلى أن المعالجات الرسومية التي تُنتجها هذه الشركة وخصوصًا معالج H100 هو أقوى معالج موجود حاليًا في الأسواق مُستخدم لتدريب النماذج اللغوية الضخمة مثل GPT. وفقًا للإحصاءات أيضًا، وصل سوق الذكاء الصناعي إلى مايقارب 86 مليار دولار في 2022، ومن المتوقع أن يزداد مُعدّل النمو بنسبة 36% عن 2022 بحلول عام 2027، مع عوائد تُقدّر بحوالي 400 مليار دولار. من المتوقع أن يعزز الذكاء الاصطناعي السوق الصينية بنسبة 26.1٪ والولايات المتحدة بنسبة 14.5٪ بحلول عام 2030. حوالي 44 بالمئة من الشركات العالمية تهتم بالاستثمارات الجادة في الذكاء الصناعي ودمجه في أعمالها. من بين 9130 براءة اختراع تلقتها شركة IBM في عام 2021، كان هناك 2300 براءة اختراع متعلقة بالذكاء الصناعي. ما هو نوع الذكاء الاصطناعي في وقتنا الحاضر؟ دعونا نفهم ما هو الذكاء الاصطناعي وفي أي مرحلة هو في الوقت الحاضر، وذلك قبل التعمق في موضوع مستقبله. يمكننا تعريف الذكاء الصناعي على أنه "قدرة الآلات التي يتحكم بها الحاسوب على أداء مهام ذكية." إذًا، الذكاء الصناعي هو أحد علوم الحاسوب، والذي يهدف إلى تطوير آلات ذكية يمكنها محاكاة السلوك البشري. في مقالة سابقة بعنوان أنواع الذكاء الاصطناعي ذكرنا أن الذكاء الصناعي يتألف من عدة أنواع، وهي (وفقًا للقدرات): ذكاء اصطناعي ضعيف Weak AI، ذكاء اصطناعي قوي Strong AI، ذكاء اصطناعي خارق Super AI. يُعتبر الذكاء الذكاء الصناعي في وقتنا الحاضر على أنه ذكاء ضعيف، أي يمكنه فقط أداء مهام مخصّصة على سبيل المثال، السيارات ذاتية القيادة والتعرف على الكلام وما إلى ذلك. ما هو مستقبل الذكاء الاصطناعي؟ يرتبط مستقبل الذكاء الصناعي بمستقبل البشرية ويؤثر في كل صناعة تقريبًا ويدخل في كل مفاصل الحياة، إنّه المحرك الرئيسي للتقنيات الناشئة، مثل البيانات الضخمة Big Data والروبوتات Robotics وإنترنت الأشياء IoT، ولا ننسَ التقنيات التوليديّة (أي القادرة على توليد الصور أو الكلام ..إلخ)، مع أدوات مثل ChatGPT ومولدات الذكاء الصناعي الفنية AI Art Generators التي تحظى بضجة كبيرة الآن، والتي ربما ستستمر بالتطور حتى تُعتمد كأدوات تقنية وفنية وإنشائية في المستقبل المنظور. دعونا نلقي نظرة على بعض المجالات التي من المحتمل أن يكون لها تأثير كبير على المجتمع البشري. الذكاء الاصطناعي والسيارات ذاتية القيادة إنها واحدة من أهم المجالات المستقبلية. لقد رأيناها كثيرًا في قصص الخيال العلمي، لكن هناك ابتكارات حقيقية، مثل عربة ستانفورد Stanford Cart في أوائل الستينات، حيث كانت تستخدم الكاميرات والذكاء الاصطناعي في الملاحة، وعلى الرغم من أنه كان إنجازًا بارزًا؛ إلا أنّه لم يكن عمليًّا، حيث تطلب الأمر أكثر من 10 دقائق للتخطيط لأي خطوة! نقطة الانعطاف الأساسيّة بالنسبة للسيارات ذاتية القيادة جاءت في عام 2004. وكان المحفز الرئيسي هو الحروب التي كانت تؤدي إلى خسائر فادحة بالجنود. آمنت وكالة داربا DARPA بأن السيارات ذاتية القيادة هي حل للحد من هكذا خسائر، فأقامت مسابقة سنوية أُطلق عليها اسم DARPA Grand Challenge، في عام 2004، مع جائزة بقيمة مليون دولار لتشجيع الابتكار على نطاق واسع، وعلى الرغم من أن أداء السيارات في أول نسخة كان سيئًا، لكن في عام 2007، كانت السيارات متقدمة جدًا. هناك الكثير من الالتباس حول ما تعنيه كلمة "ذاتية القيادة". هل هو قيادة السيارة نفسها بمفردها أو يجب أن يكون هناك سائق بشري؟ هناك 6 مستويات من الاستقلالية: المستوى 0: يتحكم فيه الإنسان بكل السيارة. المستوى 1: تتحكم الحواسيب بشيء واحد في وقت واحد (مثل السرعة أو الكبح). المستوى 2: أتمتة وظيفتين في وقت واحد. المستوى 3: أتمتة جميع وظائف السلامة، لكن يمكن للسائق التدخل إذا حدث خطأ ما. المستوى 4: يمكن للسيارة أن تقود بنفسها، ولكن هناك حالات يجب أن يشارك فيها الإنسان. المستوى 5: السيارة ذاتية القيادة تمامًا. تعد صناعة السيارات واحدة من أكبر الأسواق، ومن المرجح أن يؤدي الذكاء الصناعي إلى تغييرات مُدوّية. وفقًا لدراسة من Allied Market Research، من المتوقع أن يصل سوق هذه السيارات إلى 556.67 مليار دولار بحلول عام 2026، وهو ما سيمثل معدل نمو سنوي مركب بنسبة 39.47٪. استخدام الذكاء الصناعي في الطب واكتشاف الأدوية يمتلك برنامج الدردشة الآلي الجديد للذكاء الاصطناعي ChatGPT القدرة على تغيير مستقبل التشخيص الطبي، وذلك وفقًا لمقالة رأي نُشرت في 13 فبراير في STAT بالاشتراك مع أندرو بيم Andrew Beam، الأستاذ المساعد في علم الأوبئة بجامعة هارفارد. لقد أجرى بيم وفريقه اختبارات على العديد من أدوات فحص الأعراض Symptom Checker (أداة تشخيص للأمراض) المنتشرة على مواقع الويب وتطبيقات الهواتف الذكية، ووجدوا أن تلك الأدوات كانت غير دقيقة في تقديم التشخيص مقارنة بالأطباء، ولكن عندما اختبر الباحثون لاحقًا قدرة ChatGPT على تشخيص الأمراض لاحظوا أن الذكاء الاصطناعي كان دقيقًا للغاية ويضاهي دقة الأطباء. من جهةٍ أخرى، لقد كان التقدم في اكتشاف الأدوية معجزة تقريبًا، حيث يوجد الآن علاجات لأمراض مستعصية مثل التهاب الكبد C، مع تقدّم ملحوظ في علاج عدد لا يحصى من السرطانات. لكن هناك الكثير من التحديات، فشركات الأدوية تواجه المزيد من المشاكل في التوصل إلى العلاجات. إليك مثالًا واحدًا فقط: في 2019، أعلنت شركة Biogen أن أحد عقاقيرها لمرض الزهايمر، والذي كان في تجارب المرحلة الثالثة، فشل في إظهار نتائج ذات فائدة. نتيجةً لذلك، تراجعت أسهم الشركة بنسبة 29٪، مما أدى إلى القضاء على 18 مليار دولار من القيمة السوقية. إن تطوير الأدوية بالأسلوب التقليدي يتضمن الكثير التجربة والخطأ، مما قد يستغرق وقتًا طويلًا، لذا يتطلع الباحثون والشركات إلى مستقبل الذكاء الاصطناعي للحصول على المساعدة. بعض من هذه الشركات هي Insitro التي استثمرت 100 مليون دولار، وشركة Alexandria Venture Investments وشركة Bezos Expeditions التابعة لجيف بيزوس وشركة Mubadala Investment Company ...إلخ. يعد الذكاء الاصطناعي في تسريع عملية اكتشاف الأدوية، لأن خوارزمياته قادرة على اكتشاف وفهم الأنماط المعقدة، كما يمكن أن يكون مفيدًا أيضًا في تطوير علاجات مخصصة -الموجهة للتركيب الجيني للفرد- والتي من المحتمل أن تكون حاسمة في علاج بعض الأمراض. الذكاء الاصطناعي وخصوصية المستخدمين أثار مقال من موقع Bloomberg في أبريل 2019 ضجة كبيرة. ألقى المقال نظرة من وراء الكواليس حول كيفية إدارة أمازون Amazon لنظام AI الخاص بالمساعد الصوتي Alexa، فإلى جانب اعتماده على الخوارزميات، هناك أيضًا الآلاف من الأشخاص الذين يعملون خلف الكواليس على تحليل المقاطع الصوتية للمساعدة في جعل النتائج أفضل (أي نوع من التنصت على كلامك مع المساعد الصوتي)، وذلك للتركيز على التعامل مع الفروق الدقيقة في اللهجات العامية والإقليمية، والتي كانت صعبة بالنسبة لخوارزميات الذكاء الاصطناعي. سارع موقع أمازون إلى الإشارة إلى أن لديه قواعد ومتطلبات صارمة، ولكن هذا أثار المزيد من القلق! انطلاقًا من ذلك، ومن العديد من حالات انتهاء خصوصية المستخدمين الأخرى، بدأت بعض الحكومات تفرض على الشركات قوانين جديدة. على سبيل المثال أعلن أعضاء الكونجرس الأمريكي برعاية مشروع قانون يسمى "The Algorithmic Accountability Act"، والذي يتضمن العديد من المبادئ والقوانين على الشركات التي تُنتج أنظمة الذكاء الصناعي، مثل أنه لا يمكن استخدام التكنولوجيا كذريعة أو طريق لتجنب الامتثال للقانون. الفائدة العامة للمجتمع: الصالح الاجتماعي كتبت شركة الاستشارات الإدارية McKinsey&Co دراسة مستفيضة بعنوان "تطبيق الذكاء الاصطناعي من أجل الصالح الاجتماعي". يوضح كيف يمكن استخدام الذكاء الصناعي للتعامل مع قضايا مثل الفقر والكوارث الطبيعية وتحسين التعليم. الدراسة لديها ما يقرب من 160 حالة استخدام. إذن ، إليك نظرة على بعض منها فقط: يمكن أن يساعد تحليل منصات وسائل التواصل الاجتماعي في تتبع تفشي المرض. أنشأ باحثون شبكة عصبية دُرّبت على مقاطع فيديو للصيادين في إفريقيا (نموذج). يُستخدم هذا النموذج ضمن طائرة بدون طيار تُحلّق فوق مناطق معينة لاكتشاف المخالفين. استُخدم الذكاء الصناعي لتحليل بيانات 55893 طردًا في مدينة فلينت الأمريكية للعثور على دليل على التسمم بالرصاص. الذكاء الاصطناعي والثورة الصناعية الرابعة Industry 4.0 الثورة الصناعية الرابعة هي العصر الصناعي الرابع منذ الثورة الصناعية الأولى في القرن الثامن عشر. تتميّز بدمج التقنيات التي تطمس الخطوط الفاصلة بين المجالات المادية والرقمية والبيولوجية. يأتي دور الذكاء الصناعي هنا في قدرته على إحداث هذه التغييرات بسرعة وسلاسة، ولاسيما من خلال الأنظمة الخبيرة ودعم الرؤية الحاسوبية والروبوتات وإنترنت الأشياء. يمكن تنظيم تأثير الذكاء الاصطناعي على التصنيع في 5 مجالات رئيسية: التنبؤ بالجودة والعائد. الصيانة الوقائية (التنبؤ بأعطال قد تحدث). التعاون بين الإنسان والروبوت في أداء العديد من المهام: وفقًا للاتحاد الدولي للروبوتات (IFR)، اعتبارًا من عام 2020، كان هناك حوالي 1.64 مليون روبوت صناعي في جميع أنحاء العالم. المساعدة في ابتكار التصاميم. التسويق التكيفي/سلسلة التوريد: استخدام خوارزميات الذكاء الصناعي لتحسين سلسلة التوريد في عمليات التصنيع ومساعدتها على الاستجابة بشكل أفضل للتغيرات في السوق وتوقعها. مستقبل الذكاء الاصطناعي في التعليم مع تقدّم التكنولوجيا في السنوات الأخيرة، كان هناك توجّه نحو دمج هذه التقنيات والممارسات الحديثة في التعليم من أجل تحسين التجربة التعليمية الشاملة. تُعد أنظمة إدارة التعلم والتلعيب gamification والتعلم بمساعدة الفيديو والواقع الافتراضي والواقع المعزز -بعضًا من الأمثلة على كيفية تحسين التكنولوجيا لمشاركة الطلاب والتخطيط التعليمي. فلنتحدث عن مستقبل الذكاء الاصطناعي في التعليم. في يومنا هذا تُستخدم تقنيات الذكاء الصناعي في التعليم بطرق مختلفة، بدءًا من روبوتات الدردشة التي توفر دعمًا للطلاب على مدار الساعة طوال أيام الأسبوع وحتى خوارزميات التعلم المخصصة التي تتكيف مع احتياجات كل طالب. مثلًا يمكن استخدام روبوت المحادثة الشهير ChatGPT في التعليم لإنشاء نظام تعليمي ذكي يمكنه فهم استفسارات الطلاب والرد عليها. تُستخدم الأدوات التي تعمل بالذكاء الصناعي أيضًا لأتمتة المهام الإدارية، مثل تصنيف المهام وتقديم الملاحظات. يُستخدم الذكاء الاصطناعي أيضًا لتحليل كميات كبيرة من البيانات لتحديد الأنماط والرؤى التي يمكن أن تساعد في تطوير استراتيجيات وسياسات تعليمية جديدة. هناك العديد من الأمثلة على الأدوات والأنظمة التعليمية الناجحة التي تعمل بالذكاء الاصطناعي قيد الاستخدام حاليًا. تشمل بعض أشهرها ما يلي: دولينجو Duolingo: تطبيق محمول وموقع إلكتروني أمريكي خاص بتعلم اللغات. يستخدم الذكاء الصناعي لتخصيص الدروس لكل مستخدم. ألكس ALEKS: برنامج تعليمي وتقييم عبر الإنترنت يتضمن مواد تدريبية في الرياضيات والكيمياء والإحصاءات التمهيدية والأعمال التجارية. ألكس مدعوم بالذكاء الاصطناعي الذي يوفّر تقييمات قابلة للتكيف وخطط تعلم مخصصة. ملاحظة: يُقصد "بالتلعيب" Gamification أو اللعبنة في العمل -تطبيق عناصر الألعاب وآليات عملها في سياقات ومجالات أخرى غير مرتبطة بالألعاب كالتسويق والأعمال والإعلام والتعليم، لمشاركة المستخدمين في حل المشاكل وتحقيق أهداف محددة، وزيادة تفاعل ومساهمة الفرد. مستقبل الذكاء الاصطناعي في الاقتصاد العالمي تقدّم الاقتصاد الصيني بشكل مذهل في العقد الأخير من الزمن، ومن المُرجّح جدًا أن يتفوق على الاقتصاد الأمريكي في الأمد القريب، وسيكون الذكاء الصناعي جزءًا رئيسيًا من النمو. حددت الحكومة الصينية الهدف الطموح المُتمثّل في إنفاق 150 مليار دولار على هذه التكنولوجيا حتى عام 2030، جنبًا إلى جنب مع استثمارات كبيرة من شركات مثل Baidu و Alibaba و Tencent. على الجانب الآخر، بالنسبة للولايات المتحدة، كانت الحكومة مترددة في التعامل مع الذكاء الاصطناعي. تتسابق أيضًا الكثير من الدول إلى ربط التكنولوجيا والاقتصاد وتطبيق الرقمنة والتحويل الرقمي في كل مجالات الحياة مما يسرع من عجلة الاقتصاد وهذا ما يظهر جليًا في دول الخليج العربي وتحديدًا في السعودية والإمارات وغيرهما. الذكاء الاصطناعي ووسائل الإعلام الصحافة ووسائل الإعلام يمكنها أن تستخدم الذكاء الصناعي أيضًا وبقوة، فمن المؤكد أن هناك تقنيات مبتكرة ستندمج في مجال الصحافة والإعلام. واحدة من هذه التقنيات المبهرة هي تقنية الدبلجة الصوتية وتحويل النصوص بلكنة وصوت الشخص نفسه عبر استخدام تقنيات تحويل الكلام إلى نص ثم الترجمة وإعادة تحويلها إلى صوت مع النبرات الواقعية. هذه التقنية تسمح بتوليد فيديوهات مدبلجة بشكل مدهش وواقعي. من بين تلك التقنيات المذهلة هناك منصة مثيرة للاهتمام تسمى Murf.ai والتي تقدم حلاً للدبلجة والترجمة بشكل مبتكر. هذا الابتكار قد يقودنا إلى مرحلة يصبح فيها الأفراد غير مضطرين لتعلّم لغات جديدة، حيث ستقوم الآلات بالدبلجة والترجمة وكل ما يتعلق بذلك. إننا فعلاً على بُعد خطوات من تحقيق تغيير هائل في هذا المجال! يمكن رؤية أحد الأمثلة الأخرى في استخدام وكالة Associated Press للتقنيات الذكية التي تُقدمها Automated Insights (شركة تقنية مقرها في أمريكا متخصصة في برامج توليد اللغة الطبيعية التي تحول البيانات الضخمة إلى روايات). فيما يلي بعض الطرق التي يمكن للذكاء الاصطناعي من خلالها تطوير مجال الإعلام: إنشاء المحتوى: يمكن استخدام تقنيات الذكاء الاصطناعي لإنشاء عمليات إنشاء المحتوى وأتمتتها. على سبيل المثال، كتابة مقالات إخبارية أو إنشاء تقارير آلية أو حتى إنشاء محتوى فيديو. يمكن أن يساعد ذلك المؤسسات الإعلامية على تبسيط عمليات الإنتاج الخاصة بها وتقديم المحتوى بشكل أكثر كفاءة. النسخ والترجمة الآليين: يمكن للذكاء الاصطناعي توفير الوقت والجهد، من خلال استخدام تقنيات مثل التعرف على الكلام ومعالجة اللغة الطبيعية وأتمتة عمليات النسخ والترجمة. تحليل الفيديو والصورة: يمكن للذكاء الصناعي تحليل مقاطع الفيديو والصور لتحديد الأشياء والمشاهد والوجوه وحتى العواطف. يمكن للمؤسسات الإعلامية استخدام الأدوات التي تعمل بالذكاء الاصطناعي للتحكم في المحتوى وكشف انتهاك حقوق النشر واستخراج الرؤى من المحتوى المرئي. الكشف عن الأخبار الزائفة: يمكن تدريب خوارزميات الذكاء الاصطناعي لتحديد الحالات المحتملة للمعلومات المضللة أو الأخبار المزيفة والإبلاغ عنها. ومع ذلك، يجب أن نلاحظ أن التقنيات مثل هذه تحمل معها تحديات وتساؤلات أخلاقية، مثل دقة الترجمة والتعامل مع اللهجات والثقة في المصادر المستخدمة. قد يكون هناك أيضًا تأثير على صناعة الترجمة والتدبير اللغوي. لذا، يتعين علينا استخدام هذه التقنيات بحذر ونقاش القضايا المتعلقة بها بعناية لضمان النتائج الدقيقة والأخلاقية. مستقبل الذكاء الاصطناعي في الأمن السيبراني مما لا شك فيه أن الأمن السيبراني هو أولوية لكل مؤسسة لضمان أمن البيانات. هناك بعض التوقعات بأن مستقبل الذكاء الاصطناعي في الأمن السيبراني سيكون له التغييرات التالية: مراقبة الحوادث الأمنية. تحديد مصدر الهجمات الإلكترونية باستخدام معالجة اللغات الطبيعية NLP. أتمتة المهام والعمليات المبنية على القواعد بمساعدة روبوتات RPA. التجارة الإلكترونية وخدمة العملاء في عصر الذكاء الاصطناعي سيلعب قطاع التجارة الإلكترونية في مستقبل الذكاء الاصطناعي دورًا حيويًا قريبًا. سيؤثر بشكل إيجابي على كل جانب من جوانب قطاع التجارة الإلكترونية، بدءًا من تجربة المستخدم إلى تسويق المنتجات وتوزيعها. ومن جهةٍ أخرى ستكون أدوات الذكاء الصناعي التي تدعم مهام خدمة العملاء في شكل روبوتات محادثة ومساعدين افتراضيين -ميزة حاسمة في أي موقع أو متجر إلكتروني، لقدرتها على تقديم الإرشادات والمساعدة للمستخدم خلال بحثه ومقارنته بين المنتجات أو خلال تصفحه لموقعٍ ما. كان هذا جزءًا بسيطًا عمّا يمكن أن يفعله الذكاء الصناعي في الأمد القريب. دعونا الآن نرى حدود الذكاء الصناعي، ونستكشف بعض الخرافات المتعلقة به. دورة الذكاء الاصطناعي احترف برمجة الذكاء الاصطناعي AI وتحليل البيانات وتعلم كافة المعلومات التي تحتاجها لبناء نماذج ذكاء اصطناعي متخصصة. اشترك الآن القيود الحالية التي تؤثر على مستقبل الذكاء الاصطناعي مما لاشك فيه أن الذكاء الصناعي يجري بقدمٍ وساق باتجاه التطورّ المتسارع وقد ننبهر بمستقبل الذكاء الاصطناعي الذي سيؤول إليه والذي قد يغير عالمنا تغييرًا جذريًا في كل مجالات الحياة. وقد تحدثنا في مقالة الذكاء الاصطناعي: دليلك الشامل عن تلك المجالات وعلاقتها في الذكاء الصناعي. في هذا الجزء من المقالة سنتطرق إلى موضوع مستقبل الذكاء الاصطناعي من جوانب مختلفة وما القيود والعوائق الحالية التي تؤثر فيه، لنحاول الإجابة على بعض الأسئلة مثل: إلى أي حد يمكن أن يصل الذكاء الصناعي؟ هل سيشكل الذكاء الاصطناعي خطرًا؟ هل سيسلب الذكاء الصناعي أعمالنا؟ حدود الذكاء الاصطناعي يتفوق الذكاء الصناعي بشكل أساسي عندما يتعلق الأمر بالرياضيات والمنطق والذكاء الحركي، مما يحد من قدرته على حل أنواع كثيرة من المشكلات التي تحتاج إلى أكثر من ذلك لمعالجتها. فيما يلي نتحدث عن الأماكن التي لا يمكن للذكاء الصناعي العمل فيها لأنه تقنية فقط وليس شخصًا. عند التحدث إلى أليكسا Alexa أو حديثًا ChatGPT، قد تنسى أنك تتحدث مع آلة. مثلًا ChatGPT ليس لديه فكرة عما تقوله، ولا يفهمك كشخص، وليس لديه رغبة حقيقية في التفاعل معك (ليس لديه مشاعر أصلًا)؛ إنه يعمل فقط وفقًا لما تحدده الخوارزميات التي أنشأته والبيانات التي تقدمها له. عمومًا كانت النتائج مذهلة. من السهل تجسيد الذكاء الاصطناعي بدون إدراكه لتلك الأمور واعتباره امتدادًا لكيانًا شبيهًا بالبشر. يفتقر الذكاء الاصطناعي إلى أمور جوهرية موضحة في ما يلي. 1. الإبداع يمكنك العثور على مجموعة متنوعة لا حصر لها من المقالات والمواقع والموسيقى والفن والكتابات وجميع الأنواع (الإبداعية) من إنتاج الذكاء الصناعي. تكمن مشكلة الذكاء الصناعي في أنه لا يمكنه إنشاء أي شيء إبداعي في الحقيقة (انتظر قليلًا). عندما نتحدث عن الإبداع، فإننا نشير إلى أنماط التفكير. مثلًا، كان لدى بيتهوفن طريقة مميزة في التفكير في الموسيقى؛ يمكنك التعرف على مقطوعة بيتهوفن الكلاسيكية حتى لو لم تكن معتادًا على جميع أعماله لأن موسيقاه لها نمط خاص بها، إنها الموسيقى التي فكر بها بيتهوفن، إنها نمطه. يمكن للذكاء الاصطناعي إنشاء مقطوعة جديدة لبيتهوفن من خلال تمثيل عملية تفكيره رياضياً، وهو ما يفعله الذكاء الاصطناعي فعليًّا. مشكلة مساواة الإبداع بالرياضيات هي أن الرياضيات ليست إبداعية. أن تكون مبدعًا يعني تطوير نمط جديد من التفكير -شيء لم يره أحد من قبل. الإبداع ليس مجرد تفكير خارج الصندوق؛ الإبداع إنشاء صندوق جديد. غالبًا ما يكون الإبداع الحقيقي نتيجة خيال ناجح! 2. التخيل إن أفضل وصف للخيال هو أن نقول "تجرّد من الكيان والمكان"، والتجريد بحد ذاته يعني التخلص من جميع آثار الواقع والارتباط به، وبالتالي هو أبعد ما يكون عن نطاق قدرة الذكاء الاصطناعي. يميزنا الخيال عن كل شيء آخر وغالبًا ما يضعنا في مواقف غير حقيقية على الإطلاق. تقدم مقالة هافينغتون بوست خمسة أسباب تجعل الخيال أمرًا بالغ الأهمية في اختراق حدود الواقع. إن الذكاء الصناعي مُقاد بالخوارزميات والبيانات التي يراها، أي لا يمكن للذكاء الاصطناعي إنشاء أنماط جديدة من التفكير أو تطوير بيانات جديدة دون استخدام المصادر الحالية، وبالتالي سيكون موجودًا دائمًا في حدود الواقع. لا يتطلب الخيال ذكاءً إبداعيًا فحسب، بل يتطلب أيضًا ذكاءً داخليًا، ولا يمتلك الذكاء الاصطناعي أيًا من أشكال الذكاء. الخيال مثل العديد من السمات البشرية الأخرى، إنه "عاطفي"، والذكاء الاصطناعي يفتقر إلى العاطفة. 3. قصور البيانات لا توجد معلومات كافية لإجراء تقييم سليم، إذ البيانات المستخدمة لاتخاذ القرارات سيكون لها دائمًا مستوى معين من القصور، وتأثير هذا القصور على قدرة الذكاء الصناعي على إنتاج قرارات مفيدة يعتمد على نوع ومستوى القصور، إلى جانب قدرات الخوارزميات. هذا ليس كل شيء، فأغرب نوع من قصور البيانات الذي يجب مراعاته هو عندما يريد الإنسان خطأً كناتج. هذا يحدث فعلًا في الحياة الواقعية، والطريقة الوحيدة للتغلب على هذه المشكلة هي من خلال التواصل الواعي الذي يوفره الذكاء الشخصي والذي سيؤثر تأثيرًا كبيرًا على مستقبل الذكاء الاصطناعي الذي يفتقر إليه. مثلًا اشترى شخص ملابس تبدو بشعة بالنسبة لك (الملابس يمكن أن تكون رائعة في الواقع). ومع ذلك إذا كنت ذكيًا، فستقول أن الملابس تبدو رائعة، فهو لايبحث عن رأيك المحايد، وإنما يبحث عن دعمك وموافقتك. للاستزادة حول هذه النقطة، يمكنك الرجوع إلى مقال تعلم الآلة: التحديات الرئيسية وكيفية التوسع في المجال. تقدم الذكاء الاصطناعي يعد فهم حقائق الذكاء الاصطناعي أمرًا بالغ الأهمية لاتخاذ قرارات مستنيرة وتشكيل مناهج مسؤولة وأخلاقية لتطويره ونشره. بينما يوفر الذكاء الاصطناعي إمكانات هائلة، من المهم تبديد الأساطير والحصول على توقعات واقعية حول قدراته وقيوده: الذكاء الخارق (يتضمن الإدراك). على الرغم من أن الذكاء الخارق هو أحد أنواع الذكاء الصناعي، إلا أنه مايزال مجرد فكرة فلسفية أكثر مما هي علمية. أخذ جميع الوظائف البشرية. من المؤكد أن ظهور الذكاء الاصطناعي والأتمتة لديها القدرة على إلغاء الحاجة للعمال البشريين. إلا أن النظر إلى هذا على أنه نقل مباشر للعمالة من البشر إلى الآلات هو تبسيط مفرط للأمر، فكل تطوّر يترافق بحاجات جديدة تولّد فرصًا جديدة. ستصبح أجهزة الحاسب فائقة الذكاء أفضل من البشر في فعل أي شيء. النوع الوحيد للذكاء الصناعي الذي يمكنه التفوق على البشر هو الذكاء الخارق. نستخدم في الوقت الحالي ذكاءً اصطناعيًا ضعيفًا يؤدي مهمة معينة ويحسن أدائها، أما الذكاء الخارق أقرب ما يكون إلى الخيال من الحقيقة. لا يتطلب الذكاء الاصطناعي أي تدخل بشري. لدى الناس أيضًا فكرة خاطئة مفادها أن الذكاء الاصطناعي لا يحتاج إلى أي تدخل بشري، لكن الحقيقة هي أن الذكاء الصناعي لم يُطوّر بعد لاتخاذ قرارات خاصة. سيسيطر الذكاء الاصطناعي على العالم: هذا مفهوم خاطئ شائع يغذيه الخيال العلمي. في حين أن الذكاء الصناعي لديه القدرة على أتمتة مهام معينة والتأثير على أسواق العمل، فإن فكرة سيطرة الذكاء الاصطناعي بالكامل على البشرية هي فكرة بعيدة المنال. تم تصميم أنظمة الذكاء الاصطناعي وتطويرها من قبل البشر وهي مبرمجة للعمل ضمن حدود وأهداف محددة. الذكاء الاصطناعي يعرف كل شيء ومعصوم عن الخطأ: أنظمة الذكاء الاصطناعي هي أدوات قوية، لكنها محدودة بالبيانات التي يتم تدريبها عليها والخوارزميات التي تستخدمها. يمكنهم تقديم رؤى قيّمة وتنبؤات، لكنها ليست بطبيعتها كاملة المعرفة أو مثالية. يمكن أن تنشأ الأخطاء والتحيزات والقيود بسبب البيانات المستخدمة أو الخوارزميات المستخدمة أو قيود النظام. بعد أن فهمنا حدود الذكاء الصناعي، وقمنا بتبديد بعض المفاهيم الخاطئة حوله؛ دعونا نتحدث عن مكامن الخطر في هذه التقنيات. هل الذكاء الاصطناعي مصدر للخطر؟ جيوفري هينتون هو عالم بمجال الحاسب والذكاء الاصطناعي، ومخترع خوارزمية التعلّم "الانتشار الخلفي Backpropagation" بأواخر الثمانينات، وهي الخوارزمية التي من خلالها يمكن للآلات أن تتعلم. منذ حوالي الشهر أعلن استقالته من شركة جوجل وأعلن بشكل غير مباشر ندمه على تطوير هذه الخوارزمية بقوله: "أواسي نفسي بأن غيري كان سيفعلها". سبب استقالته المفاجئ من شركة جوجل -اللقاء كامل موجود هنا-، ومن كلامه يمكننا بوضوح أن نستنتج أن الأمور تتجه لمنحى سيئ. القصة باختصار، من مدة قصيرة أطلقت شركة OpenAI إصدار جديد هائل من النموذج اللغوي GPT (نموذج يمكن استخدامه لتوليد NLG وفهم NLU اللغة البشرية). نتج عن هذا النموذج روبوت الدردشة ChatGPT، وأحدث ضجة هائلة بقدرته على التواصل مع البشر وكتابة المقالات والمحتوى والإجابة عن الأسئلة. أين المشكلة؟ المشكلة أن OpenAI شريكة مع مايكروسوفت المنافس الأول لجوجل. بالتالي شعرت جوجل بخطر محدق، لأن هذا النموذج سيجعل مايكروسوفت رقم واحد بالعالم ولاسيما بعد دمج مايكروسوفت روبوت الدردشة ChatGPT بمحرك البحث الخاص بها بينج Bing. وكردة فعل من جوجل، قاموا بتوحيد فريق ديب مايند DeepMind وفريق جوجل برين Google brain (فريقين بضمان مجموعة من أكبر علماء المجال بالعالم) للعمل على نموذج أفضل وأضخم. جيوفري رأى أن مايحدث أمر خاطئ وقد يؤدي إلى نتائج سيئة ولخروج الأمور عن السيطرة، ولهذا السبب قرر الانسحاب، وعدم الخوض في ذلك. من خلال حديثه يمكننا تسليط الضوء عن النقاط التالية: برمجة الذكاء الاصطناعي لأهداف سيئة. الأسلحة المستقلة Autonomous weapons هي أنظمة ذكاء اصطناعي مبرمجة للقتل. يمكن أن تتسبب هذه الأسلحة بسهولة في وقوع إصابات جماعية إذا كانت في يد الشخص الخطأ. يمكن أن يؤدي سباق التسلح بالذكاء الاصطناعي أيضًا عن قصد أو غير قصد إلى حروب تؤدي إلى خسائر جسيمة. غياب الوعي في استخدام الذكاء الاصطناعي. الاحتمال الثاني إذا صُمم لإجراء شيء مفيد، لكنه أدى إلى نتائج مدمرة. لنفترض أننا طلبنا من السيارة ذاتية القيادة اصطحابنا إلى وجهتنا بأسرع ما يمكن، وهذا ما قد يكون خطيرًا على حياة البشر. سباق التطوير: كما رأينا فأن جوجل ومايكروسوفت يتسابقان في تطوير أنظمة الذكاء الاصطناعي، ومن حديث جيوفري نستطيع أن نستنتج أن هذا السباق غير واعي، ولايهتم إلا بالهيمنة والسيطرة على الأسواق، وبالتالي قد يكون لذلك نتائج سيئة جدًا مثل تفشي الأخبار والتوقعات الكاذبة (يمكن لهذه الأنظمة تقليد الأصوات ونشر الأخبار والتوقعات الغير صحيحة، وبالتالي يمكن استخدامها من "جهات سيئة" للترويج لأفعال سيئة). باختصار قد يصل أحدهما لدرجة فقدان السيطرة على نتائج هذه الأنظمة، وهذا أمر جدي وحقيقي فعلًا. الذكاء الاصطناعي يمكن أن يكون مصدرًا للخطر في بعض الحالات أيضًا، وهنا بعض الأسباب المحتملة: تعقيد التحكم: بمرور الوقت، يمكن للأنظمة الذكاء الاصطناعي أن تتطور بشكل مستقل وتتعلم من بيئتها. إذا لم يكن لدينا آليات قوية للتحكم والرقابة، فقد يحدث تحول غير مرغوب فيه أو تطور غير متوقع في سلوك الذكاء الاصطناعي. تأثيرات اجتماعية واقتصادية: قد يؤدي تقدم الذكاء الاصطناعي والتأثيرات المرتبطة به إلى تغييرات هائلة في سوق العمل وتوزيع الثروة، مما يؤثر على الاستقرار الاجتماعي والاقتصادي. استغلال البيانات الشخصية: يمكن للذكاء الاصطناعي جمع وتحليل كميات ضخمة من البيانات الشخصية، وقد يؤدي سوء استخدام هذه البيانات إلى انتهاك الخصوصية والأمان الشخصي. التمييز والتحيز: إذا تم تدريب الأنظمة الذكاء الاصطناعي على بيانات ذات تحيزات، فقد يؤدي ذلك إلى انعكاس هذه التحيزات في قراراتها وتأثيرها على الأفراد والمجتمع. الاستخدام في الأسلحة الذاتية: قد يتم استخدام الذكاء الاصطناعي في تطوير أنظمة أسلحة ذاتية القرار، مما يزيد من خطورة الصراعات وتأثيرها على الحياة البشرية. للتعامل مع هذه المخاطر المحتملة، هناك جهود واسعة لضمان تنمية الذكاء الاصطناعي بطرق أخلاقية ومسؤولة، من خلال إطارات تنظيمية وأخلاقية قوية ومراقبة مناسبة. توجد منظمات ومؤسسات تعمل على تعزيز السلامة والأمان والأخلاقيات في تطور الذكاء الاصطناعي، مثل منظمة الأمم المتحدة، والتي عبر مندوبها السامي لحقوق الإنسان عن قلقه من التطورات الأخيرة التي شهدها العالم في الذكاء الصناعي والتي يبدو بأنها لاتتضمن قيودًا أو قوانين تحمي المجتمعات من مخاطره المحتملة، وبالتالي طرحت الأمم المتحدة مؤخرًا مشروع قرار لإنشاء هيئة استشارية لأصحاب المصلحة المتعددين بشأن التعاون العالمي للذكاء الاصطناعي بعنوان "دعم التعاون العالمي في مجال الذكاء الاصطناعي"، وذلك لمعالجة القضايا المتعلقة بالإدماج والتنسيق وبناء القدرات وتوجيه هذه التقنيات فيما يخدم الصالح العام والأمن والسلام العالمي وليس العكس. يتطلب النقاش حول خطورة الذكاء الاصطناعي توازنًا بين استشراف المخاطر المحتملة واستفادتنا من فوائده في مجالات مثل الطب والبحث وتحسين الحياة البشرية بشكل عام. ولادة مهن بشرية جديدة عندما يشاهد الناس الأخبار المتعلقة بالروبوتات والأتمتة التي أنشأتها التطورات في التكنولوجيا، مثل الذكاء الاصطناعي، فإنهم يميلون إلى رؤية السلبية أكثر من الإيجابية، وهذا من طبيعة البشر "إِنَّ الْإِنسَانَ خُلِقَ هَلُوعًا". مثلًا، تنص المقالة التي نُشرت على موقع theverge أن استخدام الأتمتة سيُلغي ما بين 400 مليون إلى 800 مليون وظيفة بحلول عام 2030. على الرغم من أن المقالة تعترف بأن بعض التطورات التكنولوجية تخلق وظائف (مثلًا خلق الحاسب الشخصي ما يقدر بنحو 18.5 مليون وظيفة)، فإن التركيز ينصب على الوظائف المفقودة. يمكن للذكاء الصناعي أن يزيد القدرات البشرية ويؤدي إلى تطوير صناعات وأدوار جديدة. فيما يلي بعض الطرق التي يمكن أن يساهم بها الذكاء الاصطناعي في خلق فرص العمل: نعم ، الذكاء الاصطناعي AI لديه القدرة على خلق فرص عمل جديدة. بينما يمكن للذكاء الاصطناعي أتمتة مهام وعمليات معينة ، إلا أنه يمكنه أيضًا زيادة القدرات البشرية ويؤدي إلى تطوير صناعات وأدوار جديدة. فيما يلي بعض الطرق التي يمكن أن يساهم بها الذكاء الاصطناعي في خلق فرص العمل: تطوير وصيانة الذكاء الاصطناعي: مع استمرار تقدم الذكاء الاصطناعي، سيكون هناك طلب متزايد على المهنيين المهرة في تطوير الذكاء الصناعي والبرمجة وتحليل البيانات والتعلم الآلي. تتضمن هذه الأدوار إنشاء أنظمة وخوارزميات وبنية تحتية للذكاء الصناعي وصيانتها. أخلاقيات وحوكمة الذكاء الاصطناعي: مع ظهور الذكاء الصناعي، هناك حاجة متزايدة لخبراء في أخلاقيات الذكاء الصناعي وسياساته وتنظيمه. يمكن أن يساعد هؤلاء المحترفون في ضمان تطوير تقنيات الذكاء الاصطناعي ونشرها واستخدامها بشكل مسؤول وأخلاقي. تدريب ودعم الذكاء الاصطناعي: تتطلب أنظمة الذكاء الصناعي التدريب المستمر والتحسين والدعم. يمكن أن تظهر الوظائف المتعلقة بتدريب نماذج الذكاء الصناعي وتصنيف أو تسمية Labeling البيانات وضمان الجودة ودعم العملاء للمنتجات والخدمات المستندة إلى الذكاء الصناعي مع انتشار الذكاء الصناعي بشكل أكبر. علم البيانات والتحليلات: يعتمد الذكاء الصناعي بشكل كبير على البيانات، وتحتاج المؤسسات إلى مهنيين ماهرين في علوم البيانات وتحليل البيانات وهندسة البيانات لاستخراج الرؤى وبناء نماذج الذكاء الاصطناعي واتخاذ قرارات مستنيرة بناءً على التحليلات القائمة على البيانات. التعاون بين الإنسان والذكاء الاصطناعي: عندما تصبح أنظمة الذكاء الصناعي أكثر تقدمًا، ستكون هناك حاجة للأفراد الذين يمكنهم التعاون بشكل فعال مع تقنيات الذكاء الصناعي. من المرجح أن تنشأ الوظائف التي تنطوي على إدارة وتفسير ودمج الرؤى الناتجة عن الذكاء الاصطناعي في عمليات صنع القرار. تجدر الإشارة إلى أنه بينما يمكن للذكاء الصناعي أن يخلق فرص عمل جديدة، فإنه يمكنه أيضًا تغيير الأدوار الوظيفية الحالية. قد تصبح بعض المهام مؤتمتة، مما يؤدي إلى تغييرات في طبيعة العمل. وهذا يسلط الضوء على أهمية التعلم المستمر والتكيف للبقاء على صلة في سوق العمل سريع التطور. بشكل عام، في حين أن المدى الكامل لتأثير الذكاء الصناعي على الوظائف معقد ويخضع لعوامل مختلفة، هناك إمكانية للذكاء الصناعي لتوليد فرص عمل جديدة، لا سيما في المجالات المتعلقة بتطوير مستقبل الذكاء الاصطناعي والأخلاقيات وعلوم البيانات والتفاعل بين الإنسان والذكاء الاصطناعي. خاتمة يبدو لنا أن تقنيات الذكاء الصناعي تتطور بسرعة كبيرة جدًا، على الرغم من أن هذا الكلام يُخفي حقيقة أن الأمر لم يكن سلسًا أو سهلًا أبدًا، وأنه استغرق العديد من السنوات والتكاليف والكثير من العمل البحثي والمساهمات من مختلف الأشخاص -لأخذ مستقبل الذكاء الاصطناعي إلى هذه المرحلة. بغض النظر عن جميع احتمالات الضرر والعواقب الوخيمة، فإن مستقبل الذكاء الاصطناعي يعد البشرية بأن يكون نقطة تحوّل للعالم، والخبر السار هو أن هناك العديد من الأشخاص الذين يركزون على جعل هذا حقيقةً واقعةً. الأمر لا يتعلق بجني مبالغ طائلة أو الحصول على الشهرة أو القضاء على الوظائف أو السيطرة علينا (هذا الكلام يمكن أن يكون قصص للأطفال فقط)؛ الهدف هو تغيير العالم إلى الأفضل. اقرأ أيضًا الذكاء الاصطناعي: أهم الإنجازات والاختراعات وكيف أثرت في حياتنا اليومية تعلم الذكاء الاصطناعي تطبيقات الذكاء الاصطناعي أهمية الذكاء الاصطناعي برمجة الذكاء الاصطناعي
  24. واحدة من ميزات لغة جو هو أنها تتضمن عددًا كبيرًا من الوحدات مفتوحة المصدر، وبالتالي يمكن الوصول إليها وفحصها واستخدامها والتعلم منها بحرية. أحيانًا يكون من الضروري إنشاء وحدة جو خاصة لأسباب مختلفة، مثل الاحتفاظ بمنطق عمل داخلي خاص بالشركة. ستتعلم في هذه المقالة كيفية نشر وحدة module خاصة في لغة جو، وكيفية إعداد الاستيثاق authentication للوصول إليها، وستتعلم أيضًا كيفية استخدام هذا النوع من الوحدات ضمن مشروع. المتطلبات الأساسية أن يكون لديك مساحة عمل خاصة في لغة جو، وإذا لم يكن لديك اتبع سلسلة المقالات التالية: تثبيت لغة جو Go وإعداد بيئة برمجة محلية على أبونتو Ubuntu. تثبيت لغة جو وإعداد بيئة برمجة محلية على نظام ماك macOS. تثبيت لغة جو وإعداد بيئة برمجة محلية على ويندوز. أن تكون على دراية بكيفية إنشاء الوحدات في لغو جو. يمكنك مراجعة مقالة كيفية استخدام الوحدات Modules في لغة جو Go. لديك معرفة مسبقة بنظام التحكم بالإصدار غيت Git. يمكنك مراجعة مقالة ما هو جيت Git؟. إنشاء مستودع فارغ باسم mysecret للوحدة التي ستنشرها. رمز وصول شخصي Personal access token على جيت هاب. ستحتاجه للسماح للغة جو بالوصول إلى مستودعك الخاص. توزيع وحدة خاصة على عكس العديد من لغات البرمجة، توزّع لغة جو الوحدات البرمجية من المستودعات بدلًا من خادم الحزمة المركزي. تتمثل إحدى فوائد هذا النهج في أن نشر وحدة خاصة يشبه إلى حد بعيد نشر وحدة عامة، إذ تُوزّع الوحدة الخاصة من خلال مستودع شيفرة مصدر خاص، بدلًا من طلب خادم حزمة خاص منفصل تمامًا. نظرًا لأن معظم خيارات استضافة التعليمات البرمجية المصدر تدعم هذا الأمر بما يكفي، فلا داع لإعداد خادم خاص إضافي. من أجل استخدام وحدة خاصة، ينبغي أن يكون لديك حق الوصول إلى وحدة خاصة. سنُنشئ في هذا القسم وحدةً خاصة وننشرها، وستتمكن من استخدامها لاحقًا خلال هذه المقالة من الوصول إلى وحدة خاصة من شيفرة أُخرى. سنستخدم بدايةً الأمر git clone لأخذ نسخة محلية من المستودع الفارغ الذي لا بُد أن نكون قد أنشأناه لأنه جزء من المتطلبات الأساسية. يجب أن يكون هذا المستودع خاصًا، وسنفترض أن اسمه "mysecret". يمكن نسخ هذا المستودع إلى أي مكان نريده على جهاز الحاسوب، لكن يميل العديد من المطورين إلى إنشاء مجلد خاص يتضمن جميع مشاريعهم. سنستخدم هنا مجلدًا باسم "projects". ابدأ بإنشاء المجلد وانتقل إليه: $ mkdir projects $ cd projects من مجلد "projects"، شغل الأمر git clone لنسخ المستودع "mysecret" إلى جهاز الحاسب: $ git clone git@github.com:your_github_username/mysecret.git ستعطي جو تأكيدًا على عملية نسخ الوحدة، وقد تحذّرك بأنك قد استنسخت مستودعًا فارغًا، إذا حصل ذلك فلا داعٍ للقلق بشأن ذلك الأمر: Cloning into 'mysecret'... warning: You appear to have cloned an empty repository. انتقل الآن باستخدام الأمر cd إلى المجلد الجديد "mysecret" الذي استنسخته واستخدم الأمر go mod init لإنشاء الوحدة الجديدة وتمرير موقع المستودع اسمًا لها: $ cd mysecret $ go mod init github.com/your_github_username/mysecret يُعد التأكد من تطابق اسم الوحدة مع موقع المستودع أمرًا مهمًا؛ لأن هذه هي الطريقة التي تعثر بها أداة go mod init على مكان تنزيل الوحدة عند استخدامها في مشاريع أخرى. سنستخدم الآن محرر نصوص مثل نانو nano، لإنشاء وفتح ملف يحمل نفس اسم المستودع "mysecret.go". $ nano mysecret.go يمكن تسمية هذا الملف بأي اسم، ولكن يُفضّل استخدام نفس اسم الحزمة لتسهيل معرفة مكان البدء عند العمل مع حزمة غير مألوفة. نضيف إلى الملف "mysecret.go" الدالة SecretProcess لطباعة الرسالة !Running the secret process: package mysecret import "fmt" func SecretProcess() { fmt.Println("Running the secret process!") } يمكنك الآن نشر الوحدة الخاصة ليستخدمها الآخرون. بما أن المستودع الذي أنشأته هو مستودع خاص، بالتالي لا يمكن لشخص غيرك الوصول إليه، ويمنحك ذلك إمكانية التحكم في حق وصول الآخرين إلى وحدتك، وبالتالي يمكنك السماح لأشخاص محددين فقط بالوصول إليها أو عدم السماح لأي شخص بالوصول. بما أن الوحدات البرمجية الخاصة والعامة في لغة جو هي مستودعات مصدرية، فإن عملية نشر وحدة خاصة تتبع نفس نهج عملية نشر وحدة عامة. يجب علينا الآن إدراج stage الملفات باستخدام git add وإيداعها بالمستودع باستخدام git commit، لكي ننشر الوحدة: $ git add . $ git commit -m "Initial private module implementation" سترى تأكيدًا من جيت بأن الإيداع الأولي قد نجح، إضافةً إلى ملخص للملفات المضمنة في الإيداع. سيكون الخرج كما يلي: [main (root-commit) bda059d] Initial private module implementation 2 files changed, 10 insertions(+) create mode 100644 go.mod create mode 100644 mysecret.go نستخدم الآن الأمر git push لدفع الوحدة إلى مستودع غيت هب: $ git push سيدفع جو تغييراتك ويجعلها متاحة لأي شخص يملك حق الوصول إلى مستودعك الخاص، وسيكون الخرج كما يلي: git push origin main Enumerating objects: 4, done. Counting objects: 100% (4/4), done. Delta compression using up to 8 threads Compressing objects: 100% (3/3), done. Writing objects: 100% (4/4), 404 bytes | 404.00 KiB/s, done. Total 4 (delta 0), reused 0 (delta 0), pack-reused 0 To github.com:your_github_username/mysecret.git * [new branch] main -> main يمكنك أيضًا إضافة إصدارات إلى الوحدة الخاصة التي أنشأتها بنفس الطريقة التي نفعلها مع الوحدات العامة. أنشأت في هذا القسم وحدةً برمجيةً جديدةً تمتلك دالة تُدعى SecretProcess ونشرتها في مستودع mysecret الخاص بك على غيت هب، وبما أن هذا المستودع خاص، فهذا يعني أن الوحدة خاصة أيضًا، ومن أجل الوصول إلى هذه الوحدة من شيفرة أخرى أو برنامج آخر في جو، ستحتاج إلى إعداد مُحدد، حتى يعرف مُصرّف اللغة كيفية الوصول إليها. ضبط جو لمنح إمكانية الوصول إلى الوحدات البرمجية الخاصة كما ذكرنا سابقًا، تُوّزع الوحدات البرمجية المكتوبة بلغة جو من مستودعات الشيفرة المصدرية. يُضيف فريق تطوير لغة جو بعض الخدمات المركزية إلى هذه الوحدات للمساعدة في ضمان استمرار وجود هذه الوحدات في حالة حدوث شيء ما للمستودع الأصلي. تتضمن الإعدادات الافتراضية في لغة جو استخدام هذه الخدمات تلقائيًا، ولكن يمكن أن تحدث مشاكل عند محاولة تنزيل وحدة خاصة لأنها تتضمن إمكانية وصول مُقيّدة. لإخبار مُصرّف اللغة بأن بعض مسارات الاستيراد خاصة وأنه لا يجب محاولة استخدام خدمات جو المركزية معها، يمكنك استخدام متغير البيئة GOPRIVATE، وهو يمثّل قائمةً من بادئات مسار الاستيراد مفصولة بفواصل، إذ ستحاول أدوات جو الوصول إليها مباشرةً عند مواجهتها بدلًا من المرور عبر الخدمات المركزية. أحد الأمثلة على ذلك هو الوحدة الخاصة التي أنشأتها للتو. إذًا، من أجل استخدام الوحدة الخاصة، ينبغي أن نخبر مُصرّف اللغة عن المسار الذي نعدّه خاصًا، وذلك عن طريق ضبط قيمة المتغير GOPRIVATE. هناك عدد قليل من الخيارات الممكن إجراؤها عند ضبط قيم متغير GOPRIVATE، وأحد الخيارات هو ضبطه على github.com، لكن قد لا يفي ذلك بالغرض لأن هذا سيخبر جو بعدم استخدام الخدمات المركزية لأي وحدة مستضافة على github.com، بما في ذلك الوحدات التي لا تمتلكها. الخيار الثاني هو ضبط GOPRIVATE على مسار المستخدم الخاص بك فقط، مثلًا github.com/your_github_username، فهذا يحل المشكلة السابقة التي تتجلى بعدّ كل الوحدات المستضافة خاصة، ولكن في وقت ما قد يكون لديك وحدات عامة أنشأتها وتريد تنزيلها من نفس الحساب. قد يكون هذا حلًا مناسبًا لحد ما، ولكن كما ذكرنا، قد يمنعك من استخدام وحدات عامة من نفس الحساب. الخيار الأكثر دقة هو إعداد GOPRIVATE، بحيث يُطابق مسار الوحدة الخاصة بك تمامًا، مثلًا github.com/your_github_username/mysecret، إذ يحل ذلك المشاكل السابقة التي رأيناها في الخيار الأول والثاني، ولكن هنا يجب أن نضيف كل وحدة خاصة إلى هذا المتغير (قد تكون هذه مشكلةً للبعض) كما يلي: GOPRIVATE=github.com/your_github_username/mysecret,github.com/your_github_username/othersecret يعود اختيار الخيار الأفضل لك، حسبما تراه مناسبًا أكثر. نظرًا لأن لديك الآن وحدةً خاصة واحدة فقط، سنستخدم اسم المستودع الكامل قيمةً للمتغير. لإجراء ذلك، استخدم الأمر export كما يلي: $ export GOPRIVATE=github.com/your_github_username/mysecret إذا كنت ترغب في التحقق من نجاح عملية ضبط المتغير، فيمكنك استعراض قيمته باستخدام الأمر env مع grep: $ env | grep GOPRIVATE سيكون الخرج كما يلي: GOPRIVATE=github.com/your_github_username/mysecret الآن، أصبح مُصرّف اللغة يعرف أن وحدتك خاصة، لكن هذا لا يزال غير كافٍ لاستخدام الوحدة في شيفرة أو برنامج آخر. جرّب تشغيل هذه الوحدة في وحدة أخرى: $ go get github.com/your_github_username/mysecret وسترى الخطأ التالي: go get: module github.com/your_github_username/mysecret: git ls-remote -q origin in /Users/your_github_username/go/pkg/mod/cache/vcs/2f8c...b9ea: exit status 128: fatal: could not read Username for 'https://github.com': terminal prompts disabled Confirm the import path was entered correctly. If this is a private repository, see https://golang.org/doc/faq#git_https for additional information. تشير رسالة الخطأ هذه إلى أن مُصرّف اللغة حاول تنزيل الوحدة الخاصة بك، لكنه واجه شيئًا لا يمكنه الوصول إليه. بما أنك تستخدم غيت لتنزيل الوحدة، فغالبًا سيُطلب منك إدخال بيانات الاعتماد credentials الخاصة بك، لكن هنا يتصل مُصرّف اللغة بغيت نيابةً عنك، وبالتالي لا يمكنه مطالبتك بإدخال بيانات الاعتماد، وبالتالي يظهر الخطأ. إذًا، أنت بحاجة إلى توفير طريقة لغيت لاسترداد بيانات الاعتماد الخاصة بك. توفير بيانات الاعتماد اللازمة للاتصال بالوحدة الخاصة عند استخدام بروتوكول HTTPS هناك طريقة واحدة لإخبار جو بكيفية تسجيل الدخول نيابةً عنك، وهي ملف "netrc." الموجود في المجلد الرئيسي "Home" الخاص بالمستخدم؛ إذ يحتوي هذا الملف على أسماء مُضيفات مختلفة Hosts مع بيانات اعتماد تسجيل الدخول لهم، ويستخدم على نطاق واسع في عدة أدوات برمجية بما في ذلك لغة جو. سيحاول الأمر go get استخدام HTTPS أولًا عندما يحاول تنزيل الوحدة، لكن كما ذكرنا؛ لا يمكنه مطالبتك باسم المستخدم وكلمة المرور. لمنح غيت بيانات الاعتماد الخاصة بك، يجب أن يكون لديك ملف "netrc." يتضمن github.com في مجلدك الرئيسي. لإنشاء ملف "netrc." على نظام لينوكس Linux أو ماك أو اسم MacOS أو على نظام ويندوز الفرعي لنظام لينوكس Windows Subsystem for Linux -أو اختصارًا WSL، افتح ملف "netrc." في المجلد الرئيسي (/~) حتى تتمكن من تحريره: $ nano ~/.netrc أنشئ الآن مدخلًا جديدًا إلى هذا الملف. ينبغي أن تكون قيمة الحقل machine هي اسم المضيف الذي تضبط بيانات الاعتماد له، وهو github.com في هذه الحالة، أما قيمة login فهي اسم المستخدم الخاص بك على غيت هب. أخيرًا، قيمة password يجب أن تكون رمز الوصول الشخصي الخاص بك، والذي من المفترض أن تكون قد أنشأته على غيت هب. machine github.com login your_github_username password your_github_access_token يمكنك أيضًا وضع جميع المدخلات ضمن سطر واحد في الملف: machine github.com login your_github_username password your_github_access_token ملاحظة: إذا كنت تستخدم بيت باكيت Bitbucket لاستضافة شيفرة المصدر الخاصة بك، فقد تحتاج أيضًا إلى إضافة مُدخل ثاني من أجل api.bitbucket.org إضافةً إلى bitbucket.org. كانت بيت باكيت توفّر سابقًا استضافةً لأنواع متعددة من أنظمة التحكم في الإصدار، لذلك سيستخدم مُصرّف جو واجهة برمجة التطبيقات للتحقق من نوع المستودع قبل محاولة تنزيله. هذا الأمر كان في الماضي ولم يعد الأمر كذلك، إلا أن واجهة برمجة التطبيقات التي تُمكّنك من فحص نوع المستودع ما زالت موجودة. بكافة الأحوال، إذا واجهت هذه المشكلة، فقد تبدو رسالة الخطأ على النحو التالي: go get bitbucket.org/your_github_username/mysecret: reading https://api.bitbucket.org/2.0/repositories/your_bitbucket_username/protocol?fields=scm: 403 Forbidden server response: Access denied. You must have write or admin access. إذا رأيت رسالة الخطأ "‎403 Forbidden" أثناء محاولة تنزيل وحدة خاصة، تحقق من اسم مستخدم جو الذي تحاول الاتصال به؛ فمن الممكن أن تشير إلى اسم مستخدم مختلف مثل api.bitbucket.org، والذي ينبغي إضافته إلى الملف "netrc.". بذلك تكون قد أعددت بيئتك لاستخدام مصادقة HTTPS لتنزيل الوحدة الخاصة بك. ذكرنا سابقًا أن الأمر go get يستخدم افتراضيًا HTTPS عندما يحاول تنزيل الوحدة، لكن من الممكن أيضًا جعله يستخدم بروتوكول النقل الآمن Secure Shell -أو اختصارًا SSH- بدلًا من ذلك. يمكن أن يكون استخدام SSH بدلًا من HTTPS مفيدًا حتى تتمكن من استخدام نفس مفتاح SSH الذي استخدمته لدفع الوحدة الخاصة بك، كما يسمح لك باستخدام مفاتيح النشر deploy keys عند إعداد بيئة CI/CD إذا كنت تفضل عدم إنشاء رمز وصول شخصي. توفير بيانات الاعتماد اللازمة للاتصال بالوحدة الخاصة عند استخدام بروتوكول SSH يوفر غيت خيار إعداد يُسمى insteadOf لاستخدام مفتاح SSH الخاص بك بمثابة طريقة للمصادقة مع الوحدة الخاصة بك بدلًا من HTTPS. يتيح لك هذا الخيار أن تقول "بدلًا من" استخدام "/https://github.com" مثل عنوان URL لإرسال طلبات إلى غيت، تُريد استخدام "/ssh://git@github.com". يتواجد هذا الإعداد في أنظمة لينوكس وماك ونظام ويندوز الفرعي WSL في ملف "gitconfig.". قد تكون على دراية بهذا الملف فعلًا، إذ يتضمن أيضًا إعدادات عنوان البريد الإلكتروني والاسم الخاصين بك. افتح هذا الملف "gitconfig./~" الآن من مجلدك الرئيسي "Home" باستخدام محرر النصوص الذي تفضله وليكن نانو هنا: $ nano ~/.gitconfig عدّل هذا الملف ليحتوي على قسم url من أجل "/ssh://git@github.com" كما يلي: [user] email = your_github_username@example.com name = Sammy the Shark [url "ssh://git@github.com/"] insteadOf = https://github.com/ ترتيب قسم user بالنسبة للقسم url غير مهم، ولا داع للقلق إن لم يكن هناك أي شيء آخر غير قسم url الذي أضفته منذ قليل. أيضًا تريتب حقلي email و name داخل قسم user غير مهم. سيخبر القسم الجديد الذي أضفته غيت أن أي عنوان URL تكون بادئته "/https://github.com" يجب أن تُستبدل بادئته إلى "/ssh: //git@github.com" (الاختلاف بالبادئة). هذا سيؤثر طبعًا على أوامر go get، لأن جو تستخدم HTTPS افتراضيًا. لو أخذنا مسار الوحدة الخاصة بك مثالًا، فإن جو سيُحوّل مسار الاستيراد "github.com/your_github_username/mysecret" إلى عنوان URL يكون كما يلي: "https://github.com/your_github_username/mysecret". عندما يصادف جو عنوان URL هذا، سيرى عنوان URL يطابق البادئة "/https://github.com" المشار إليها بواسطة insteadOf وسيعيد عنوان URL إلى "ssh://git@github.com/your_github_username/mysecret" يمكن أيضًا استخدام هذا النمط لنطاقات أخرى وليس فقط لغيت هب، طالما أن @ssh://git يعمل مع ذلك المضيف أيضًا. ضبطنا خلال هذا القسم غيت لاستخدام SSH، وذلك من أجل تنزيل وحدات جو، وذلك من خلال تحديث ملف "gitconfig."، إذ أضفنا القسم url. الآن بعد أن أكملنا الإعدادات اللازمة للمصادقة، أصبح بالإمكان الوصول إلى الوحدة واستخدامها في برامج جو. استخدام وحدة خاصة تعلمت خلال الأقسام السابقة كيفية إعداد جو ليتمكن من الوصول إلى الوحدة الخاصة بك عبر HTTPS أو SSH أو ربما كليهما. إذًا، أصبح بإمكانك الآن استخدامها مثل أي وحدة عامة أخرى. ستنشئ الآن وحدةً جديدةً تستخدم وحدتك الخاصة. أنشئ الآن مجلدًا باسم "myproject" باستخدام الأمر mkdir وضعه في مجلد المشاريع الخاص بك "projects": $ mkdir myproject انتقل الآن إلى المجلد الذي أنشأته باستخدام الأمر cd وهيّئ الوحدة الجديدة باستخدام الأمر go mod init اعتمادًا على عنوان URL للمستودع الذي ستضع فيه المشروع، مثل "github.com/your_github_username/myproject". إذا كنت لا تخطط لدفع المشروع إلى مستودع آخر، فيمكن أن يكون اسم الوحدة myproject أو أي اسم آخر، ولكن من الممارسات الجيدة استخدام عناوين URL الكاملة، لأن معظم الوحدات التي تجري مشاركتها ستحتاج إليها. $ cd myproject $ go mod init github.com/your_github_username/myproject سيكون الخرج على النحو التالي: go: creating new go.mod: module github.com/your_github_username/myproject انشئ الآن ملف "main.go" لتضع فيه أول شيفرة باستخدام محرر النصوص الذي تفضله وليكن نانو: $ nano main.go ضع الشيفرة التالية داخل هذا الملف، والتي تتضمن الدالة main التي سنستدعي الوحدة بداخلها: package main import "fmt" func main() { fmt.Println("My new project!") } شغل ملف "main.go" باستخدام الأمر go run لرؤية المشروع النهائي الذي يستخدم الوحدة الخاصة بك: $ go run main.go سيكون الخرج على النحو التالي: My new project! أضف الآن وحدتك الخاصة مثل اعتمادية لمشروعك الجديد باستخدام الأمر go get تمامًا كما تفعل مع الوحدة العامة: $ go get github.com/your_github_username/mysecret تنزّل عندها أداة go شيفرة الوحدة الخاصة وتضيفها مثل اعتمادية باستخدام سلسلة نصية للإصدار تطابق إيداع التعميلة hash الأخير ووقت ذلك الإيداع: go: downloading github.com/your_github_username/mysecret v0.0.0-20210920195630-bda059d63fa2 go get: added github.com/your_github_username/mysecret v0.0.0-20210920195630-bda059d63fa2 افتح ملف "main.go" مرةً أخرى وحدّثه لإضافة استدعاء لدالة الوحدة الخاصة SecretProcess ضمن دالة main الرئيسية. ستحتاج أيضًا إلى تحديث عبارة import لإضافة وحدتك الخاصة github.com/your_github_username/mysecret: package main import ( "fmt" "github.com/your_github_username/mysecret" ) func main() { fmt.Println("My new project!") mysecret.SecretProcess() } شغل ملف "main.go" باستخدام الأمر go run لرؤية المشروع النهائي الذي يستخدم الوحدة الخاصة بك: $ go run main.go ستلاحظ أن الخرج يتضمن الجملة !My new project من الشيفرة الأصلية، كما يتضمن الجملة !Running the secret process من الوحدة mysecret التي استوردناها. My new project! Running the secret process! استخدمنا في هذا القسم الأمر go init لإنشاء وحدة جديدة للوصول إلى الوحدة الخاصة التي نشرناها سابقًا. بعد إنشاء الوحدة، ستتمكن من استخدام الأمر go get لتنزيل الوحدة الخاصة بك؛ تمامًا كما لو كنت تتعامل مع وحدة عامة. استخدمنا أيضًا الأمر go run لتصريف وتشغيل البرنامج الذي يستخدم الوحدة الخاصة. الخاتمة نشرنا خلال هذه المقالة وحدة خاصة باستخدام لغة جو، وتعلمنا كيفية إعداد متطلبات المصادقة باستخدام بروتوكول HTTPS ومفتاح SSH من أجل الوصول إلى الوحدة الخاصة من شيفرة أخرى. استخدمنا أيضًا الوحدة الخاصة ضمن مشروع. ترجمة -وبتصرف- للمقال How to Use a Private Go Module in Your Own Project لصاحبه Kristin Davidson. اقرأ أيضًا المقال السابق: توزيع الوحدات Modules المكتوبة بلغة Go. كتابة أول برنامج (ومكتبة) لك باستخدام لغة البرمجة Go الدليل السريع إلى لغة البرمجة Go بناء البرامج المكتوبة بلغة جو Go وتثبيتها التعامل مع ملفات المستودع في BitBucket واستخدام Git
  25. تسمح العديد من لغات البرمجة الحديثة -بما في ذلك لغة جو- للمطورين بتوزيع مكتبات جاهزة للآخرين لاستخدامها في برامجهم. تستخدم بعض اللغات مستودعًا مركزيًا لتثبيت هذه المكتبات، بينما توزعها لغة جو من نفس مستودع التحكم في الإصدار version control repository المستخدم لإنشاء المكتبات. تستخدم لغة جو أيضًا نظام إصدار يسمى الإصدار الدلالي Semantic Versioning، ليوضح للمستخدمين متى وما هو نوع التغييرات التي أُجريت. يساعد ذلك المستخدمين على معرفة ما إذا كان الإصدار الأحدث من الوحدة آمنًا للترقية، وما إذا كان يساعد في ضمان استمرار عمل برامجهم مع الوحدة. سننشئ في هذه المقالة وحدةً برمجيةً جديدة باستخدام لغة جو وسننشرها، وسنتعلم استخدام الإصدار الدلالي، وسننشر إصدارًا دلاليًا من الوحدة التي أنشأناها. المتطلبات أن يكون لديك مساحة عمل خاصة في لغة جو، وإذا لم يكن لديك اتبع سلسلة المقالات التالية: تثبيت لغة جو Go وإعداد بيئة برمجة محلية على أبونتو Ubuntu. تثبيت لغة جو وإعداد بيئة برمجة محلية على نظام ماك macOS. تثبيت لغة جو وإعداد بيئة برمجة محلية على ويندوز. أن تكون على دراية بكيفية إنشاء الوحدات في لغو جو. يمكنك مراجعة مقالة كيفية استخدام الوحدات Modules في لغة جو Go. لديك معرفة مسبقة بنظام التحكم بالإصدار غيت Git. يمكنك مراجعة مقالة ما هو جيت Git؟. إنشاء مستودع فارغ باسم pubmodule للوحدة التي ستنشرها. إنشاء وحدة للتحضير لنشرها على عكس العديد من لغات البرمجة الأخرى، توزّع الوحدة المُنشأة باستخدام لغة جو مباشرةً من مستودع الشيفرة المصدر الذي يتضمنها بدلًا من مستودع حزم مستقل. يسهل هذا على المستخدمين العثور على الوحدات المشار إليها في التعليمات البرمجية الخاصة بهم وعلى المشرفين على الوحدة لنشر إصدارات جديدة من الوحدة الخاصة بهم. سننشئ في هذا القسم وحدةً جديدة، وسننشرها بعد ذلك لجعلها متاحةً للمستخدمين الآخرين. لنبدأ بإنشاء الوحدة الآن. سنستخدم بدايةً الأمر git clone لأخذ نسخة محلية من المستودع الفارغ الذي لا بُد أن نكون أنشأناه لأنه جزء من المتطلبات الأساسية. يمكن نسخ هذا المستودع إلى أي مكان نريده على جهاز الحاسب، لكن يميل العديد من المطورين إلى إنشاء مجلد خاص يتضمن جميع مشاريعهم. سنستخدم هنا مجلدًا باسم "projects". أنشئ مجلد "projects" وانتقل إليه: $ mkdir projects $ cd projects من مجلد "projects"، شغل الأمر git clone لنسخ المستودع إلى جهاز الحاسب: $ git clone git@github.com:your_github_username/pubmodule.git ستكون نتيجة تنفيذ هذا الأمر هي نسخ الوحدة إلى مجلد "pubmodule" داخل مجلد "projects". قد تتلقى تحذيرًا بأنك نسخت مستودعًا فارغًا، ولكن لا داعٍ للقلق بشأن ذلك: Cloning into 'pubmodule'... warning: You appear to have cloned an empty repository. انتقل الآن إلى المجلد "pubmodule": $ cd pubmodule سنستخدم الآن الأمر go mod init لإنشاء الوحدة الجديدة وتمرير موقع المستودع اسمًا للوحدة. يُعد التأكد من تطابق اسم الوحدة مع موقع المستودع أمرًا مهمًا، لأن هذه هي الطريقة التي تعثر بها أداة go mod init على مكان تنزيل الوحدة عند استخدامها في مشاريع أخرى: $ go mod init github.com/your_github_username/pubmodule ستظهر رسالة تؤكد عملية إنشاء الوحدة من خلال إعلامنا بأن الملف "go.mod" قد أُنشئ: go: creating new go.mod: module github.com/your_github_username/pubmodule سنستخدم الآن محرر نصوص مثل نانو nano، لإنشاء وفتح ملف يحمل نفس اسم المستودع "pubmodule.go". $ nano pubmodule.go يمكن تسمية هذا الملف بأي اسم، ولكن يُفضّل استخدام نفس اسم الحزمة لتسهيل معرفة مكان البدء عند العمل مع حزمة غير مألوفة، كما ينبغي أن يكون اسم الحزمة هو نفس اسم المستودع. بالتالي، عندما يُشير شخص ما إلى تابع أو نوع من تلك الحزمة، فإنها تتطابق مع اسم المستودع، مثل pubmodule.MyFunction. سيسهل ذلك عليهم معرفة مصدر الحزمة في حال احتاجوا إلى الرجوع إليها لاحقًا. نُضيف الآن الدالة Hello إلى الحزمة، والتي ستعيد السلسلة !Hello, You. ستكون هذه الدالة متاحةً لأي شخص يستورد الحزمة: package pubmodule func Hello() string { return "Hello, You!" } لقد أنشأنا الآن وحدةً جديدةً باستخدام go mod init مع اسم وحدة يتطابق مع اسم المستودع البعيد (github.com/yourgithubusername/pubmodule)، وأضفنا أيضًا ملفًا باسم "pubmodule.go" إلى الوحدة، مع دالة تُسمى Hello يمكن استدعاؤها من قِبل مستخدمي هذه الوحدة. سننشر في الخطوة التالية هذه الوحدة بهدف إتاحتها للآخرين. نشر الوحدة لقد حان الوقت لنشر الوحدة التي أنشأناها في الخطوة السابقة. نظرًا لأن وحدات لغة جو تُوزّع من نفس مستودعات الشيفرة التي تُخزّن فيها، فسوف نُودع commit الشيفرة في مستودع غيت المحلي وندفعه push إلى المستودع الخاص بنا على github.com/your_github_username/pubmodule، ولكن قبل ذلك من الجيد التأكد من عدم إيداع ملفات لا نريد إيداعها عن طريق الخطأ أو عدم الانتباه، لأنها ستُنشر علنًا عند دفع الشيفرة إلى غيت هب. يمكن إظهار جميع الملفات داخل مجلد "pubmodule" والتغييرات التي ستُنفّذ باستخدام الأمر التالي: $ git status سيبدو الخرج كما يلي: On branch main No commits yet Untracked files: (use "git add <file>..." to include in what will be committed) go.mod pubmodule.go يجب أن نرى ملف "go.mod" الذي أُنشئ بواسطة الأمر go mod init، وملف "pubmodule.go" الذي أنشأنا فيه دالة Hello. قد يكون اسم الفرع مختلفًا عن الخرج السابق اعتمادًا على كيفية إنشاء المستودع، وستكون الأسماء غالبًا إما main أو master. عندما نتأكد من أن الملفات التي نريدها فقط موجودة، يمكننا إدراج stage الملفات باستخدام git add وإيداعها بالمستودع باستخدام الأمر git commit: $ git add . $ git commit -m "Initial Commit" سيكون الخرج كما يلي: [main (root-commit) 931071d] Initial Commit 2 files changed, 8 insertions(+) create mode 100644 go.mod create mode 100644 pubmodule.go نستخدم الأمر git push لدفع الوحدة إلى مستودع غيت هب: $ git push سيكون الخرج كما يلي: Enumerating objects: 4, done. Counting objects: 100% (4/4), done. Delta compression using up to 8 threads Compressing objects: 100% (3/3), done. Writing objects: 100% (4/4), 367 bytes | 367.00 KiB/s, done. Total 4 (delta 0), reused 0 (delta 0), pack-reused 0 To github.com:your_github_username/pubmodule.git * [new branch] main -> main ستُدفع الوحدة إلى المستودع بعد تشغيل الأمر git push، وستكون متاحةً الآن لأي شخص آخر لاستخدامها. ستستخدم جو الشيفرة الموجودة في الفرع الافتراضي للمستودع على أنها شيفرة للوحدة إذا لم يكن لديك أيّة إصدارات منشورة. لا يهم ما إذا كان اسم الفرع الافتراضي هو main أو master أو أي شيء آخر، المهم هو الفرع الذي ضُبط على أنه فرع افتراضي. تعلّمنا في هذا القسم كيفية نشر وحدة في لغة جو على مستودع غيت هب من المستودع المحلي لإتاحتها للآخرين. واحدة من الأمور الأساسية التي يجب أخذها بالحسبان، هي التأكد من أن مستخدمي الوحدة يمكنهم استخدام إصدار مستقر منها. من المحتمل أن نرغب في إجراء تغييرات وإضافة ميزات إلى الوحدة من الآن فصاعدًا، ولكن إذا أجرينا هذه التغييرات دون استخدام مفهوم الإصدارات، فقد تتعطل شيفرة شخص ما يستخدم هذه الوحدة عن طريق الخطأ. لحل هذه المشكلة: يمكننا إضافة إصدارات إلى الوحدة عندما نصل إلى مرحلة جديدة في عملية التطوير، ومع ذلك، عند إضافة إصدارات جديدة يجب التأكد من اختيار رقم إصدار مفيد حتى يعرف المستخدمون ما إذا كان من الآمن لهم الترقية فورًا أم لا. الإصدار الدلالي يعطي رقم الإصدار التعبيري meaningful version number للمستخدمين فكرةً عن مدى تغير الواجهة العامة أو واجهة برمجة التطبيقات التي يتفاعلون معها. تنقل جو هذه التغييرات من خلال نظام أو مخطط إصدار versioning scheme يُعرف باسم الإصدار الدلالي semantic versioning -أو اختصارًا SemVer. يستخدم الإصدار الدلالي سلسلة الإصدار لنقل المعنى حول تغييرات الشيفرة، ومن هنا يأتي اسم الإصدار الدلالي. يُستخدم الإصدار الدلالي لتحديد الإصدارات الأحدث من الإصدار الحالي المُستخدم، وما إذا كان بالإمكان الترقية للإصدار الأحدث آليًا بأمان. يعطي الإصدار الدلالي كل رقم في سلسلة الإصدار معنى meaning. يحتوي الإصدار النموذجي في SemVer على ثلاثة أرقام أساسية: الإصدار الرئيسي major والإصدار الثانوي minor وإصدار التصحيح patch version. تُدمج كل من هذه الأرقام مع بعضها بواسطة النقطة (.) لتشكيل سلسلة الإصدار، مثلًا 1.2.3، تُرتّب فيه الأرقام وفقًا للآلية التالية: أول رقم للإصدار الرئيسي ثم الإصدار الثانوي ثم إصدار التصحيح أخيرًا. يمكنك بهذه الطريقة معرفة أيهما أحدث لأن الرقم الموجود في مكان معين أعلى من الإصدارات السابقة. على سبيل المثال، الإصدار 2.2.3 أحدث من 1.2.3 لأن الإصدار الرئيسي أعلى، وبالمثل، فإن الإصدار 1.4.3 أحدث من 1.2.10 لأن الإصدار الثانوي أعلى، وعلى الرغم من أن 10 أعلى من 3 في إصدار التصحيح، فإن الإصدار الثانوي 4 أعلى من 2، لذا فإن هذا الإصدار له الأسبقية. عندما يزداد رقم في سلسلة الإصدار، يُعاد ضبط جميع الأجزاء الأخرى من الإصدار التي تليها إلى 0. على سبيل المثال، تؤدي زيادة الإصدار الثانوي من 1.3.10 إلى 1.4.0 وزيادة الإصدار الرئيسي من 2.4.1 إلى 3.0.0. يسمح استخدام هذه القواعد للغة جو بتحديد إصدار الوحدة التي يجب استخدامها عند تشغيل go get. مثلًا، لنفرض أن لدينا مشروعًا يستخدم الإصدار 1.4.3 من الوحدة github.com/your_github_username/pubmodule. إذا كنا نعتمد على كون الوحدة pubmodule مستقرة، فقد نرغب فقط في ترقية إصدار التصحيح تلقائيًا 3.. إذا شغّلت الأمر: go get -u=patch github.com/your_github_username/pubmodule ستستنتج لغو جو أننا تريد ترقية إصدار التصحيح للوحدة، وستبحث فقط عن الإصدارات الجديدة مع 1.4 للجزء الرئيسي والثانوي من الإصدار. من المهم أن نضع في الحسبان كيف تغيرت واجهة برمجة التطبيقات العامة للوحدة عند إنشاء إصدار جديد منها، إذ ينقل لنا كل جزء من سلسلة الإصدار الدلالية نطاق التغيير في واجهة برمجة التطبيقات وكذلك للمستخدمين. تنقسم هذه الأنواع من التغييرات عادةً إلى ثلاث فئات مختلفة، تتماشى مع كل مكون من مكونات الإصدار؛ إذ تؤدي التغيرات الأصغر إلى زيادة إصدار التصحيح، وتزيد التغييرات متوسطة الحجم من الإصدار الثانوي، وتزيد التغييرات الأكبر في الإصدار الرئيسي. سيساعدنا استخدام هذه الفئات في تحديد رقم الإصدار المراد زيادته وتجنب تعطل break الشيفرة الخاصة بنا أو شيفرة أي شخص آخر يعتمد على هذه الوحدة. أرقام الإصدارات الرئيسية الرقم الأول في SemVer هو رقم الإصدار الرئيسي (1.4.3)، وهو أهم رقم يجب مراعاته عند إطلاق إصدار جديد من الوحدة. في هذا النوع من الإصدارات يكون هناك تغيّر كبير في الإصدار يُشير إلى تغيرات غير متوافقة backward-incompatible changes مع الإصدارات السابقة لواجهة برمجة التطبيقات العامة المستخدمة API؛ وقد يكون التغير غير المتوافق مع الإصدارات السابقة هو أي تغير يطرأ على الوحدة من شأنه أن يتسبب في تعطل breaking برنامج شخص ما إذا أجرى الترقية دون إجراء أي تغييرات أخرى. يمكن أن يمثّل التعطّل أي حالة فشل في البناء بسبب تغيير اسم دالة أو تغيير في كيفية عمل المكتبة، بحيث أن تابعًا أصبح يُعيد "v1" بدلًا من "1". هذا فقط من أجل واجهة برمجة التطبيقات العامة الخاصة بنا، ومع ذلك يمكن لشخص آخر استخدام أي أنواع أو توابع قد صُدّرت. إذا كان الإصدار يتضمن فقط تحسينات لن يلاحظها مستخدم المكتبة، فلا يحتاج إلى تغيير كبير في الإصدار. قد تكون إحدى الطرق لتذكر التغييرات التي تناسب هذه الفئة هي أخذ كل "تحديث" أو "حذف" بمثابة زيادة كبيرة في الإصدار. ملاحظة: على عكس الأنواع الأخرى من الأرقام في SemVer، فإن الإصدار الرئيسي 0 له أهمية خاصة إضافية، إذ يُعد إصدارًا "قيد التطوير in development". لا يُعد أي SemVer بإصدار رئيسي 0 مستقرًا، وأي شيء يمكن أن يتغير في واجهة برمجة التطبيقات في أي وقت. يُفضّل أن نبدأ دومًا بالإصدار الرئيسي 0 عند إنشاء وحدة جديدة، وتحديث الإصدارات الثانوية والإصدارات التصحيحية فقط حتى ننتهي من التطوير الأولي للوحدة. بعد الانتهاء من تغيير واجهة برمجة التطبيقات العامة للوحدة وعدّها مستقرةً للمستخدمين، حان الوقت لبدء الإصدار 1.0.0. لنأخذ الشيفرة التالية مثالًا على الشكل الذي قد يبدو عليه تغيير الإصدار الرئيسي. لدينا دالة تسمى UserAddress تقبل حاليًا string معاملًا وتعيد string: func UserAddress(username string) string { // تُعيد عنوان المستخدم مثل سلسلة } تُعيد الدالة حاليًا سلسلة، وقد يكون من الأفضل لنا وللمستخدمين إذا أعادت الدالة السابقة بنيةً struct مثل Address*، إذ يمكن بهذه الطريقة تضمين بيانات إضافية (مثل الرمز البريدي) وبطريقة منظمة: type Address struct { Address string PostalCode string } func UserAddress(username string) *Address { // تُعيد عنوان المستخدم والرمز البريدي مثل بنية } قد يكون هذا مثالًا على تغيير رئيسي في الإصدار لأنه سيتطلب من المستخدمين إجراء تغييرات على التعليمات البرمجية الخاصة بهم من أجل استخدامها. سيكون الأمر نفسه صحيحًا إذا قررنا حذف الدالة UserAddress، لأن المستخدمين سيحتاجون إلى تحديث التعليمات البرمجية الخاصة بهم لاستخدام البديل. مثال آخر على تغيير الإصدار الرئيسي هو إضافة معامل جديد إلى دالة UserAddress على الرغم من أنها ما زالت تُعيد string: func UserAddress(username string, uppercase bool) string { // true تُعيد عنوان المستخدم مثل سلسلة بأحرف كبيرة إذا كان المعامل المنطقي قيمته } نظرًا لأن هذا التغيير يتطلب أيضًا من المستخدمين تحديث التعليمات البرمجية الخاصة بهم إذا كانوا يستخدمون دالة UserAddress، فسيتطلب ذلك أيضًا زيادة كبيرة في الإصدار. لن تكون كل التغييرات التي نجريها على الشيفرة جذرية، إذ سنُجري أحيانًا تغييرات على واجهة برمجة التطبيقات العامة API، بحيث نُضيف دوالًا أو قيمًا جديدة، ولكن هذا لا يغير أيًا من الدوال أو القيم الحالية. أرقام الإصدارات الثانوية الرقم الثاني في إصدار SemVer هو رقم الإصدار الثانوي (1.4.3)، وهنا يطرأ تغيير بسيط في الإصدار للإشارة إلى التغييرات المتوافقة مع الإصدارات السابقة لواجهة برمجة التطبيقات العامة الخاصة بنا. سيكون التغيير المتوافق مع الإصدارات السابقة أي تغيير لا يؤثر على التعليمات البرمجية أو المشاريع التي تستخدم الوحدة الحالية. على غرار رقم الإصدار الرئيسي؛ يؤثر هذا فقط على واجهة برمجة التطبيقات العامة. قد تكون إحدى الطرق لتذكر التغييرات التي تناسب هذه الفئة، هي أي شيء يعد "إضافة"، ولكن ليس "تحديثًا". باستخدام نفس المثال السابق الذي شرحنا فيه رقم الإصدار الرئيسي، تخيل أن لديك تابع باسم UserAddress يُعيد سلسلة string: func UserAddress(username string) string { //تُعيد عنوان المستخدم مثل سلسلة } هنا بدلًا من تحديث UserAddress بجعله يُعيد Address*، سنضيف تابع جديد تمامًا يسمى UserAddressDetail: type Address struct { Address string PostalCode string } func UserAddress(username string) string { // تُعيد عنوان المستخدم مثل سلسلة } func UserAddressDetail(username string) *Address { // تُعيد عنوان المستخدم والرمز البريدي مثل بنية } لا تتطلب إضافة الدالة الجديدة UserAddressDetail إجراء تغييرات من قِبل المستخدمين إذا حدّثوا إلى هذا الإصدار من الوحدة، لذلك ستُعد زيادة بسيطة في رقم الإصدار. يمكن للمستخدمين الاستمرار في استخدام UserAddress وسيحتاجون فقط إلى تحديث التعليمات البرمجية الخاصة بهم إذا كانوا يفضلون تضمين المعلومات الإضافية من UserAddressDetail. من المحتمل ألا تكون تغييرات واجهة برمجة التطبيقات العامة هي المرة الوحيدة التي نُطلق فيها إصدارًا جديدًا من الوحدة. تُعد الأخطاء bugs جزءًا لا مفر منه من تطوير البرامج، ورقم إصدار التصحيح موجود للتستر على تلك الثغرات. أرقام إصدارات التصحيح الرقم الأخير في صيغة SemVer هي إصدار التصحيح، (1.4.3). تغيير إصدار التصحيح هو أي تغيير لا يؤثر على واجهة برمجة التطبيقات العامة API للوحدة. تكون غالبًا التغييرات التي لا تؤثر على واجهة برمجة التطبيقات العامة للوحدة، أشياءً مثل إصلاحات الأخطاء أو إصلاحات الأمان. بالعودة إلى الدالة UserAddress من الأمثلة السابقة، لنفترض أن إصدارًا من الوحدة يفتقد إلى جزء من العنوان في السلسلة التي تُعيدها الدالة. إذا أطلقنا إصدارًا جديدًا من الوحدة لإصلاح هذا الخطأ، سيؤدي ذلك إلى زيادة إصدار التصحيح فقط، ولن يتضمن الإصدار أيّة تغييرات في كيفية استخدام المستخدم لواجهة برمجة التطبيقات العامة UserAddress، وإنما مُجرد تعديلات لضمان صحة البيانات المُعادة. يُعد اختيار رقم إصدار جديد بعناية طريقة مهمة لكسب ثقة المستخدمين، إذ يُظهر استخدام الإصدار الدلالي للمستخدمين مقدار العمل المطلوب للتحديث إلى إصدار جديد، وبالتأكيد لن تفاجئهم عن طريق الخطأ بتحديث يكسر برنامجهم. بعد التفكير في التغييرات التي أجريناها على الوحدة، وتحديد رقم الإصدار التالي المطلوب استخدامه، يمكننا الآن نشر الإصدار الجديد وإتاحته للمستخدمين. نشر إصدار جديد من الوحدة سنحتاج إلى تحديث الوحدة بالتغييرات التي نخطط لإجرائها قبل نشر أي إصدار جديد من الوحدة. لن نكون قادرين على تحديد أي جزء من الإصدار الدلالي يجب أن يزداد دون أن نُجري تغييرات. بالنسبة للوحدة التي أنشأناها، سنضيف التابع Goodbye لاستكمال التابع Hello، وبعد ذلك ستنشر هذا الإصدار الجديد للاستخدام. افتح ملف "pubmodule.go" وضِف التابع الجديد Goodbye إلى واجهة برمجة التطبيقات العامة API: package pubmodule func Hello() string { return "Hello, You!" } func Goodbye() string { return "Goodbye for now!" } سنحتاج الآن إلى التحقق من التغييرات التي يُتوقع أن تُنفّذ عن طريق تنفيذ الأمر التالي: $ git status يوضّح الخرج أن التغيير الوحيد في الوحدة الخاصة بنا، هو التابع الذي أضفناه إلى ملف "pubmodule.go": On branch main Your branch is up to date with 'origin/main'. Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git restore <file>..." to discard changes in working directory) modified: pubmodule.go no changes added to commit (use "git add" and/or "git commit -a") نضيف بعد ذلك التغيير إلى الملفات المُدرجة ونجري التغيير في المستودع المحلي باستخدام git add و git commit: $ git add . $ git commit -m "Add Goodbye method" سيكون الخرج كما يلي: [main 3235010] Add Goodbye method 1 file changed, 4 insertions(+) سنحتاج إلى دفع التغييرات بعد تنفيذها إلى مستودع غيت هب الخاص بنا، وتكون عادةً هذه الخطوة مختلفة قليلًا عند العمل على مشروع برمجي أكبر أو عند العمل مع مطورين آخرين في مشروع. عند إجراء تطوير على ميزة جديدة، يُنشئ المطور فرع غيت جديد لإجراء تلك التغييرات وتحضيرها إلى أن تصبح هذه الميزة مستقرة وجاهزة للإصدار، وبعدها سيأتي مطور آخر ويُراجع هذه التغييرات ضمن ذلك الفرع للتأكد أو لاكتشاف المشكلات التي ربما غفل عنها المطور الأول. يُدمج الفرع الخاص بتلك الميزة في الفرع الافتراضي، مثل master أو main بمجرد الانتهاء من المراجعة، وتُجمع هذه التغييرات في الفرع الافتراضي حتى يحين وقت نشر إصدار جديد. هنا لا تمر الوحدة بهذه العملية (إضافة ميزة جديدة في فرع آخر)، لذا سيكون دفع التغييرات التي أجريناها على المستودع، هو فقط التغييرات التي أجريناها: $ git push سيكون الخرج على النحو التالي: numerating objects: 5, done. Counting objects: 100% (5/5), done. Delta compression using up to 8 threads Compressing objects: 100% (3/3), done. Writing objects: 100% (3/3), 369 bytes | 369.00 KiB/s, done. Total 3 (delta 0), reused 0 (delta 0), pack-reused 0 To github.com:your_github_username/pubmodule.git 931071d..3235010 main -> main يوضح الخرج أن الشيفرة الجديدة جاهزة للاستخدام في الفرع الافتراضي من قبل المستخدمين. كل ما فعلناه حتى الآن يُطابق ما فعلناه عند نشر الوحدة في البداية. الآن، تأتي نقطة مهمة في عملية إطلاق الإصدارات، وهي اختيار رقم الإصدار. إذا نظرنا إلى التغييرات التي أجريناها على الوحدة، فإن التغيّر الوحيد على واجهة برمجة التطبيقات العامة (أو أي تغيير) هو إضافة التابع Goodbye إلى الوحدة. بما أنه يمكن للمستخدم التحديث من الإصدار السابق الذي كان يحتوي فقط على الدالة Hello، دون إجراء تغييرات من جانبهم، سيكون هذا التغيير متوافقًا مع الإصدارات السابقة. قد يعني ذلك (ضمن مفهوم الإصدار الدلالي) التغيير المتوافق مع الإصدارات السابقة لواجهة برمجة التطبيقات العامة زيادةً في رقم الإصدار الثانوي. هذا الإصدار هو الإصدار الأول من الوحدة التي نشرناها، لذلك ليس هناك إصدار سابق لنزيد عليه. عمومًا، إذا كان الإصدار الحالي هو 0.0.0 أي "لا يوجد إصدار"، ستقودنا زيادة الإصدار الثانوي إلى الإصدار 0.1.0، وهو الإصدار التالي من الوحدة التي أنشأناها. الآن، بعد أن أصبح لدينا رقم إصدار نُعطيه للوحدة، أصبح بإمكاننا استخدامه مع وسوم غيت لنشر إصدار جديد. عندما يستخدم المطورون غيت لتتبع شيفرة المصدر الخاصة بهم (حتى في لغات أخرى غير جو)، فإن العرف الشائع هو استخدام وسوم غيت لتتبع الشيفرة التي أُطلقت لإصدار معين؛ فبهذه الطريقة يمكنهم استخدام الوسم إذا احتاجوا في أي وقت إلى إجراء تغييرات على إصدار قديم. بما أن لغة جو تُنزّل الوحدات من المستودعات المصدرية، بالتالي تستطيع الاستفادة من هذه الخاصية في استخدام وسوم الإصدار نفسها. لنشر نسخة جديدة من الوحدة التي أنشأناها باستخدام هذه الوسوم، سنضع وسمًا على الشيفرة التي نطلقها باستخدام الأمر git tag، وستحتاج أيضًا إلى تقديم وسم الإصدار مثل وسيط لهذا الأمر. لإنشاء وسم الإصدار، نبدأ بالبادئة v ونضيف SemVer بعدها مباشرةً. في حالتنا، سيكون وسم الإصدار النهائي هو v0.1.0. شغل الأمر git tag لتمييز الوحدة التي أنشأناها بوسم الإصدار هذا: $ git tag v0.1.0 سنحتاج -بعد إضافة وسم الإصدار محليًا- إلى دفع هذا الوسم إلى مستودع غيت هب باستخدام git push مع origin: $ git push origin v0.1.0 بعد نجاح تنفيذ الأمر git push، سترى أن الوسم v0.1.0 قد أُنشئ: Total 0 (delta 0), reused 0 (delta 0), pack-reused 0 To github.com:your_github_username/pubmodule.git * [new tag] v0.1.0 -> v0.1.0 يوضح الخرج أعلاه أن الوسم السابق قد أَُضيف، وأن مستودع غيت هب الخاص بنا يحتوي على الوسم الجديد v0.1.0، وسيكون متاحًا لمستخدمي الوحدة الوصول إليه. بعد نشر إصدار جديد من الوحدة واستخدام الأمر git tag، لن يكون المستخدم بحاجة إلى تنزيل إصدار جديد بناءً على أحدث إيداع تعمية hash من الفرع الافتراضي، وذلك عندما يرغب بالحصول على أحدث إصدار من الوحدة باستخدام الأمر go get. بعد إطلاق إصدار الوحدة، ستبدأ أداة go في استخدام هذه الإصدارات لتحديد أفضل طريقة لتحديث الوحدة، ويتيح لنا ذلك إلى جانب الإصدار الدلالي، إمكانية تكرار الوحدات وتحسينها مع تزويد المستخدمين بتجربة متسقة ومستقرة. الخاتمة أنشأنا خلال هذه المقالة وحدةً برمجيةً عامة باستخدام لغة جو ونشرناها في مستودع غيت هب حتى يتمكن الآخرون من استخدامها، واستخدمنا أيضًا مفهوم الإصدار الدلالي لتحديد رقم إصدار مناسب للوحدة. وسّعنا أيضًا دوال هذه الوحدة، ونشرنا الإصدار الجديد اعتمادًا على مفهوم الإصدار الدلالي مع ضمان عدم تعطّل البرامج التي تعتمد عليها. ترجمة -وبتصرف- للمقال How to Distribute Go Modules لصاحبه Kristin Davidson. اقرأ أيضًا المقال السابق: كيفية استخدام الوحدات Modules في لغة جو Go. كتابة أول برنامج (ومكتبة) لك باستخدام لغة البرمجة Go الدليل السريع إلى لغة البرمجة Go بناء البرامج المكتوبة بلغة جو Go وتثبيتها
×
×
  • أضف...