الفرق بين المراجعتين لصفحة: «TypeScript/functions»

من موسوعة حسوب
سطر 108: سطر 108:
}
}
</syntaxhighlight>
</syntaxhighlight>
والدالة
والدالة
<syntaxhighlight lang="typescript">
<syntaxhighlight lang="typescript">
function buildName(firstName: string, lastName = "Smith") {
function buildName(firstName: string, lastName = "Smith") {
سطر 127: سطر 127:
let result4 = buildName(undefined, "Adams");    // "Will Adams"
let result4 = buildName(undefined, "Adams");    // "Will Adams"
</syntaxhighlight>
</syntaxhighlight>
==المعاملات المتبقية (Rest Parameters)==
==المعاملات المتبقية (Rest Parameters)==
المعاملات المطلوبة، والاختيارية، والافتراضية كلها تشترك في ميزة واحدة: وهي أنها تكون معاملًا واحدًا فقط في كل مرّة. قد ترغب أحيانًا بالعمل مع عدة معاملات كمجموعة واحدة، أو قد لا تدري عدد المعاملات التي ستأخذها الدالة. يُمكنك العمل مع المعاملات الممرّرة مباشرةً عبر المتغيّر ‎<code>arguments</code>‎ الذي يكون ظاهرًا داخل جسم كل دالة.
المعاملات المطلوبة، والاختيارية، والافتراضية كلها تشترك في ميزة واحدة: وهي أنها تكون معاملًا واحدًا فقط في كل مرّة. قد ترغب أحيانًا بالعمل مع عدة معاملات كمجموعة واحدة، أو قد لا تدري عدد المعاملات التي ستأخذها الدالة. يُمكنك العمل مع المعاملات الممرّرة مباشرةً عبر المتغيّر ‎<code>arguments</code>‎ الذي يكون ظاهرًا داخل جسم كل دالة.

مراجعة 10:44، 9 أغسطس 2018


مقدمة

تُعدّ الدوال أحد أساسات أي تطبيق مكتوب بلغة JavaScript. إذ تُستخدم لبناء طبقات تجريد (layers of abstraction)، ولبناء مكونات تعمل كالأصناف، ولإخفاء المعلومات (information hiding)، وتعمل كوحداتٍ (modules) كذلك. ورغم أن الأصناف ومجالات الأسماء والوحدات موجودة في TypeScript، إلّا أنّ الدوال لا تزال تلعب الدور الرئيسيّ في وصف كيفيّة القيام بالأمور. وتُضيف TypeScript كذلك بعض المزايا الجديدة لدوال JavaScript الاعتياديّة لتسهيل مهمّة العمل معها.

الدوال

يُمكن بدايةً إنشاء الدوال في لغة TypeScript كما في لغة JavaScript، ويُمكن إنشاء الدوال إمّا مُسمَّاةً (named function) أو مجهولة الاسم (anonymous function). يسمح هذا باختيار أكثر طريقة ملائمة لك في تطبيقك، سواء أَكُنتَ تبني عدّة دوال في واجهة برمجيّة (API) أو دالة ذات استعمال واحد لتمريرها إلى دالة أخرى (اسم الدالة لا يهم في هذه الحالة).

كتذكير بسيط، إليك كلا الطريقتين في لغة JavaScript:

// دالة مُسمّاة
function add(x, y) {
    return x + y;
}

// دالة مجهولة الاسم
let myAdd = function(x, y) { return x + y; };

يُمكن -كما في JavaScript- للدوال أن تُشير إلى متغيرات موجودة خارج جسم الدالة. عند القيام بهذه العملية فإنّنا نقول أنّنا "نلتقط (capture)" هذه المتغيرات. ورغم أن فهم آلية عمل الالتقاط وسلبياته خارج عن أهداف هذا الدليل، إلا أن فهم كيفية عمله مهم لمبرمجي JavaScript وTypeScript.

let z = 100;

function addToZ(x, y) {
    return x + y + z;
}

أنواع الدوال

إضافة الأنواع إلى الدوال

لنضف الأنواع إلى المثالين السابقين:

function add(x: number, y: number): number {
    return x + y;
}

let myAdd = function(x: number, y: number): number { return x + y; };

يُمكننا إضافة الأنواع لكل معامل ثمّ إلى الدالة نفسها لإضافة النوع المُعاد (return type). يُمكن للغة TypeScript استنتاج النوع المُعاد عبر النظر إلى جُمل ‎return‎، لذا يُمكن الاستغناء عنه في الكثير من الأحيان.

كتابة نوع الدالة

بعد إضافة أنواع المعاملات ونوع القيمة المعادة إلى الدالة، لنكتب كامل نوع الدالة عبر إلقاء نظرة على كل جزء من أجزاء نوع الدالة:

let myAdd: (x: number, y: number) => number =
    function(x: number, y: number): number { return x + y; };

لأنواع الدوال نفس الجزأين: نوع المعاملات ونوع القيمة المعادة. يكون كلا الجزأين مطلوبًا عند كتابة كامل نوع الدالة. نكتب أنواع المعاملات كما نفعل مع قوائم المعاملات، وذلك بإعطاء كل معامل اسمًا ونوعًا. هذا الاسم يُساعد في مقروئية (readability) الشيفرة فقط ولا يلزم أن تكون الأسماء متطابقة، إذ كان يُمكن أن نكتب الشيفرة أعلاه كما يلي:

let myAdd: (baseValue: number, increment: number) => number =
    function(x: number, y: number): number { return x + y; };

ما دامت أنواع المعاملات مرتّبة بشكل صحيح، فهذا يعني بأنه نوع صالح للدالة بغض النظر عن أسماء المعاملات في نوع الدالة.

الجزء الثاني هو النوع المعاد. نُوضّح جزء النوع المعاد عبر استخدام السهم ‎‎=‎>‎‎ للفصل بين المعاملات والنوع المعاد. وهذا الجزء -كما سبق ذكره- مطلوب في نوع الدالة، لذا إن لم يكن للدالة قيمة معادة، فاستعمل النوع ‎void‎ عوضًا عن تركه.

لاحظ كذلك بأن المعاملات والنوع المعاد هي وحدها من يُكوِّن نوع الدالة. والمتغيرات الملتقطَة لا تكون في النوع. لذا فالمتغيّرات الملتقطَة تعدّ جزءًا من "الحالة المخفية (hidden state)" لأي دالة ولا تكون جزءًا من واجهتها البرمجيّة.

استنتاج الأنواع (type inference)

يُمكن لمترجم (compiler) لغة TypeScript استنتاج النوع إن حدّدت النوع على جانب دون آخر:

// يعلم المترجم نوع الدالة بالكامل ولو لم يُحدَّد على الجهة اليُسرى
let myAdd = function(x: number, y: number): number { return  x + y; };

// هنا كذلك، يُعرَف نوع الدالة الكامل من الجانب الأيسر للتصريح
let myAdd: (baseValue: number, increment: number) => number =
    function(x, y) { return x + y; };

يُسمّى هذا بإضافة الأنواع سياقيًّا (contextual typing)، وهو شكل من أشكال استنتاج الأنواع، ويُساعد على تقليل المجهود الذي تتطلبه إضافة الأنواع للبرامج.

المعاملات الافتراضية والاختيارية

يكون تمرير قيم لجميع معاملات الدالة مطلوبًا ليعمل استدعاؤها بشكل صحيح في TypeScript. لكن هذا لا يعني أنه لا يمكن تمرير القيمة ‎null‎ أو ‎undefined‎، بل يعني أنّ المترجم سيتحقق من أنّ المستخدم قد مرّر قيمة لكل معامل من معاملات الدالة. ويعتبر المترجم كذلك أن هذه المعاملات هي المعاملات الوحيدة التي ستُمرّر إلى الدالة. أو بعبارة أخرى، يجب على عدد القيم الممرّرة للدالة أن يوافق عدد المعاملات التي تتوقعها الدالة.

function buildName(firstName: string, lastName: string) {
    return firstName + " " + lastName;
}

let result1 = buildName("Bob");                  // خطأ، عدد لا يكفي من المعاملات
let result2 = buildName("Bob", "Adams", "Sr.");  // خطأ عدد المعاملات أكثر مما هو متوقع
let result3 = buildName("Bob", "Adams");         // عدد المعاملات جيد

في لغة JavaScript تكون جميع المعاملات اختياريّة، ويُمكن للمستخدمين ترك بعضها حسب ما يُناسب. وتكون قيمتُها القيمةَ ‎undefined‎ عند تركها وعدم تمريرها. يُمكننا الحصول على هذه الميزة عبر إضافة المحرف ‎?‎ لآخر المعاملات التي نُريد لها أن تكون اختيارية. لنقل مثلًا بأنّنا نريد أن يكون المعامل ‎lastName‎ في المثال أعلاه اختياريًّا:

function buildName(firstName: string, lastName?: string) {
    if (lastName)
        return firstName + " " + lastName;
    else
        return firstName;
}

let result1 = buildName("Bob");                  // يعمل بشكل صحيح
let result2 = buildName("Bob", "Adams", "Sr.");  // خطأ، عدد المعاملات أكثر مما هو متوقع
let result3 = buildName("Bob", "Adams");         // عدد المعاملات جيد

يجب على المعاملات الاختيارية أن تلحقَ دائمًا بالمعاملات المطلوبة وتكونَ بعدها. فلو أردنا جعل المعامل ‎firstName‎ اختياريًّا عوضًا عن المعامل ‎lastName‎ لاحتجنا إلى تغيير ترتيب المعاملات في الدالة بوضع المعامل ‎firstName‎ في آخر قائمة المعاملات.

يُمكننا في لغة TypeScript أيضًا ضبط قيمة سيحملها المعامل إن لم يُمرّر المُستخدم قيمة مُغايرة، أو إن مرّر المُستخدم القيمة ‎undefined‎ لها. وتُسمى بالمعاملات المُهيأة افتراضيًّا (default-initialized parameters). لنأخذ المثال السابق ولنُعطِ للمعامل ‎lastName‎ القيمة الافتراضية ‎‎"Smith"‎‎.

function buildName(firstName: string, lastName = "Smith") {
    return firstName + " " + lastName;
}

let result1 = buildName("Bob");                  // "Bob Smith"
let result2 = buildName("Bob", undefined);       // "Bob Smith"
let result3 = buildName("Bob", "Adams", "Sr.");  // خطأ عدد المعاملات أكثر مما هو متوقع
let result4 = buildName("Bob", "Adams");         // عدد المعاملات جيد

تعدّ المعاملات المُهيأة افتراضيًّا التي تكون بعد جميع المعاملات المطلوبةِ معاملاتٍ اختياريّة، و يُمكن ترك تمرير قيمة لها عند استدعاء الدالة كما المعاملات الاختيارية. هذا يعني بأن المعاملات الاختيارية والمعاملات الافتراضية التي تكون في مؤخرة القائمة تتشارك في أنواعها. لذا فكل من الدالة

function buildName(firstName: string, lastName?: string) {
    // ...
}

والدالة

function buildName(firstName: string, lastName = "Smith") {
    // ...
}

يتشاركان في نفس النوع ‎‎(firstName:‎ string‎,‎ lastName‎?‎:‎ string‎)‎ ‎=>‎ string‎‎. بحيث تختفي القيمة الافتراضية للمعامل ‎lastName‎ في النوع، مُبقيةً على حقيقة أنّ المعاملَ معاملٌ اختياريّ.

وعلى النقيض من المعاملات الاختيارية العادية، فالمعاملات المهيأة افتراضيا لا تحتاج إلى أن تكون بعد المعاملات المطلوبة. إن كان معامل مهيأ افتراضيا قبل معامل مطلوب، فسيحتاج المستخدمون إلى تمرير القيمة ‎undefined‎ بصراحة للحصول على القيمة الافتراضية. يُمكن مثلًا كتابة المثال السابق بقيمة افتراضيّة للمعامل ‎firstName‎ فقط:

function buildName(firstName = "Will", lastName: string) {
    return firstName + " " + lastName;
}

let result1 = buildName("Bob");                  // خطأ، عدد لا يكفي من المعاملات
let result2 = buildName("Bob", "Adams", "Sr.");  // خطأ عدد المعاملات أكثر مما هو متوقع
let result3 = buildName("Bob", "Adams");         // "Bob Adams"
let result4 = buildName(undefined, "Adams");     // "Will Adams"

المعاملات المتبقية (Rest Parameters)

المعاملات المطلوبة، والاختيارية، والافتراضية كلها تشترك في ميزة واحدة: وهي أنها تكون معاملًا واحدًا فقط في كل مرّة. قد ترغب أحيانًا بالعمل مع عدة معاملات كمجموعة واحدة، أو قد لا تدري عدد المعاملات التي ستأخذها الدالة. يُمكنك العمل مع المعاملات الممرّرة مباشرةً عبر المتغيّر ‎arguments‎ الذي يكون ظاهرًا داخل جسم كل دالة.

يُمكنك في TypeScript جمع هذه المعاملات في متغير واحد:

function buildName(firstName: string, ...restOfName: string[]) {
    return firstName + " " + restOfName.join(" ");
}

let employeeName = buildName("Joseph", "Samuel", "Lucas", "MacKinzie");

تُعامَل المعاملات المتبقية كعدد لا محدود من المعاملات الاختيارية. عند تمرير القيم إلى معامل متبقٍ، يُمكنك تمرير أي عدد من القيم ويُمكن عدم تمرير أية قيمة كذلك. سيبني المُترجم (compiler) مصفوفة تحتوي على القيم المُمرّرة، ستُسمّى هذه المصفوفة بالاسم المُعطى بعد النقاط الثلاث (‎...‎)، ما يسمح لك بالوصول إلى هذه القيم داخل الدالة.

تُستَخدم النقاط الثلاث مع المعاملات المتبقية في نوع الدالة كذلك:

function buildName(firstName: string, ...restOfName: string[]) {
    return firstName + " " + restOfName.join(" ");
}

let buildNameFun: (fname: string, ...rest: string[]) => string = buildName;

الكلمة المفتاحية ‎this

تعلّم كيفية استخدام ‎this‎ في JavaScript مرحلة يجب أن يمر منها كل مطور. ويجب على مطوري TypeScript تعلم كيفية استخدام ‎this‎ وكيفية ملاحظة الاستخدامات غير المناسبة لها. تُنبّه TypeScript إلى الاستخدامات غير المناسبة لها اعتمادًا على بعض التقنيات. إن احتجت إلى تعلّم كيفية استخدام الكلمة المفتاحية ‎this‎ في JavaScript، فاقرأ هذا المقال الذي يغطي آلية عمل ‎this‎ جيدًا -انظر كذلك صفحة ‎this‎ على الموسوعة- لذا سنُغطي هنا الأساسيات فقط.

الكلمة المفتاحية ‎this‎ والدوال السهمية (arrow functions)

تُمثل الكلمة المفتاحية ‎this‎ في JavaScript متغيّرًا يُضبط عند استدعاء الدالة. ما يجعله ميزة قوية ومرنة، لكن عليك دائمًا معرفة السياق (context) الذي تُنفَّذ فيه الدالة. وهو أمر محيّر في كثير من الأحيان، خاصة عند إعادة دالة أو تمرير دالة كمُعامل. لنلق نظرة على مثال بسيط:

let deck = {
    suits: ["hearts", "spades", "clubs", "diamonds"],
    cards: Array(52),
    createCardPicker: function() {
        return function() {
            let pickedCard = Math.floor(Math.random() * 52);
            let pickedSuit = Math.floor(pickedCard / 13);

            return {suit: this.suits[pickedSuit], card: pickedCard % 13};
        }
    }
}

let cardPicker = deck.createCardPicker();
let pickedCard = cardPicker();

alert("card: " + pickedCard.card + " of " + pickedCard.suit);

لاحظ أنّ ‎createCardPicker‎ دالةٌ تُعيد دالةً أخرى. إن حاولنا تنفيذ المثال، فسنحصل على خطأ عوضًا عن صندوق التنبيه المتوقَّع. هذا لأن ‎this‎ المُستخدَم داخل الدالة المُنشأة من طرف ‎createCardPicker‎ سيُضبَط لتكون قيمتُه القيمةَ ‎window‎ عوضًا عن الكائن ‎deck‎. وذلك لأننا نستدعي الدالة ‎‎cardPicker‎()‎‎ بشكل مستقل. إذ أن استدعاءً عالي المستوى لغَيرِ تابعٍ (A top-level non-method syntax call) كهذا سيستخدم ‎window‎ للمتغيّر ‎this‎. (لاحظ أن قيمة ‎this‎ ستكون ‎undefined‎ عوضًا عن ‎window‎ في الوضع الصارم ‎strict‎).

يُمكننا حل هذه المشكلة عبر التأكد من أن الدالة مرتبطة بالمتغير ‎this‎ بشكل صحيح قبل إعادة الدالة لاستخدامها لاحقًا. بهذه الطريقة سيبقى المتغير في الدالة قادرًا على الوصول إلى الكائن ‎deck‎ الأصلي بغض النظر عن كيفية استخدام الدالة لاحقًا. للقيام بذلك سنُغيّر تعبير الدالة ليستخدم ميزة الدوال السهمية التي جاءت بها النسخة ECMAScript 6. إذ تلتقط الدوال السهمية المتغيّرَ ‎this‎ في مكان إنشاء الدالة عوضًا عن مكان استدعائها:

let deck = {
    suits: ["hearts", "spades", "clubs", "diamonds"],
    cards: Array(52),
    createCardPicker: function() {
        // لاحظ أن السطر أدناه أصبح دالة سهمية، ما يسمح لنا بالتقاط المتغير هنا
        return () => {
            let pickedCard = Math.floor(Math.random() * 52);
            let pickedSuit = Math.floor(pickedCard / 13);

            return {suit: this.suits[pickedSuit], card: pickedCard % 13};
        }
    }
}

let cardPicker = deck.createCardPicker();
let pickedCard = cardPicker();

alert("card: " + pickedCard.card + " of " + pickedCard.suit);

ستُنبّهك كذلك TypeScript عند ارتكابك لهذا الخطأ إن مرّرت الراية ‎‎-‎-‎noImplicitThis‎‎ إلى المترجم. إذ سيُنبّهك إلى أنّ ‎this‎ الموجود في ‎‎this.suits[pickedSuit]‎‎ من النوع ‎any‎.

معاملات ‎this

لا يزال نوعُ ‎‎this‎.‎suits‎[pickedSuit]‎‎ النوعَ ‎any‎ للأسف. هذا لأن المتغير ‎this‎ يأتي من تعبير الدالة الموجود داخل قيمة الكائن الحرفيّة (object literal). يُمكن تمرير معامل‎ this ‎ صريحٍ لإصلاح هذا الأمر. معاملات ‎this‎ هي معاملات مزيّفة تأتي في أول قائمة معاملات الدالة:

function f(this: void) {
    // تأكد من أن 
    // `this`
    // غير قابل للاستعمال داخل هذه الدالة
}

لنُضِف بضعة واجهات لمثالنا السابق، سنُضيف النوعين ‎Card‎ و‎Deck‎ لجعل الأنواع أوضح وأكثر قابلية لإعادة الاستعمال:

interface Card {
    suit: string;
    card: number;
}
interface Deck {
    suits: string[];
    cards: number[];
    createCardPicker(this: Deck): () => Card;
}
let deck: Deck = {
    suits: ["hearts", "spades", "clubs", "diamonds"],
    cards: Array(52),
    // لاحظ أن الدالة الآن تُحدد بصراحة بأن المُستدعى الخاص بها يجب أن يكون من النوع
    // Deck
    createCardPicker: function(this: Deck) {
        return () => {
            let pickedCard = Math.floor(Math.random() * 52);
            let pickedSuit = Math.floor(pickedCard / 13);

            return {suit: this.suits[pickedSuit], card: pickedCard % 13};
        }
    }
}

let cardPicker = deck.createCardPicker();
let pickedCard = cardPicker();

alert("card: " + pickedCard.card + " of " + pickedCard.suit);

تتوقّع TypeScript الآن من ‎createCardPicker‎ أن تُستدعى على كائن من النوع ‎Deck‎. هذا يعني بأن المتغير ‎this‎ قد أصبح الآن من النوع ‎Deck‎، ولم يعد نوعُه النوعَ ‎any‎، لذا فالراية ‎‎-‎-‎noImplicitThis‎‎ لن تنتج أية أخطاء.

معاملات ‎this‎ في دوال رد النداء (callbacks)

يُمكن أن تحدث أخطاء عند العمل مع ‎this‎ في دوال رد النداء كذلك، عند تمرير الدوال إلى مكتبة (library) تستدعيها لاحقًا. وسبب ذلك هو أنّ المكتبة التي تستدعي دالة رد النداء الخاصة بك ستستدعيها كدالة عادية، فسيحمل المتغيّر ‎this‎ القيمة ‎undefined‎. يُمكنك استعمال معاملات ‎this‎ لتجنب أخطاء دوال رد النداء كذلك. أولًا، سيحتاج كاتب المكتبة إلى إضافة حاشية (annotate) إلى نوع دالة رد النداء بالمتغير ‎this‎ كما يلي:

interface UIElement {
    addClickListener(onclick: (this: void, e: Event) => void): void;
}

هنا، التعبير ‎‎this: void‎‎ يعني بأن الدالة ‎‎addClickListener‎‎ تتوقع من ‎onclick‎ أن تكون دالة لا تتطلب نوعَ ‎this‎. ثانيًا، أضف حاشية إلى الشيفرة التي تستدعي الدالة بالمتغير ‎this‎ كما يلي:

class Handler {
    info: string;
    onClickBad(this: Handler, e: Event) {
        // خطأ، نستعمل
        // this
        // هنا، لذا فاستدعاء دالة رد النداء هذه سيفشل في زمن التنفيذ
        this.info = e.message;
    }
}
let h = new Handler();
uiElement.addClickListener(h.onClickBad); // خطأ

بعد إضافة حاشية نوع للمتغير ‎this‎، فهذا يقضي صراحةً بأن الدالة ‎onClickBad‎ يجب لها أن تُستدعى من على نسخة من الصنف ‎Handler‎. ستكتشف TypeScript بعد ذلك بأن ‎addClickListener‎ تتطلب دالة تملك الحاشية ‎this: void‎. لإصلاح هذا الخطأ، غيِّر نوع ‎this‎ كما يلي:

class Handler {
    info: string;
    onClickGood(this: void, e: Event) {
        // لا يُمكن هنا استعمال المتغيّر
        // this
        // لأنه من النوع
        // void
        console.log('clicked!');
    }
}
let h = new Handler();
uiElement.addClickListener(h.onClickGood);

لأن الدالة ‎onClickGood‎ تحدّد نوع ‎this‎ الخاص بها على أنّ نوعَه هو ‎void‎، فمن الممكن تمريرها إلى الدالة ‎addClickListener‎. لكن هذا يعني طبعًا بأنك لن تستطيع الوصول إلى ‎‎this.info‎‎. إن أردت كلا الميزتين فسيتوجّب عليك استعمال دالة سهميّة:

class Handler {
    info: string;
    onClickGood = (e: Event) => { this.info = e.message }
}

هذا يعمل بسبب أن الدوال السهمية لا تلتقط المتغير ‎this‎، لذا يُمكنك دائمًا تمريرها إلى ما يتوقّع مُتغيّر ‎this‎ من النوع ‎void‎ (أي ‎‎this: void‎). عَيبُ هذه الطريقة أنّ الدالة السهمية تُنشَؤُ لكل كائن من النوع ‎Handler‎. أما التوابع فتُنشؤ مرة واحدة فقط وتُربَط بسلسلة prototype الخاصة بالصنف ‎Handler‎. وتُشارَكُ بين جميع الكائنات من النوع ‎Handler‎.

الأحمال الزائدة (Overloads)

لغة JavaScript ديناميكية إلى حد كبير، ومن الشائع أن تُعيد دالة JavaScript واحدة أنواعًا مُختلفةً من الكائنات حسب شكل المعاملات المُمرّرة لها.

let suits = ["hearts", "spades", "clubs", "diamonds"];

function pickCard(x): any {
    // تحقق مما إذا كنا نعمل مع كائن أو مصفوفة
    // إن كان الأمر كذلك، فسنختار البطاقة من مجموعة البطاقات
    if (typeof x == "object") {
        let pickedCard = Math.floor(Math.random() * x.length);
        return pickedCard;
    }

    // إن لم يكن الأمر كذلك، فسندع المستخدم  يختار البطاقة
    else if (typeof x == "number") {
        let pickedSuit = Math.floor(x / 13);
        return { suit: suits[pickedSuit], card: x % 13 };
    }
}

let myDeck = [{ suit: "diamonds", card: 2 }, { suit: "spades", card: 10 }, { suit: "hearts", card: 4 }];
let pickedCard1 = myDeck[pickCard(myDeck)];
alert("card: " + pickedCard1.card + " of " + pickedCard1.suit);

let pickedCard2 = pickCard(15);
alert("card: " + pickedCard2.card + " of " + pickedCard2.suit);

ستُعيد الدالة ‎pickCard‎ هنا نوعين من البيانات حسب ما مرّره المستخدم. إن مرَّر كائنًا يُمثل مجموعة البطاقات (deck)، فالدالة ستختار البطاقة. وإن اختار بطاقةً فسنُخبره أيُّها اختار. لكن كيف يُمكن وصف هذا لمُدقّق الأنواع؟

الجواب هو إضافة عدة أنواعِ دوالٍ لنفس الدالة كقائمةٍ من الأحمال الزائدة. هذه القائمة هي ما سيستخدمه المترجم (compiler) للحكم على استدعاءات الدالة. لنُنشئ قائمة أحمالٍ زائدةٍ تصِف ما تقبله الدالة ‎pickCard‎ وماذا ستُعيد:

let suits = ["hearts", "spades", "clubs", "diamonds"];

function pickCard(x: {suit: string; card: number; }[]): number;
function pickCard(x: number): {suit: string; card: number; };
function pickCard(x): any {
    // تحقق مما إذا كنا نعمل مع كائن أو مصفوفة
    // إن كان الأمر كذلك، فسنختار البطاقة من مجموعة البطاقات
    if (typeof x == "object") {
        let pickedCard = Math.floor(Math.random() * x.length);
        return pickedCard;
    }
    // إن لم يكن الأمر كذلك، فسندع المستخدم  يختار البطاقة
    else if (typeof x == "number") {
        let pickedSuit = Math.floor(x / 13);
        return { suit: suits[pickedSuit], card: x % 13 };
    }
}

let myDeck = [{ suit: "diamonds", card: 2 }, { suit: "spades", card: 10 }, { suit: "hearts", card: 4 }];
let pickedCard1 = myDeck[pickCard(myDeck)];
alert("card: " + pickedCard1.card + " of " + pickedCard1.suit);

let pickedCard2 = pickCard(15);
alert("card: " + pickedCard2.card + " of " + pickedCard2.suit);

بهذا التغيير أصبحت الأحمال الزائدة الآن تُضيف التحقق من الأنواع إلى استدعاءات الدالة ‎pickCard‎.

لكي يختار المُترجم التحقق من الأنواع بشكل صحيح، فإنّه يُؤدّي عمليّةً مشابهة لما تقوم به JavaScript الضِّمنية. إذ تنظر إلى قائمة الأحمال الزائدة، ثم تحاول استدعاء الدالة بالمعاملات المعطاة مع الحمل الزائد الأول، إن كان هناك توافق، فستختار هذا الحمل الزائد على أنه الصحيح. لهذا فإنّه من المعتاد ترتيب الأحمال الزائدة من أكثرها تحديدًا وتفصيلًا إلى أقلها دقّةً.

لاحظ بأن القطعة ‎function pickCard(x): any‎ ليست جزءًا من قائمة الأحمال الزائدة، لذا فللدالة حملان زائدان اثنان فقط: الأول يأخذ كائنًا والآخر يأخذ عددًا. استدعاء ‎pickCard‎ بأيّ نوع من أنواع المعاملات الأخرى سيُنتج خطأ.

مصادر