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

هدى جبور

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

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

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

9 متابعين

آخر الزوار

2295 زيارة للملف الشخصي

إنجازات هدى جبور

عضو نشيط

عضو نشيط (3/3)

23

السمعة بالموقع

  1. يقود الذكاء الاصطناعي Artificial Intelligence في يومنا هذا طليعة التقدم التقني ويمثل قفزة ثورية في الطريقة التي يمكن بها للآلات وأنظمة الحاسب محاكاة الذكاء البشري وأداء المهام التي تتطلّب عادةً ذكاءًا بشريًّا. يشمل هذا المجال مجموعة واسعة من المجالات مثل الروبوتات ومعالجة اللغات الطبيعية والرؤية الحاسوبية والتعلم الآلي وعلوم البيانات ..إلخ، ويؤثر على جوانب مختلفة من المجتمع، من الأعمال والرعاية الصحية إلى الترفيه والتعليم. يستكشف هذا المقال إيجابيات وسلبيات الذكاء الاصطناعي، ويسلط الضوء على الطرق العديدة التي يؤثر بها على عالمنا والتحديات التي يفرضها، فعلى الرغم من أن الذكاء الاصطناعي يوفر إمكانيات مهمة لتحسين الكفاءة والدقة والابتكار، فإنه في نفس الوقت يثير المخاوف بشأن إزاحة الوظائف والتحيز وانتهاك الخصوصية والاعتبارات الأخلاقية الأخرى. إيجابيات الذكاء الاصطناعي يُقدم الذكاء الاصطناعي وتطبيقاته المتنوعة العديد من المزايا المهمة التي لها تأثيرات جذرية على مختلف القطاعات. يتعمق هذا القسم في الجوانب الإيجابية للذكاء الاصطناعي، ويسلط الضوء على كيفية إحداثه ثورة في الصناعات وتغيير الطريقة التي نعيش بها ونعمل بها. إيجابيات الذكاء الاصطناعي في مجال التعليم أدخل الذكاء الصناعي العديد من التطورات الإيجابية في مجال التعليم، مما أدى إلى تغيير الطريقة التي يتعلم بها الطلاب ويدرّس بها المعلمون ومن أهم هذه الفوائد. التعلم المخصّص: تستطيع خوارزميات الذكاء الاصطناعي تحليل أنماط التعلم الفردي وتفضيلاته، وتسمح بتوفير بتجارب تعليمية مخصصة. حيث يمكن للطلاب التقدم في وتيرة التعلم بالسرعة التي تناسبهم، مع التركيز على المجالات التي يحتاجون إلى تحسين فيها، وهذا ما يؤدي إلى عملية تعليمية أكثر كفاءة وفعالية. منصات التعلّم المتكفية: تعمل منصات التعلم المتكيفة المدعومة بالذكاء الاصطناعي على ضبط مستوى صعوبة المحتوى التعليمي في الزمن الحقيقي بناءً على أداء الطلاب. وهذا يضمن أن الطلاب يواجهون التحديات المناسبة لمستوياتهم مما يعزز فهمهم ومعرفتهم. أنظمة التدريس الذكية: توفر أنظمة التدريس المعتمدة على الذكاء الاصطناعي تعليقات وتوجيهات مخصصة للطلاب على غرار المعلم البشري. وتقدم هذه الأنظمة مساعدة فورية، حيث تساعد الطلاب في الواجبات المنزلية وحل المشكلات التي قد تصادفهم وتعزيز التعلم الذاتي. التقييم التلقائي: يمكن لخوارزميات الذكاء الاصطناعي تقييم المهام والاختبارات وتصنيفها بسرعة ودقة وتوفر وقت المعلمين، مما يسمح لهم بالتركيز على تقديم تعليقات مفيدة ودعم للطلاب. الفصول الافتراضية والتعلم عن بعد: يسهل الذكاء الاصطناعي الفصول الدراسية الافتراضية وتجارب التعلم عبر الإنترنت، كما تساعد روبوتات الدردشة الذكية الطلاب في الإجابة على أسئلتهم، وتقوم التحليلات المدعومة بالذكاء الاصطناعي بتتبع تقدم الطالب، بالتالي ضمان بيئة تعليمية سلسة وتفاعلية عن بعد. أدوات تعلم اللغات: تساعد أدوات تعلّم اللغات المستندة إلى الذكاء الاصطناعي متعلمي اللغة من خلال توفير الترجمة وتوجيهات النطق وتمارين اللغة، وتعمل هذه الأدوات على تعزيز مهارات اكتساب اللغة وتسهيلها. أنظمة التدخل المبكر: يمكن لخوارزميات الذكاء الاصطناعي تحديد الطلاب الذين يواجهون صعوبات أكاديمية وتتيح أنظمة التدخل المبكر للمعلمين تقديم الدعم في الوقت المناسب، مما يضمن حصول الطلاب على المساعدة التي يحتاجونها لتحقيق النجاح. إنشاء محتوى تعليمي: يستطيع الذكاء الاصطناعي إنشاء محتوى تعليمي، بما في ذلك عمليات المحاكاة التفاعلية والاختبارات والبرامج التعليمية. ويمكن للمعلمين الاستفادة من هذه الموارد لتعزيز أنشطة الفصل الدراسي وإشراك الطلاب في تجارب تعليمية تفاعلية. دعم ذوي الاحتياجات الخاصة: تعمل بعض تقنيات الذكاء الاصطناعي، مثل التعرف على الكلام وتطبيقات تحويل النص إلى كلام، على جعل المواد التعليمية في متناول الطلاب ذوي الاحتياجات الخاصة وتوفر بيئة تعليمية أكثر شمولاً، مما يضمن إمكانية مشاركة جميع فئات الطلاب بشكل كامل في العملية التعليمية. من خلال الاستفادة من قدرات الذكاء الاصطناعي، يمكن لمجال التعليم أن يتطور أكثر، وأن يقدم حلولًا مبتكرة تعزز نتائج التعلّم وتدعم المعلمين. زيادة الكفاءة والأتمتة يلعب الذكاء الاصطناعي دورًا أساسيًّا في تعزيز الكفاءة والأتمتة في مختلف القطاعات والصناعات. ويظهر تأثيرها الإيجابي بعدة طرق: أتمتة المهام: تعمل الأنظمة التي تعمل بالذكاء الصناعي على أتمتة المهام المتكررة، مما يقلل من التدخل البشري في الأنشطة العادية. لا يوفر هذا الوقت فحسب، بل يضمن أيضًا الدقة وتقليل الأخطاء التي قد تنشأ عن العمليات اليدوية. تقليل التكلفة: تؤدي أتمتة المهام من خلال تقنيات الذكاء الصناعي إلى خفض التكلفة بشكل كبير، حيث يمكن للشركات تحقيق إنتاج أعلى بموارد أقل، مما يؤدي إلى توفير كبير في التكاليف التشغيلية. السرعة والدقة: تعالج أنظمة الذكاء الصناعي المعلومات بسرعات غير مسبوقة، حيث يمكنهم تحليل بيانات ضخمة وإجراء حسابات معقدة بسرعة ودقة، مما يؤدي إلى عمليات اتخاذ قرار أسرع. العمل على مدار الساعة طوال أيام الأسبوع: يمكن للأدوات والأنظمة التي تعمل بالذكاء الصناعي أن تعمل على مدار الساعة دون تعب، مما يضمن استمرار الخدمة دون انقطاع. تعتبر هذه القدرة ذات قيمة خاصة في قطاعات مثل دعم العملاء والأمن السيبراني التي تحتاج إلى تواجد دائم ويقظة مستمرة. التخصيص: تتيح تقنيات الذكاء الصناعي تخصيص المنتجات والخدمات والتوصية بما يناسب منها بناءً على التفضيلات الفردية، وهذا ما يعزز تجارب العملاء، وبالتالي ارتفاع معدلات الرضا وزيادة ولاء العملاء للعلامة التجارية. مراقبة الجودة: في عمليات التصنيع والإنتاج، تقوم الأنظمة المدعومة بالذكاء الصناعي بمراقبة الجودة في الزمن الحقيقي. أي انحرافات أو عيوب تُكتشف على الفور، بالتالي ضمان وصول المنتجات عالية الجودة فقط إلى السوق. تحسين سير العمل: يقوم الذكاء الصناعي بتحليل أساليب سير العمل ويقترح التحسينات. بالتالي، ومن خلال تحديد الاختناقات وأوجه القصور في سير العمل، ويمكن للشركات إعادة تصميم العمليات لتعزيز الإنتاجية والفعالية بشكل عام. العمليات عن بعد: تُسهّل الأتمتة المعتمدة على الذكاء الصناعي العمليات والإدارة عن بعد. وهذه الميزة مفيدة بشكل خاص في قطاعات مثل المرافق، حيث يمكن مراقبة البنية التحتية والتحكم فيها من مواقع مركزية، بالتالي تعزيز الكفاءة والاستجابة عمومًا. إن زيادة الكفاءة والأتمتة من خلال تقنيات الذكاء الصناعي لا تعمل على تبسيط العمليات فحسب، بل تمهد الطريق أيضًا للابتكار، وهذا ما يسمح للشركات والصناعات بالازدهار في ظل هذا التنافس العالمي الكبير. تحسين عملية صنع القرار لقد أثر اندماج الذكاء الاصطناعي بشكل كبير على عمليات صنع القرار في مختلف القطاعات، وهذا يوفر العديد من المزايا: تحليل البيانات وتفسيرها: يمكن لخوارزميات الذكاء الاصطناعي معالجة كميات هائلة من البيانات في الزمن الحقيقي، وهذا ما يتيح للشركات جمع الرؤى من مجموعات البيانات الكبيرة بسرعة ودقة، مما يساعد في اتخاذ قرارات فعّالة. التحليلات التنبؤية: تستخدم أنظمة الذكاء الصناعي النمذجة التنبؤية لتوقع الاتجاهات المستقبلية للأسواق بناءً على البيانات السابقة. يساعد هذا التحليل التنبؤي الشركات على توقع تغيرات السوق وسلوكيات العملاء وأنماط الطلب، بالتالي إمكانية اتخاذ قرارات استباقية. إدارة المخاطر: تعمل خوارزميات الذكاء الاصطناعي على تقييم المخاطر من خلال تحليل البيانات السابقة. يساعد هذا التحليل الشركات في مختلف الصناعات (مثل شركات التمويل و التأمين) على تقييم المخاطر بشكل أكثر دقة وتطوير استراتيجيات فعالة لتخفيف المخاطر. كشف الاحتيال: يمكن لأنظمة الذكاء الصناعي اكتشاف العمليات المرتبطة بالأنشطة الاحتيالية في الزمن الحقيقي، وهذا مفيد بشكل خاص في القطاع المالي، حيث تقوم خوارزميات الذكاء الصناعي بتحليل المعاملات وسلوكيات المستخدم لتحديد الاحتيال المحتمل، بالتالي تجنبها من خلال التدخل السريع. إيجابيات الذكاء الاصطناعي في المجال الطبي لقد قدم الذكاء الصناعي العديد من المزايا التي تعزز رعاية المرضى وتبسّط العمليات وتساهم في البحوث الطبية. فيما يلي بعض الإيجابيات الرئيسية للذكاء الاصطناعي في المجال الطبي: الكشف المبكر عن الأمراض: يمكن لخوارزميات الذكاء الاصطناعي تحليل مجموعات واسعة من البيانات بما في ذلك السجلات الطبية وفحوصات التصوير، وذلك لاكتشاف حالات معينة وتحديد الأمراض المحتملة في مرحلة مبكرة، بالتالي المساعدة في الكشف المبكر والتدخل والعلاج في الوقت المناسب، وهذا ماينعكس إيجابًا على فرص الشفاء. التشخيص الدقيق: تعمل أدوات التشخيص الطبي المدعومة بالذكاء الاصطناعي، على تحسين دقة التشخيص وتساعد هذه الأدوات متخصصي الرعاية الصحية في تفسير الصور المعقدة مثل الأشعة السينية والرنين المغناطيسي، وبالتالي الحصول على تشخيصات أكثر دقة وموثوقية. خطط العلاج الشخصية: يقوم الذكاء الاصطناعي بتحليل بيانات المرضى لإنشاء خطط علاجية مخصصة لهم، وذلك من خلال النظر في العوامل الفردية للمريض مثل علم الوراثة والتاريخ الطبي ونمط الحياة. يساعد الذكاء الاصطناعي الأطباء على تصميم العلاجات لتحقيق أقصى قدر من الفعالية. اكتشاف الأدوية وتطويرها: يعمل الذكاء الاصطناعي على تسريع اكتشاف الأدوية من خلال تحليل البيانات البيولوجية والتنبؤ بالفعالية المحتملة للمركبات الجديدة. يؤدي هذا إلى تسريع عملية البحث والتطوير واكتشاف علاجات جديدة لمختلف الأمراض. التحليلات التنبؤية لنتائج المرضى: يمكن لمقدمي الرعاية الصحية استخدام أدوات التحليل التنبؤية للتدخل بشكل استباقي، بالتالي ضمان حصول المرضى على الرعاية والموارد المناسبة في الوقت المناسب. الجراحة الروبوتية: يمكن أن تساعد الأنظمة الروبوتية المعتمدة على الذكاء الاصطناعي الجراحين أثناء الإجراءات الجراحية المعقدة، مما يعزز الدقة ويقلل من الأخطاء البشرية ويحسن النتائج عمومًا، إذ يمكن لهذه الأنظمة أداء المهام بدقة عالية. إدارة بيانات الرعاية الصحية: يعمل الذكاء الصناعي على تبسيط إدارة بيانات الرعاية الصحية من خلال تنظيم وتحليل كميات هائلة من بيانات المرضى وأتمتتها. المراقبة عن بعد: تتيح بعض الأجهزة التي يمكن ارتداؤها والتي تعمل بالذكاء الاصطناعي -حلولًا للمراقبة عن بعد والتتبع المستمر للعلامات الحيوية للمرضى والمقاييس الصحية. يضمن ذلك التدخل في الوقت المناسب ويقلل الحاجة إلى الزيارات المتكررة إلى المستشفى. تحسين الأبحاث والتجارب السريرية: يمكن استخدام الذكاء الصناعي لمحاكاة التجارب السريرية، مما يوفر وقتًا وجهدًا ويقلل من التكلفة. كما يمكن للذكاء الصناعي الاستفادة من البيانات السريرية الضخمة وتحليلها بسرعة ودقة، مما يساعد الباحثين في فهم الأمراض والاستنتاجات الطبية. يوفر الذكاء الاصطناعي في المجال الطبي فرصًا لا مثيل لها لتعزيز تقديم الرعاية الصحية وتحسين نتائج المرضى ودفع عجلة التقدم الطبي. تمهد هذه التطورات الطريق لنظام رعاية صحية أكثر تخصيصًا وفعالية وسهولة في الحصول عليه. إيجابيات الذكاء الاصطناعي في البحث العلمي والتطوير يعمل الذكاء الاصطناعي على تسريع البحث العلمي من خلال معالجة مجموعات البيانات المعقدة ومحاكاة التجارب واكتشاف العلاقات المعقدة بين الأشياء. إنه يلعب دورًا هامًا في مجالات مثل اكتشاف الأدوية وعلوم المواد وعلم الجينوم وغيرها الكثير. إيجابيات الذكاء الاصطناعي في الأمن السيبراني تكتشف خوارزميات الذكاء الاصطناعي تهديدات الأمن السيبراني وتمنعها في الزمن الحقيقي Real Time. كما تعمل نماذج التعلم الآلي على تحليل سلوكيات الشبكة وتحديد الحالات الشاذة وحماية البيانات الحساسة، مما يضمن اتخاذ تدابير حماية قوية. تسلط هذه الإيجابيات الضوء على التأثير الكبير للذكاء الاصطناعي في مختلف مجالات الحياة، وهذا ما يمهد الطريق لحلول مبتكرة وتحسين الكفاءات في مختلف المجالات.لكن من ناحية أخرى وإلى جانب كل هذه الفوائد، يطرح الذكاء الاصطناعي تحديات وأسئلة أخلاقية تتطلب مراجعة دقيقة أيضًا. لنتناول في القسم التالي بعض هذه القضايا. دورة الذكاء الاصطناعي احترف برمجة الذكاء الاصطناعي AI وتحليل البيانات وتعلم كافة المعلومات التي تحتاجها لبناء نماذج ذكاء اصطناعي متخصصة. اشترك الآن سلبيات الذكاء الاصطناعي نظرا للتقدم الكبير في أبحاث الذكاء الاصطناعي، فقد أصبحنا على حافة عصر الروبوتات المستقلة وإنترنت الأشياء منذ عام 2005 تقريبا. بالتالي، فإننا نواجه بشكل متسارع تبعات الذكاء الاصطناعي في الحياة اليومية. فمن ينوي التخصص في مجال الذكاء الاصطناعي، أن يتعامل أيضًا مع التأثيرات والاعتبارات الأخلاقية والاجتماعية لهذا التخصص. إذًا وعلى الرغم من أن الذكاء الصناعي يجلب لعالمنا تطورات ملحوظة، ولكنه يجلب أيضًا تحديات وقضايا كبيرة. ويعد فهم هذه السلبيات أمرًا بالغ الأهمية لاتخاذ قرارات دقيقة بشأن استخدام الذكاء الاصطناعي وتنظيمه. الإزاحة الوظيفية والبطالة أدى ظهور الذكاء الاصطناعي إلى ظهور المخاوف بشأن تقلص فرص الوظائف وزيادة البطالة. وهذا ليس أمرًا حديثًا فمنذ يناير/كانون الثاني 2016، أصدر المنتدى الاقتصادي العالمي دراسة تنبأت بأن صعود "الصناعة 4.0" سيؤدي إلى إزاحة أكثر من خمسة ملايين وظيفة في غضون خمس سنوات.واليوم تزداد هذه المخاوف نظرا للأتمتة المتزايدة في مختلف القطاعات مثل المصانع والمكاتب ووسائل النقل والمنازل، والمعتمدة على الذكاء الاصطناعي بشكل كبير. على السطح، يبدو هذا الأمر بمثابة نعمة، حيث يحرر البشر من المهام المجهدة جسديًا وغير السارة. تَعِد الأتمتة بزيادة الكفاءة وانخفاض التكاليف وربما المزيد من الرخاء. لكن هذه الرؤية المثالية تصطدم بالواقع. فعلى الرغم من الإنتاجية المتزايدة، لا يزال الناس يعملون لساعات طويلة ويواجهون التوتر والإرهاق وانخفاض الأجور الحقيقية كما أن الضغوط التنافسية تجبر الشركات على خفض التكاليف، غالبًا عن طريق تسريح العمال، مما يؤدي إلى البطالة وزيادة الضغوط على القوى العاملة كما أن ارتفاع الإنتاجية لا ينعكس على العمال ولا يساهم في تحسين أجورهم ويتسبب في تراكم الثروة بين القلة تاركًا لأغلبية تكافح من أجل تغطية نفقاتها ومسببًا تفاوتًا في المستوى الاقتصادي بين طبقات المجتمع. المخاوف الأخلاقية والتحيزيّة تبرز المخاوف الأخلاقية والتحيزيّة في عالم الذكاء الاصطناعي كتحديات كبيرة تتطلب اهتمامًا عاجلًا. فإحدى القضايا البارزة هي التحيز الخوارزمي Algorithmic bias. إذ ترث أنظمة الذكاء الاصطناعي عن غير قصد في كثير من الأحيان التحيزات الموجودة في البيانات التي تدربت عليها ويمكن أن تؤدي هذه التحيزات التي تعكس التحيزات المجتمعية إلى معاملة غير عادلة في مختلف التطبيقات، مثل عمليات التوظيف أو أنظمة العدالة الجنائية. من جانبٍ آخر يشكل الافتقار إلى الشفافية في قرارات الذكاء الاصطناعي معضلة أخلاقية حيث تعمل العديد من خوارزميات الذكاء الاصطناعي كصناديق سوداء يصعب فهم كيفية وصولها إلى استنتاجات أو قرارات محددة ما يعيق القدرة على كشف وتصحيح نتائجها المتحيزة. كما أن المسؤولية في تطوير الذكاء الاصطناعي من الجوانب الهامة التي تحتاج إلى دراسة متأنية فتحديد المسؤول عندما يخطئ نظام الذكاء الاصطناعي أو يتسبب في ضرر ما أمر معقد، ولكنه ضروري لضمان الممارسات الأخلاقية والحماية من اتخاذ القرارات المتحيزة. إن معالجة هذه المخاوف الأخلاقية والتحيزية يتطلب اتباع نهج يتضمن الشفافية والمساءلة والتدقيق المستمر لبناء تطبيقات ذكاء اصطناعي عادلة وشفافة وخاضعة للمساءلة أمام المجتمع الذي تخدمه. التكاليف الأولية المرتفعة والاعتماد على التكنولوجيا يتطلب تنفيذ تقنيات الذكاء الاصطناعي استثمارات مالية كبيرة لذا تحتاج الشركات والمنظمات إلى تخصيص ميزانيات كبيرة للبحث والتطوير وتكامل أنظمة الذكاء الاصطناعي ضمن بنيتها التحتية الحالية. تشمل نفقات التنفيذ هذه التكاليف المرتبطة بتوظيف المتخصصين المهرة والحصول على الأجهزة المتقدمة وترخيص البرامج المتطورة ..إلخ. كما أن عمليات التحسين والصيانة المستمرة تشكل التزامات مالية مستمرة. تتطلب التحديثات المنتظمة وإصلاحات الأخطاء وتكييف أنظمة الذكاء الاصطناعي مع الاحتياجات المتطورة تمويلًا ثابتًا. من ناحية أخرى، هناك قلق بالغ بشأن مخاطر الاعتماد المفرط على أنظمة الذكاء الاصطناعي، ففي حين أن تكنولوجيا الذكاء الاصطناعي يمكن أن تعزز الكفاءة وعمليات صنع القرار، فإن الاعتماد المفرط عليها يمكن أن يؤدي إلى خلق نقاط ضعف. إن الاعتماد بشكل كبير على الذكاء الاصطناعي قد يؤدي إلى تآكل المهارات البشرية والتفكير النقدي، وقد يجعل الخبرة البشرية ثانوية بالنسبة للخوارزميات الآلية. لذا يثير هذا الاعتماد المفرط على الآلات الذكية تساؤلات حول العواقب طويلة المدى المترتبة على تضاؤل المشاركة البشرية في العمليات المهمة ولا شك أن تحقيق التوازن بين تبني إمكانات الذكاء الاصطناعي وفهم حدوده أمر ضروري لتجنب الاعتماد غير المبرر والمخاطر المرتبطة به. الافتقار إلى الذكاء العاطفي تكمن إحدى العيوب الكبيرة للذكاء الاصطناعي في افتقاره إلى الذكاء العاطفي وهي السمة المتجذّرة بعمق في التفاعلات البشرية. فأنظمة الذكاء الاصطناعي وعلى الرغم من خوارزمياتها وقدراتها المتقدمة، تكافح من أجل فهم مدى تعقيد المشاعر الإنسانية. ويبرز هذا القيد بشكل خاص في تطبيقات خدمة العملاء التي يعد التعاطف أمرًا محوريًا فيها في بعض الحالات. فحلول خدمة العملاء المعتمدة على الذكاء الاصطناعي وعلى الرغم من كفاءتها في التعامل مع الاستفسارات الروتينية، إلا أنها غالبًا ما تفشل في فهم الفروق الدقيقة في المشاعر الإنسانية، وهذا قد يشعر العملاء بالخيبة والإحباط. وفي قطاعي الرعاية الصحية، يشكل غياب الذكاء العاطفي في أدوات الذكاء الاصطناعي تحديًا كبيرًا. فقد يشعر المرضى الذين يبحثون عن فهم تعاطفي أثناء الاستشارات الطبية أو جلسات العلاج أن واجهات الذكاء الاصطناعي لا تفهمهم ولا تدرك ما يشعرون به، وهذا قد يؤثر على تجربة المريض بشكل عام. فعجز الذكاء الاصطناعي عن فهم تعقيدات المشاعر الإنسانية يثير أسئلة أخلاقية ويؤكد على أهمية دمج التعاطف البشري في تقنيات الذكاء الاصطناعي لتحسين جودة تفاعلات العملاء وتقديم خدمات الرعاية الصحية. المخاطر الأمنية وقضايا الخصوصية لقد جلب ظهور الذكاء الصناعي معه عددًا لا يحصى من المخاوف المتعلقة بالأمن والخصوصية. إحدى المشكلات الأكثر إلحاحًا هي التعرض لخروقات البيانات وتهديدات الأمن السيبراني، فمع اعتماد أنظمة الذكاء الصناعي بشكل متزايد على كميات هائلة من البيانات الحساسة، أصبح احتمال الوصول غير المصرح به والهجمات الضارة مصدر قلق كبير. تؤدي هذه الانتهاكات إلى تعريض المعلومات الشخصية للخطر، ويمكن أن يكون لها أيضًا عواقب بعيدة المدى تؤثر على الشركات والحكومات والأفراد على حدٍ سواء. إضافةً إلى ذلك فإن شبح الوصول غير المصرح به إلى أنظمة الذكاء الاصطناعي يلوح في الأفق. فقد يستغل المُخترقون Hackers ذوو النوايا الشريرة نقاط الضعف في خوارزميات الذكاء الاصطناعي أو البنى التحتية، ويحصلون على سيطرة غير مصرح بها على هذه الأنظمة. يثير هذا مخاوف بشأن سلامة البيانات ويشكل أيضًا مخاطر كبيرة على وظائف وسلامة التطبيقات التي تعتمد على الذكاء الاصطناعي، وخاصة تلك المستخدمة في القطاعات الحيوية مثل الرعاية الصحية أو التمويل. من جانب آخر، يؤدي انتشار الذكاء الاصطناعي في حياتنا اليومية إلى إثارة مخاوف بشأن انتهاكات الخصوصية إذ يمكن لتقنيات الذكاء الاصطناعي، وخاصة تلك الموجودة في كاميرات المراقبة والمنصات عبر الإنترنت، مراقبة وتحليل سلوك الأفراد، مما يؤدي إلى انتهاك الخصوصية على نطاق غير مسبوق. وقد شعلت هذه مخاوف إساءة استخدام البيانات الشخصية المناقشات حول الآثار الأخلاقية للذكاء الاصطناعي، ودفعت المجتمعات والحكومات إلى إصدار لوائح تنظيمية صارمة لحماية خصوصية الأفراد لتسخير إمكانات الذكاء الاصطناعي لتحقيق التقدم المجتمعي وليس للاستغلال أو الانتهاك. التأثير البيئي يعد التأثير البيئي للذكاء الاصطناعي مصدر قلق متزايد أيضًا. فإحدى القضايا المهمة هي استهلاك الطاقة الهائل المرتبط بأنظمة الذكاء الاصطناعي. إذ تتطلب الحسابات والخوارزميات التي تعمل في مراكز البيانات كميات كبيرة من الكهرباء، مما يساهم في ارتفاع انبعاثات الكربون وإجهاد موارد الطاقة العالمية. بالإضافة إلى ذلك، أدى انتشار أجهزة الذكاء الاصطناعي إلى زيادة كبيرة في النفايات الإلكترونية. فغالبًا ما ينتهي الأمر بأجهزة الذكاء الاصطناعي التي يتم التخلص منها، مثل الهواتف الذكية القديمة والأدوات المنزلية الذكية، في مدافن النفايات، وهذا ما يشكل مخاطر بيئية بسبب مكوناتها السامة والتخلص غير السليم من هذه الأجهزة يزيد من تلويث البيئة ويعرض النظم البيئية للخطر. عمومًا، هناك بصيص من الأمل وسط هذه التحديات، يتجلى من خلال ظهور مبادرات الاستدامة وممارسات "الذكاء الاصطناعي الأخضر". إذ تركز الشركات ومختبرات البحث اليوم على إنشاء تقنيات ذكاء اصطناعي موفرة للطاقة وصديقة للبيئة. وتتضمن هذه المبادرات تطوير الخوارزميات والأجهزة التي تتطلب طاقة حسابية أقل، وبالتالي تقليل استهلاك الطاقة. إضافةً إلى ذلك، هناك تركيز على إعادة التدوير والتخلص المسؤول من أجهزة الذكاء الاصطناعي لتقليل النفايات الإلكترونية، كما يجري استكشاف المواد المستدامة وعمليات التصنيع الجديدة لإنشاء أجهزة ذكاء اصطناعي صديقة للبيئة. إن الجهود المستمرة في مبادرات الاستدامة والذكاء الاصطناعي الأخضر تمهد الطريق لمستقبل أكثر مسؤولية من الناحية البيئية. من خلال الابتكار والوعي والتعاون، يمكن لصناعة التكنولوجيا والمجتمع ككل التخفيف من العواقب البيئية السلبية للذكاء الاصطناعي وضمان التعايش المتناغم بين التكنولوجيا والطبيعة. التنقل في المستقبل: التحديات والفرص في عصر السيارات الروبوتية غالبًا ما يُشار إلى فقدان سائقي سيارات الأجرة لوظائفهم على أنه أحد عيوب السيارات الآلية. من شبه المؤكد أنه لن يكون هناك سائقي سيارات أجرة اعتبارًا من عام 2030 فصاعدًا، لكن هذا ليس مشكلة بالضرورة. كما ذكرنا سابقًا، يحتاج مجتمعنا فقط إلى التعامل مع الإنتاجية المكتسبة حديثًا والتطورات بشكل صحيح. بالإضافة إلى المزايا العديدة التي تجلبها لنا السيارات ذاتية القيادة أو السيارات الآلية، تواجه هذه السيارات مشكلتين خطيرتين. أولًا، ما يسمى "بتأثير الارتداد" الذي سوف يلغي على الأقل بعض المكاسب في توفير الموارد والطاقة والوقت. ففترات القيادة الأقصر بالإضافة إلى القيادة الأكثر راحة والأرخص ستغرينا بالقيادة أكثر. ولا يمكننا التعامل مع هذه المشكلة إلا من خلال إعادة التفكير في موقفنا تجاه الاستهلاك ونوعية الحياة واستخدام كامل الوقت المدخر لمزيد من الأنشطة هناك مشكلة أخرى يجب أن نأخذها على محمل الجد وهي أن السيارات الآلية ستحتاج إلى أن تكون متصلة بالشبكة وهذا قد يمنح المخترقين القدرة على الوصول إلى عناصر التحكم في المركبات والتلاعب بها من خلال الثغرات الأمنية في بروتوكولات الشبكة. إذا تمكن أحد المخترقين من القيام بذلك مرة واحدة، فيمكنه تكرار الهجوم على نطاق واسع، مما قد يؤدي إلى توقف أساطيل المركبات بأكملها أو التسبب في وقوع حوادث أو التجسس على ركاب المركبات أو الشروع في أعمال إجرامية أخرى. هنا كما هو الحال في مجالات أخرى مثل التشغيل الآلي للمنزل وإنترنت الأشياء، ستكون هناك حاجة إلى خبراء في أمن تكنولوجيا المعلومات لضمان أعلى ضمانات أمنية ممكنة باستخدام أدوات التجارة مثل أساليب التشفير. بالمناسبة، ستكون خوارزميات التعلم الآلي مفيدة في اكتشاف هجمات القرصنة. بعد أن ناقشنا أهم إيجابيات وسلبيات الذكاء الاصطناعي يمكن القول أن الراحة والرخاء التي سينتجها لنا الذكاء الاصطناعي لا يخلو من كثير من التحديات التي علينا مواجهتها لتجنب أي خطر قد يعرض البشرية والكوكب للخطر. خاتمة عند الحديث عن الذكاء الاصطناعي، يتضح لنا أن تقنيات الذكاء الاصطناعي تقدم فوائد ملحوظة مثل زيادة الكفاءة وحلول الرعاية الصحية المتقدمة والعمليات التجارية المبسطة ..إلخ، إلا أن ذلك يترافق مع تحديات من أبرزها إزاحة الوظائف والمخاوف الأخلاقية والمخاطر الأمنية. يعد فهم هذه الإيجابيات والسلبيات أمرًا بالغ الأهمية لاتخاذ قرارات مستنيرة بشأن اندماج الذكاء الاصطناعي بطريقة آمنة في حياتنا والحرص على تحقيق التوازن بين تسخير فوائد الذكاء الصناعي والتخفيف من تحدياته. كما يتعين على الشركات وصناع السياسات أن يعملوا بشكل تعاوني لتحقيق الممارسات الأخلاقية ومعالجة التحيزات وضمان الشفافية. ويتعين على الباحثين الحرص على توفير حلول ذكاء اصطناعي أخلاقية وآمنة وغير متحيزة بجانب تطويره، وإنشاء مبادرات تعليمية لتعزيز الفهم العام للذكاء الاصطناعي، كما ينبغي على صناع السياسات أن يتعاونوا على مستوى العالم لإنشاء لوائح تنظيمية قوية تضمن التطوير الأخلاقي والاستخدام الصحيح للذكاء الاصطناعي وبناء مستقبل يفيد فيه الذكاء الاصطناعي البشرية ككل. اقرأ أيضًا تعلم الذكاء الاصطناعي أهمية الذكاء الاصطناعي مجالات الذكاء الاصطناعي فوائد الذكاء الاصطناعي برمجة الذكاء الاصطناعي مستقبل الذكاء الاصطناعي لغات برمجة الذكاء الاصطناعي
  2. الرؤية الحاسوبيّة 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
  3. في هذا العصر الذي تتسارع فيه وتيرة التطور التقني، يُمثّل الذكاء الاصطناعي 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 وتحليل البيانات وتعلم كافة المعلومات التي تحتاجها لبناء نماذج ذكاء اصطناعي متخصصة. اشترك الآن خاتمة لقد برز الذكاء الاصطناعي كشريك لا غنى عنه في رحلتنا المستمرة نحو المستقبل كإنجاز تقني فريد وتجسيد للإمكانيات العظيمة للعقول البشرية. من الإبداع الصناعي إلى تحسين الرعاية الصحية، ومن الأتمتة الصناعية إلى استكشاف الفضاء والواقع الافتراضي، وأصبح الذكاء الاصطناعي جزءًا لا يتجزأ من حياتنا اليومية ومنافسًا حقيقيًا في عدة ميادين. ولا يمكن تجاهل كل هذا التقدم في مجالات التعرّف على الصور والروبوتات ومعالجة اللغات الطبيعية والألعاب، ما يفتح أمامنا أفقًا جديدًا من الإمكانيات ويدفعنا لتعلم واستخدام الذكاء الاصطناعي لنحقق من خلاله أهدافنا وطموحاتنا البشرية. ولنتطلع بثقة وتفاؤل نحو هذا المستقبل المشرق، حيث يبقى الذكاء الاصطناعي مصدر إلهام يشكل الأساس لابتكاراتنا ورحلتنا المستمرة نحو التقدم والتطور. اقرأ أيضًا الذكاء الاصطناعي: أهم الإنجازات والاختراعات وكيف أثرت في حياتنا اليومية أساسيات الذكاء الاصطناعي مستقبل الذكاء الاصطناعي برمجة الذكاء الاصطناعي مستقبل البرمجة
  4. من المرجّح أنك سمعت بمصطلح الذكاء الاصطناعي Artificial Intelligence أو اختصارًا AI كثيرًا في الآونة الأخيرة نظرًا للتطورات المذهلة التي وصل إليها هذا المجال. ولكن ما هو الذكاء الاصطناعي بالضبط، وما أهمية الذكاء الاصطناعي؟ بالنسبة للشق الأول من السؤال، فلقد أجبنا عليه بالتفصيل سابًقا في مقالة الذكاء الاصطناعي: دليلك الشامل، إذ يشير الذكاء الاصطناعي في جوهره إلى قدرة الآلات على تقليد أو محاكاة السلوك البشري، بغية أداء المهام التي يقوم بها الإنسان. ومن الضروري أن نلاحظ أنه على الرغم من أن الذكاء الاصطناعي يمكن أن يحاكي جوانب معينة من الذكاء البشري، إلا أنه يفتقر إلى الوعي والعواطف البشرية. إذ يمتلك البشر الحدس والإبداع والفهم العاطفي وهي صفات لا يستطيع الذكاء الاصطناعي محاكاتها، وبغض النظر عن مدى تقدمه. من أداء ثديبدو مذهلًا إلا أن الذكاء الاصطناعي يعمل ضمن المعايير التي حددها مبرمجوه والبيانات التي تم تدريبه عليها، وهذا لا يتطابق مع تعقيد عمليات التفكير البشري. بالنسبة للشق الثاني من السؤال، فهذا هو موضوع مقالتنا هذه المرة. فقد أحدث الذكاء الصناعي ضجة كبيرة في عالمنا لأنه يمثل تحولًا مذهلًا في طريقة عيشنا وعملنا وتفاعلنا مع التقنية. متميّزًا عن أي ابتكار سابق، فإن الذكاء الصناعي قادر على إحداث ثورة في الصناعات وحل المشكلات المعقدة والارتقاء بحياتنا اليومية وتسهيلها بطرق لم يكن من الممكن تصورها من قبل فالكفاءة والدقة والإمكانيات اللامحدودة هو ما يجعل الذكاء الاصطناعي موضوعًا مهمًا ومثيرًا للغاية. نكتشف معًا في هذه المقالة الأسباب الكامنة وراء هذا الضجيج، ونستكشف سبب أهمية الذكاء الاصطناعي وكيف يعيد تشكيل عالمنا. أهمية الذكاء الاصطناعي: النقاط التي تجعل الذكاء الاصطناعي مهمًا الذكاء الاصطناعي ليس مجرد كلمة طنانة بل هو قوة تحويلية تعيد تشكيل الطريقة التي نعيش بها ونعمل ونتفاعل مع العالم. وسنستكشف في الفقرات التالية التأثير العميق للذكاء الاصطناعي على مجتمعنا ولماذا يعد فهم أهميته أمرًا ضروريًا للجميع. الذكاء الاصطناعي وحل المشكلات: تطبيقات العالم الحقيقي لعل أحد الأسباب الأساسية لأهمية الذكاء الاصطناعي هي قدرته على حل المشكلات التي نواجهها. حيث يمكن لأنظمة الذكاء الاصطناعي تحليل كميات هائلة من البيانات وتحديد الأنماط التي قد تفوتها عقولنا البشرية. مما يوفر لنا هذه القدرة إمكانية برمجة تطبيقات ثورية في مجالات مثل الرعاية الصحية والنقل والأعمال والصناعة والزراعة وإنتاج المحاصيل، ولاشك أن براعة الذكاء الاصطناعي في حل المشكلات يعد مساعدة عظيمة لمعالجة التحديات الصعبة التي تواجه البشرية. الذكاء الاصطناعي والكفاءة: توفير الوقت والموارد تخيل عالماً تتعامل فيه مع المهام المتكررة المملة بسلاسة وسرعة، ويسمح لك بالتركيز على الإبداع والابتكار. يجعل الذكاء الاصطناعي هذا الأمر ممكنًا من خلال أتمتة المهام التي كانت تستغرق منك وقتًا طويلاً. على سبيل المثال في الصناعات، تعمل الروبوتات التي تعتمد على الذكاء الاصطناعي على تبسيط عمليات التصنيع وتحسين الكفاءة وخفض تكاليف الإنتاج. وفي مجال خدمة العملاء، تستجيب روبوتات المحادثة المدعومة بالذكاء الاصطناعي للاستفسارات بشكل فوري، مما يعزز تجربة المستخدمين ويوفر الوقت والموارد. الذكاء الاصطناعي في المستقبل: رسم ملامح المرحلة القادمة يعد الذكاء الاصطناعي حافزًا للابتكار، ويولد أفكارًا وتطبيقات عديدة كانت تعتبر في السابق خيالًا علميًا. فمن السيارات ذاتية القيادة التي تَعِد بطرق أكثر أمانًا إلى الفن والموسيقى التي يولدها الذكاء الاصطناعي والتي تتحدى مفاهيمنا عن الإبداع، فإن الاحتمالات لا حدود لها. كما تعمل الأبحاث المعتمدة على الذكاء الاصطناعي على تسريع الاكتشافات العلمية وتساعد في تحسين مجالات مثل الطب وعلوم البيئة. ومثل هذه الابتكارات لا ترتقي بحياتنا فحسب، بل تمهد الطريق أيضًا لمستقبل تكون فيه الإمكانات البشرية لا حدود لها. إن فهم القوة التحويلية للذكاء الاصطناعي لا يقتصر على مجرد مسألة محو الأمية الرقمية بل هو جانب هام لكي تكون مشاركًا مستنيرًا ونشطًا في العالم الحديث. ونتعمق في الأقسام التالية أكثر في أهمية الذكاء الاصطناعي، ونستكشف كيف ساهم في تحويل الصناعات وإحداث ثورة في الطريقة التي نعيش بها ونعمل بها، كما نكشف عن التأثير الاستثنائي للذكاء الاصطناعي في حياتنا اليومية. الذكاء الاصطناعي في الحياة اليومية لقد دمج الذكاء الاصطناعي نفسه بسلاسة في نسيج روتيننا اليومي، ليشكل بهدوء الطريقة التي نعيش بها ونتفاعل مع العالم. نستكشف في هذا القسم الجوانب المختلفة للذكاء الاصطناعي في حياتنا اليومية، ونسلط الضوء على الطرق التي يؤثر بها علينا. كيف يؤثر الذكاء الاصطناعي عليك يوميًا يؤثر الذكاء الاصطناعي على يومك منذ اللحظة التي تستيقظ فيها. فهو يُسهّل روتينك الصباحي من خلال التنبؤ بطرق حركة المرور، مما يضمن وصولك إلى وجهتك بسرعة، أو أثناء قيامك بتصفح موجز الوسائط الاجتماعية الخاص بك، إذ تعمل خوارزميات الذكاء الاصطناعي على تنظيم المحتوى وتخصيصه وفقًا لاهتماماتك وتجعل تجربتك عبر الإنترنت أكثر فائدة وسهولة وجاذبية. الذكاء الاصطناعي موجود في هواتفنا الذكية يعد هاتفك الذكي، ذلك الرفيق الموجود في كل مكان، مثالًا رئيسيًا على اندماج الذكاء الاصطناعي في حياتنا اليومية. إذ يستجيب المساعدون الافتراضيون الصوتيين المدعومون بالذكاء الاصطناعي مثل سيري Siri وأليكسا Alexa ومساعد جوجل Google Assistant لأوامرك الصوتية ويساعدونك في جدولة المواعيد وتوفير المعلومات في الزمن الحقيق، كما يكمل النص التنبؤي المعتمد على الذكاء الاصطناعي الجمل التي تكتبها، مما يجعل إرسال الرسائل النصية أسرع وأكثر سهولة. أهمية الذكاء الاصطناعي في الرعاية الصحية بالنسبة لمجال الرعاية الصحية، تأتي أهمية الذكاء الاصطناعي من إمكانية تحليل مجموعات كبيرة من البيانات لتعزيز دقة التشخيص ومساعدة الأطباء في تحديد الأمراض في مراحل مبكرة. وتتنبأ خوارزميات الذكاء الاصطناعي بتفشي الأمراض، مما يساعد في التخطيط المسبق للرعاية الصحية. أهمية الذكاء الاصطناعي في النقل تأتي أهمية الذكاء الاصطناعي في مجال النقل من إمكانية تشغيل أنظمة الملاحة وتحسين المسارات في الزمن الحقيقي والتنبؤ بأنماط حركة المرور، وهذا ما يضمن أن تكون رحلتك فعّالة وخالية من التوتر أو المشاكل. كما حققت صناعة السيارات في السنوات الـ 130 الماضية تقدمًا ملحوظًا، مما أدى إلى تغيير الطريقة التي نتحرك بها. في ألمانيا مثلًا، يمتلك أكثر من نصف السكان الآن سيارة خاصة بهم، مما يوفر لهم إمكانية تنقل غير مسبوقة للعمل والأنشطة اليومية والترفيه. هذا الاعتماد على المركبات لا يمكن إنكاره، خاصة في المناطق ذات وسائل النقل العام المحدودة مثل الأرياف. يعد مستقبل النقل بمزيد من الراحة من خلال دمجه مع أنواع مختلفة من الذكاء الاصطناعي إذ ستصبح السيارات الكهربائية ذاتية القيادة أو السيارات الروبوتية، حقيقة واقعة وستنقلنا هذه المركبات ذاتية القيادة إلى أي وجهة تقريبًا، مما يسمح للركاب بالقراءة أو العمل أو الاسترخاء أثناء الرحلة، وفي حين توفر وسائل النقل العام وسائل راحة مماثلة، فإن السيارات الآلية ستوفر هذه الحرية على أي طريق وفي أي وقت. إضافة إلى ذلك ستظهر سيارات الأجرة الآلية التي لا تحتاج لسائق والتي يمكننا استدعاؤها وقت الحالجة من خلال تطبيق هاتف ذكي بسيط، وسيكون لدينا خيار السفر بمفردنا أو مشاركة الرحلة مع الآخرين، مما يلغي الحاجة إلى السيارات الشخصية تمامًا. ولا يوفر لنا هذا التحول المال فحسب، بل يريحنا أيضًا من مسؤوليات مختلفة مثل التزود بالوقود والصيانة والتنظيف ومعضلات ركن السيارات، مما يعد بمستقبل التنقل السهل والفعال من حيث التكلفة. وإلى جانب المكاسب الفورية في مجال الراحة والملاءمة، ستوفر السيارات الآلية مزايا كبيرة أخرى. على سبيل المثال، وفقاً لدراسة أجرتها شركة ماكينزي، سنحتاج إلى عدد أقل بكثير من السيارات، وفي المقام الأول، سنحتاج إلى عدد أقل بكثير من أماكن ركن السيارات في عصر السيارات ذاتية القيادة، وهو ما سيؤدي إلى انخفاض هائل في استهلاك الموارد. ووفقًا لدراسة أجراها مختبر لورانس بيركلي الوطني، ستتسبب السيارات الكهربائية ذاتية القيادة في انخفاض بنسبة 90% في انبعاثات غازات الاحتباس الحراري لكل ميل مسافر بسبب كفاءة استخدام الطاقة في المركبات والتوافق الأمثل بين السيارة والغرض منها. وبالتالي ستكون سيارات الأجرة الآلية أكثر صداقة للبيئة من الحافلات الثقيلة مثلًا، والتي غالبًا ما تعمل بسعة منخفضة، خاصة في المناطق الريفية. بشكل عام، سوف تساهم سيارات الأجرة الروبوتية بشكل كبير في توفير الطاقة. وستكون سلامة الركاب أعلى بكثير مما هي عليه اليوم، ويقدر الخبراء حاليًا معدلات الحوادث المستقبلية بين صفر وعشرة بالمائة مقارنة باليوم فلن يكون هناك قيادة عاطفية أو غضب على الطريق أو قيادة مشتتة. أهمية الذكاء الاصطناعي في الترفيه: البث والألعاب تُحدث خوارزميات الذكاء الاصطناعي ثورة في مجال الترفيه وتبرز أهمية الذكاء الاصطناعي في منصات البث للتوصية بالأفلام والعروض المناسبة لاهتمام المستخدمين، حيث تكون مصممة خصيصًا لسجل المشاهدة الخاص بك، وبالتالي ستظهر أمامك المحتويات التي تهمك دومًا. من الناحية الأخرى، تبرز أهمية الذكاء الاصطناعي في عالم صناعة الألعاب وقدرته على تكييف طريقة اللعب بناءً على تصرفات اللاعبين. أهمية الذكاء الاصطناعي في الأعمال تستفيد الشركات من الذكاء الاصطناعي لتبسيط العمليات وتعزيز خدمة الدعم الفني وتحسين عملية صنع القرار. إذ توفر روبوتات الدردشة Chatbots المدعومة بالذكاء الاصطناعي، دعمًا فوريًا للعملاء وحل الاستفسارات بكفاءة. تقوم التحليلات المعتمدة على الذكاء الاصطناعي بفحص كميات هائلة من البيانات، واستخراج رؤى قيّمة تفيد في اتخاذ قرارات الأعمال الاستراتيجية، مما يضمن بقاء الشركات قادرة على المنافسة في سوق سريع التغير، ولا يقتصر تأثير الذكاء الصناعي على هذه المجالات فحسب، بل يمتد ليشمل كل جانب من جوانب حياتنا تقريبًا. ننتقل في القسم التالي للحديث عن أهمية الذكاء الاصطناعي في مجال مهم وهو الابتكار والإبداع. الابتكار والإبداع على عكس المخاوف من أن تحل الآلات محل البشر أو الإبداع البشري، فإن الذكاء الاصطناعي أداة قوية لتعزيز قدراتنا الإبداعية وتضخيمها وليس استبدالها. فقد كان الابتكار دائمًا قوة دافعة وراء تقدم وتطور المجتمعات على مر العصور، ولطالما كان هناك عناصر وعوامل تُنشّط وتعزز الابتكار في كل حقبة، فسابقًا كان لازدهار الدولة الإسلامية في بغداد السبب الرئيسي لظهور علماء وابتكارات عربية مازالت مرتبطة بأسماء النجوم حتى الآن. مثال آخر، وفي وقت ليس ببعيد، كان لاختراع الكهرباء سببًا مباشرًا لآلاف الابتكارات الأخرى. أما اليوم فلدينا الذكاء الاصطناعي، الذي بدون أدنى شك، هو كهرباء هذه الحقبة. حيث يعمل الذكاء الاصطناعي كمحفز للابتكارات التكنولوجية والعلمية التي من شأنها أن ترتقي بمجتمعاتنا من خلال تقديم وجهات نظر وحلول جديدة للمشاكل أو المهام القديمة ولا شك أن قدرة الذكاء الصناعي على معالجة وتحليل البيانات الضخمة في وقت قصير، سيؤدي إلى مزيد من الاكتشافات والابتكارات الرائدة. دورة الذكاء الاصطناعي احترف برمجة الذكاء الاصطناعي AI وتحليل البيانات وتعلم كافة المعلومات التي تحتاجها لبناء نماذج ذكاء اصطناعي متخصصة. اشترك الآن أهمية الذكاء الاصطناعي في الفن والموسيقى والتصميم لقد حقق الذكاء الاصطناعي نجاحات كبيرة في عالم الإبداع. على سبيل المثال، في عالم الفن، تولد خوارزميات الذكاء الاصطناعي لوحات ومنحوتات وقطع فنية رقمية مذهلة. هذه الإبداعات، التي لا يمكن تمييزها غالبًا عن تلك التي صنعها الفنانون البشريون في الدقة والجمالية. من ناحية أخرى، يعد التأليف الموسيقي المعتمد على الذكاء الاصطناعي مجالًا مثيرًا آخر يؤكّد لنا مدى أهمية الذكاء الاصطناعي في هذا الحقل، حيث تقوم الخوارزميات بتحليل الأنماط الموسيقية وإنشاء مقطوعات موسيقية ملهمة. وفي مجال التصميم الجرافيكي، تساعد أدوات الذكاء الاصطناعي المصممين المحترفين من خلال أتمتة المهام المتكررة وتسمح لهم بالتركيز على التفكير والتصور. إطلاق العنان للإبداع باستخدام أدوات الذكاء الاصطناعي أحد الجوانب الرائعة للذكاء الاصطناعي هو قدرته على تمكين الأفراد من إطلاق العنان لإبداعهم. إذ يمكن الآن بسهولة الوصول إلى العديد من أدوات ومنصات الذكاء الاصطناعي، مما يمكّن الأشخاص ذوي الخبرة التقنية المحدودة من استخدام قدارات الذكاء الاصطناعي. وتتراوح هذه الأدوات من تطبيقات تحرير الصور التي تستخدم الذكاء الاصطناعي لتحسين الصور، إلى روبوتات الدردشة التي تساعد المؤلفين على توسيع وتطوير الأفكار. ومن تسهيل إمكانية الوصول إلى تكنولوجيا الذكاء الاصطناعي، سيصبح الإبداع بلا حدود ومتاحًا لأي شخص لديه شرارة من الخيال. يعمل الذكاء الاصطناعي بمثابة لوحة يمكن للإبداع البشري أن يزدهر عليها، فهو يوفر وسيلة لاستكشاف مناطق مجهولة وتجربة أفكار جديدة وتحويل كل ما يدور بخيالنا لأفكار واقعية، ما يعد بمستقبل لا يعرف فيه الابتكار حدودًا. تبني ثورة الذكاء الاصطناعي في ختام مقالتنا عن أهمية الذكاء الاصطناعي، يصبح من الواضح أن تبني ثورة الذكاء الاصطناعي ليس مجرد خيار، بل هي ضرورة للأفراد والمجتمعات على حد سواء. فتبني الذكاء الصناعي يعني تبني التغيير والتكيف مع التطور التكنولوجي والتعرّف على الإمكانات المذهلة التي يحملها الذكاء الصناعي. ومن خلال فهم قدراته ومحدوياته يمكننا تسخير قوته لخلق مستقبل أفضل. واعتماد الذكاء الصناعي ينطوي على تحوّل في عقليتنا وتفكيرنا، والاعتراف بأن التقنية هي أداة يمكنها حل المشكلات المعقدة وتحسين حياتنا شريطة استخدامها بشكل مسؤول. وأنت كقارئ وعضو في المجتمع، تلعب دورًا هامًا في تشكيل مستقبل الذكاء الاصطناعي، وينبغي أن تبقى على اطلاع دائم بتطورات الذكاء الاصطناعي، وتطرح الأسئلة حوله وتشارك في المناقشات حول آثاره المجتمعية وتشجّع التعليم والوعي حول الذكاء الصناعي في مجتمعك. أفكار أخيرة حول أهمية الذكاء الاصطناعي في هذا النسيج الكبير للتقدم البشري المُمتد على مليارات السنين، يعد الذكاء الاصطناعي خيطًا جديدًا ينسج طريقه إلى كل جانب من جوانب حياتنا. فهو لم يعد مفهومًا بعيدًا ولكنه أصبح واقعًا حاليًّا يؤثر على خياراتنا ويدفع الابتكار ويحل التحديات التي كانت تعتبر في السابق مستحيلة الحل، وبينما نمضي قدمًا، دعونا نتعامل مع الذكاء الصناعي بتوازن بين الحماس والمسؤولية. ومن خلال القيام بذلك، يمكننا إطلاق العنان لإمكاناته الكاملة. في الختام، تذكر أن أهمية الذكاء الاصطناعي تتجاوز مجالات التقينة وتؤثر على كل مجالات الحياة وعلينا أن نتقبل ثورة الذكاء الصناعي بعقل متفتح وقلب مفعم بالأمل ونأمل أنها ستجعل مستقبلنا أكثر إشراقًا. اقرأ أيضًا أساسيات الذكاء الاصطناعي: دليل المبتدئين مجالات الذكاء الاصطناعي مكتبات وأطر عمل الذكاء الاصطناعي: القوة الكامنة خلف الأنظمة الذكية لغات برمجة الذكاء الاصطناعي فوائد الذكاء الاصطناعي
  5. لاشك أن المجتمع اليوم مفتون بشدة بالذكاء الاصطناعي ، خاصةً مع التقدم الأخير في معالجة اللغات الطبيعية 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. اقرأ أيضًا مكتبات وأطر عمل الذكاء الاصطناعي: القوة الكامنة خلف الأنظمة الذكية خوارزميات الذكاء الاصطناعي الذكاء الاصطناعي: مراحل البدء والتطور والأسس التي نشأ عليها تعلم الذكاء الاصطناعي
  6. يعد الذكاء الاصطناعي 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
  7. لقد انتقل الذكاء الاصطناعي التوليدي 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 برمجة الذكاء الاصطناعي: بناء مستقبل الآلات الذكية
  8. إذا كنا بحاجة إلى عرض البيانات بتنسيقات منظمة مثل التقارير النصية أو صفحات 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 المخصصة وقوالبها.
  9. قدمت لغة جو في الإصدار 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
  10. يُعد بروتوكول 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
  11. يكرّس العديد من المطورين جزءًا من وقتهم لبناء خوادم تُسهّل توزيع المحتوى عبر الإنترنت. يُعد بروتوكول النقل التشعبي 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.
  12. شهد الذكاء الاصطناعي 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 تُستخدم في التحليل المكاني، حيث تُمكّن المستخدمين من معالجة البيانات المكانية وتحليلها. هذه كانت مجموعة من أهم مكتبات الذكاء الاصطناعي. تعمل هذه المكتبات بشكل جماعي على تمكين المطورين من إنشاء تطبيقات ذكاء اصطناعي معقدة عبر مجالات متنوعة. من خلال الاستفادة من قدرات هذه المكتبات، يمكن لممارسي الذكاء الاصطناعي تسريع عملية التطوير وإنشاء نماذج أكثر قوة ودقة. فوائد مكتبات الذكاء الاصطناعي كما أشرنا سابقًا، هناك العديد من الفوائد التي يمكن الحصول عليها من خلال إنشاء واستخدام مكتبات الذكاء الاصطناعي، وهي كما يلي: سهولة الاستخدام: تعمل المكتبات على تبسيط الخوارزميات المعقدة، مما يسهل على المطورين فهم حلول الذكاء الاصطناعي وتنفيذها دون الخوض في تعقيدات الخوارزميات الأساسية. النماذج الأولية السريعة: يمكن للمطورين إنشاء نماذج أولية سريعة لأفكارهم باستخدام المكتبات، مما يسمح لهم باختبار الفرضيات وتجربة أساليب مختلفة قبل الالتزام بحل محدد. دعم المجتمع: تمامًا مثل أطر العمل، تتمتع المكتبات الشعبية بمجتمعات نابضة بالحياة. تساهم هذه المجتمعات في المكتبات من خلال توفير التحديثات وإصلاحات الأخطاء والوظائف الإضافية، مما يضمن بقاء المكتبات قوية ومحدثة. قابلية التخصيص: توفر المكتبات التوازن بين الحلول المعدة مسبقًا والتخصيص. يمكن للمطورين تعديل أجزاء معينة من كود المكتبة لتخصيص الدالة وفقًا لمتطلبات مشروعهم. كفاءة الموارد: تتميز المكتبات عمومًا بقلة حجمها مقارنةً بأطر العمل، مما يجعلها مناسبة للبيئات المحدودة الموارد مثل أجهزة إنترنت الأشياء. خاتمة لقد أضفت مكتبات وأطر الذكاء الاصطناعي قوةً كبيرةً على تطوير الذكاء الاصطناعي، مما مكّن كلاً من المحترفين المتمرسين والوافدين الجدد من تسخير قوة الذكاء الاصطناعي. سواء كنت تقوم ببناء روبوتات محادثة ذكية أو مركبات ذاتية التحكم أو أنظمة تحليلات تنبؤية، فإن هذه المكتبات والأطر تعمل كحلفاء لك في مجال الذكاء الصناعي. إن فهم مشهد مكتبا وأطر عمل الذكاء الاصطناعي هو الخطوة الأولى في الشروع في رحلة لإنشاء الجيل القادم من الأنظمة الذكية التي ستشكل عالمنا. اقرأ أيضًا تعلم الذكاء الاصطناعي لغات برمجة الذكاء الاصطناعي خوارزميات الذكاء الاصطناعي أهمية الذكاء الاصطناعي فوائد الذكاء الاصطناعي
  13. إحدى الخصائص المهمة في البرامج الحديثة هو إمكانية التواصل مع البرامج الأخرى، سواءٌ كان برنامج جو يتحقق ما إذا كان لدى المستخدم حق الوصول إلى برنامج آخر، أو برنامج جافا سكريبت 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 في لغة جو.
  14. عند تطوير التطبيقات الكبيرة، وخصوصًا برمجيات الخادم - يكون من المفيد أحيانًا لدالةٍ ما معرفة بعض المعلومات عن البيئة التي تُنفّذ بها إلى جانب المعلومات اللازمة لعمل الدالة نفسها. لنأخذ مثلًا دالة خادم ويب تتعامل مع طلب 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 على أنظمة التشغيل والمعماريات المختلفة.
  15. في المشهد سريع التطور للذكاء الصناعي 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 حجر الأساس في تطوير تطبيقات الذكاء الاصطناعي، مما يوفر للمطورين خيارات كثيرة لبناء النماذج والخوارزميات المعقدة. تعمل هذه اللغات على تمكين المبرمجين من معالجة البيانات وتحليلها وتصميم شبكات عصبية معقدة وصياغة أنظمة ذكية يمكنها التعلم والتكيف. كما في أي مجال علمي آخر هناك قيود واعتبارات لابد من الإشارة إليها لأخذها بعين الاعتبار. من المهم لممارسي الذكاء الاصطناعي اختيار لغة البرمجة التي تتوافق بشكل أفضل مع متطلبات مشروعهم المحددة مع إدراكهم للقيود المحتملة وإيجاد طرق للتغلب عليها. لا يقوم المطورون فقط بإنشاء التعليمات البرمجية؛ إنهم يصممون مستقبل الذكاء نفسه. نظرًا لأننا نقف على أعتاب الاحتمالات التي لم يتم استكشافها بعد، هناك شيء واحد مؤكد: رحلة برمجة الذكاء الاصطناعي لا تشكل المستقبل فحسب، بل تعمل أيضًا على تمكين المطورين من صياغة حلول ذكية تحدد عالم الغد. اقرأ أيضًا تعلم الذكاء الاصطناعي تعلم البرمجة أسهل لغات البرمجة مستويات لغات البرمجة أهمية الذكاء الاصطناعي مستقبل الذكاء الاصطناعي
×
×
  • أضف...