تمت ترجمة المحتوى الموجود على هذا الموقع باستخدام الذكاء الاصطناعي (AI) أو تقنية الترجمة الآلية، وقد تحتوي على أخطاء.

Skip to content

استدلال الأنواع في Luau

منذ عام 2006، يستخدم مطورو Roblox لغة البرمجة Lua لإنشاء الألعاب والتجارب التفاعلية على Roblox. ينتمي مطورو Roblox إلى مختلف مناحي الحياة ويمتلكون مستويات متنوعة من الخبرة، وتتميز إبداعاتهم بالتنوع نفسه.

بعض هذه الإبداعات عبارة عن برامج متطورة حقًا. يبلغ حجم العديد منها عشرات الآلاف من أسطر الكود.

على الرغم من أن Lua لغة برمجة رائعة ونحبها كثيرًا، إلا أننا تعلمنا الدرس نفسه الذي تعلمته مجتمع مطوري الويب: إن كتابة تطبيقات كبيرة باستخدام لغات برمجة ذات أنواع ديناميكية أمر صعب!

لقد أنشأنا Luau لسد هذه الفجوة.

في جوهرها، Luau هي محرك استدلال الأنواع أولاً ومدقق الأنواع ثانيًا. نستمد هذه الفلسفة من الأعمال السابقة الناجحة مثل مُجمِّعات OCaml و TypeScript.

ومع ذلك، فإن Lua ليست تمامًا مثل OCaml أو JavaScript. لجعل Luau رائعة بقدر الإمكان، من المهم جدًا أن نقوم بنمذجة العناصر التي تجعل Lua مميزة بدقة.

أولاً، دعونا نوضح بعض الأساسيات.

مقدمة في استدلال الأنواع

يتعلق استدلال الأنواع دائمًا تقريبًا بملاحظة أن نوعًا ما A يجب أن يكون مطابقًا لنوع آخر B. هذا تبسيط، ولكنه أقل مما قد يعتقده المرء.

لنبدأ بمثال صغير جدًا:

function id(x)
    return x
end

local a = 5
local b = id(a)

عندما نتحقق من نوع id، ننشئ نوعين من العناصر النائبة: نوع x ونوع الإرجاع للدالة.

تشير هذه العناصر النائبة إلى شيء واحد فقط: أننا لا نعرف شيئًا عنها.

ثم نقوم بتحليل عبارة return. نلاحظ أنه مهما كان النوع الذي قد تعيده الدالة، فهو نفس نوع x. وبما أن x لا تخضع لأي قيود أخرى، يتم استنتاج النوع النهائي المستنتج لـ id على أنه الدالة العامة (T) -> T.

(لا يوفر Luau حتى الآن صيغة للوظائف العامة، لكن هياكل البيانات الداخلية يمكنها تمثيلها.)
عندما يحين الوقت لاستنتاج نوع لـ b، يمكننا الاستفادة من معرفتنا بـ id. نأخذ نوع id ونربط نوع معلماته بنوع الحجة الملموسة التي لدينا. وبما أن نوع إرجاع الدالة مرتبط بالفعل بنوع معلماتها، يتم إنشاء مثيل لـ (T) -> T كـ (number) -> number. ومن هذا، يمكننا استنتاج أن نوع b هو number.

تقريبًا كل أعمال التحقق من الأنواع التي يقوم بها Luau هي امتداد لهذه الفكرة.

العودة المتعددة

في معظم لغات البرمجة الشائعة الاستخدام، تُرجع الدوال قيمة واحدة فقط. توفر العديد من اللغات (خاصة اللغات الوظيفية) مجموعات خفيفة الوزن لتسهيل العوائد المتعددة وجعلها مريحة.

تخالف Lua هذا الاتجاه من خلال السماح لكل دالة بإرجاع 0 أو أكثر من القيم. لا توجد آلية مجموعات هنا حيث لا توجد طريقة لربط المجموعة بأكملها باسم واحد.

function take_five(a, b, c, d, e)
    print(a, b, c, d, e)
end

function get_five()
    return 1, 2, 3, 4, 5
end
-- a receives 1.  The other return values are discarded.
local a = get_five()
-- foo, bar, and baz receive 1, 2, and 3, respectively.
-- Arguments 4 and 5 are discarded.
local foo, bar, baz = get_five()
-- the 5 return values from get_five are passed as
-- parameters to take_five
take_five(get_five())

وهذا يطرح بعض التحديات الجديدة. ما هو نوع compose أدناه؟

function compose(f, g)
    return function(...)
        return f(g(...))
    end
end

في لغات أخرى، يمكننا أن نستنتج أن نوع الإرجاع لـ g يجب أن يكون هو نفسه نوع الحجة لـ f. في Lua، يجب أن تقبل f نفس عدد الحجج وأنواعها التي ترجعها g.

في Luau، نمثل هذا بشيء نسميه حزمة الأنواع. وهي تشبه إلى حد كبير بنية البيانات التي نستخدمها لوصف نوع ما، لكنها تمثل نوعًا واحدًا أو أكثر.

مثل الأنواع، تدعم حزم الأنواع مفهوم العناصر النائبة التي يمكن ربطها لاحقًا بحزم أخرى. تتمتع حزم الأنواع ببعض الخصائص الأخرى: قد تكون أطوالها معروفة أو غير معروفة، وإذا كانت معروفة، فقد يكون حجمها ثابتًا أو متغيرًا.

تصمم Luau الدوال كزوج من حزم الأنواع: واحدة لقائمة الحجج وأخرى لقيم الإرجاع.
إذا افترضنا أن الصيغة A... تشير إلى حزمة أنواع عامة، فيمكننا كتابة نوع لـ compose:

((B...) -> C..., (A...) -> B...) -> (A...) -> C...

(لا يدعم Luau هذه الصيغة بعد. قريبًا!)

الجداول

تعد الجداول مهمة جدًا عند كتابة Lua. فهي تمثل المصفوفات وخرائط التجزئة والكائنات في آن واحد. ومن المنطقي أن يكون استنتاج أنواع الجداول بشكل صحيح من كود Lua أمرًا مهمًا للغاية.

في Luau، نقسم الجداول إلى 4 فئات:

  • الجداول التي نعرف هيكلها بالضبط
  • الجداول التي يتم إنشاؤها على أجزاء
  • الجداول الشبيهة التي يتم تمريرها كمعلمات دالة، و
  • أنواع بيانات واجهة برمجة تطبيقات Roblox

نسمي هذه الجداول الجداول المختومة، والجداول غير المختومة، والجداول العامة، والفئات الأصلية.

الجداول المختومة

أحد الأخطاء الشائعة جدًا التي نريد أن نتمكن من اكتشافها هو اسم خاصية به خطأ إملائي في تعيين خاصية الجدول.

local some_table = {some_property=0}
-- oops.  I got the name of the property wrong
some_table.sone_property = 55

يتم إغلاق الجداول بشكل عام بشكل افتراضي.

الجداول غير المختومة

للعمل بسلاسة مع لغة Lua الاصطلاحية، نحتاج إلى طريقة ما لدعم الدوال والوحدات النمطية التي تبني الجداول عبر عدة عبارات.

local Counter = {}
Counter.value = 0

function Counter.increment()
    Counter.value = Counter.value + 1
    return Counter.value
end

في هذا المثال، سيكون من الحماقة أن ننظر إلى السطر الأول، ونستنتج النوع {} وننتج خطأ في النوع في السطر الثاني لأننا نتوقع أن يظل Counter فارغًا إلى الأبد.

لذلك، نتخذ موقفًا مفاده أن Counter هي جدول غير مغلق. يمكن لـ Luau بسهولة معرفة الشكل الدقيق لهذا الجدول، لكننا نعتبره قابلاً للتوسيع.

لا نريد أن نجعل من السهل جدًا فتح الجدول، لذلك نطبق بعض القواعد البديهية البسيطة:

  • يكون نوع الجدول غير مختوم عندما يتم تهيئته بجدول فارغ حرفي، و
  • يتم تحويل الجداول غير المختومة إلى جداول مختومة كلما صادفنا واحدة في توقيع دالة.

وهذا يوفر لنا قابلية استخدام جيدة جدًا.

function new_counter()
    local Counter = {}
    Counter.value = 0 -- OK.  Counter is unsealed.

    function Counter.increment()
        Counter.value = Counter.value + 1
        return Counter.value
    end

    return Counter
end

local c = new_counter()
c.value_ = 5 -- Not allowed.  c is a sealed table here.

الجداول العامة

عند التعامل مع معلمات الدالة غير المُعلَّمة، نادرًا ما يكون من الممكن تحديد الشكل الدقيق لمتغير يُستخدم بطريقة تشبه الجدول:

local function print_point(p)
    print(‘X =’, p.X, ‘Y =’, p.Y)
end

يمكننا معرفة أن p يحتوي على X و Y، ولكن نظرًا لأن دالة print يمكنها طباعة أي شيء، فهذا هو كل ما في الأمر. يمكن أن تكون لهذه الخصائص أي نوع. كما يمكن أن يتواجد أي عدد من الخصائص الأخرى. ولا يلزم حتى أن يكون جدولًا فعليًا؛ فقد يكون نوعًا من أنواع واجهة برمجة تطبيقات Roblox مثل Vector3.

(المزيد عن واجهة برمجة تطبيقات Roblox بعد قليل)

مثل OCaml وبعض لغات البرمجة الأخرى، فإن معلمات الجدول في Luau متعددة الأشكال على مستوى الصفوف. في Luau، نسميها الجداول العامة. الحقول التي يُستنتج وجودها في جدول عام هي متطلبات مفروضة على المستدعين. يُسمح بالخصائص الأخرى طالما أن البنية المطلوبة موجودة:

local a = print_point({X=3, Y=4})
local c = print_point({X=4, Y=3, Name='The Best Point'})
local b = print_point(Vector3.new(3, 4, 0))

واجهة برمجة تطبيقات Roblox

توفر واجهة برمجة تطبيقات Roblox الكثير من الأدوات القوية لمجتمع المطورين الطموح لدينا. تتكون واجهة برمجة التطبيقات من عدد كبير من فئات C++ التي تم عكسها إلى Lua.

من الواضح أن Luau بحاجة إلى معرفة هذه الواجهة البرمجية للتطبيقات. جزء من هذه المعرفة هو إدراك أن مثيلات فئات Roblox ليست في الواقع جداول Lua. على سبيل المثال، لا يمكن استخدام الدالة المدمجة pairs() للتكرار عبر خصائص نوع واجهة برمجة تطبيقات Roblox.

نظام الأنواع الذي وصفناه لـ Luau حتى الآن هو نظام أنواع هيكلي بالكامل. تعتبر Lua (و Luau) الجداول مجرد مجموعة من الخصائص التي تحتوي عليها. لا تتوافق واجهة برمجة تطبيقات Roblox مع هذا النموذج. توفر لغة C++ نظام أنواع اسمي حيث تتمتع كل فئة بـ "ذاتية" خاصة بها. ومن المعتاد تمامًا وجود فئتين متميزتين على الرغم من مشاركتهما في نفس البنية بالضبط. هناك فئات Roblox فعلية تستوفي هذه الخاصية ويحتاج Luau إلى نمذجتها بشكل صحيح.

نحل هذه المشكلة عن طريق إدخال نوع يشبه الجدول لمثيلات فئات Roblox المدمجة. تختلف أنواع الفئات عن أنواع الجداول في أن لها هويات تميزها حتى لو كانت تدعم جميع الطرق نفسها مع جميع الأنواع نفسها. كما أنها تدعم مفهوم الوراثة، تمامًا مثل فئات C++ التي صُممت لتصميمها.

الخلاصة

يُشكل استخلاص الأنواع الثابتة من لغة ديناميكية مثل Lua الكثير من التحديات. العديد من هذه التحديات خاصة بلغة Lua، ولكنها قابلة للحل تمامًا. نعتقد أن الأمر يعمل بشكل جيد جدًا! 🙂

أندي فريزن هو المسؤول التقني عن أداة فحص أنواع Luau. وهو متحمس للعمل في مجال يجمع بين ألعاب الفيديو وأدوات المطورين ولغات البرمجة.

لا تؤيد شركة Roblox Corporation ولا هذا المدونة أي شركة أو خدمة. كما لا توجد أي ضمانات أو وعود فيما يتعلق بدقة أو موثوقية أو اكتمال المعلومات الواردة في هذا المدونة.