> ## Content Index
> Fetch the complete content index at: https://arabroot.io/llms.txt
> Use this file to discover other available public pages before exploring further.

# أنماط البرمجة وأنظمة الأنواع: لماذا تختلف اللغات؟
- URL: https://arabroot.io/articles/انماط-البرمجة-وانظمة-الانواع-للمبتدئين/
- Published: 2026-10-05T15:30:00.000Z
- Updated: 2026-10-09T09:42:27.000Z
- Description: نحل مسألة واحدة بعدة أساليب برمجة، ونرى هل تكتشف اللغة أخطاءك قبل التشغيل أم بعده، وماذا يعني ذلك عند اختيار لغة لمشروعك.
- Author: فريق عرب رووت
- Tags: الأدلة التقنية, أساسيات الحاسب والبرمجة, DevOps وCI/CD, الاستضافة الذاتية

تبدأ تعلم البرمجة بلغة واحدة، ثم تفتح أول مشروع في العمل فتجد الواجهة الأمامية بلغة TypeScript، والخدمة الخلفية بلغة Go، وسكربتات الأتمتة بلغة Python، وتقارير قاعدة البيانات بلغة SQL، وملفات البنية التحتية بلغة Terraform، ثم تقرأ في الإعلانات الوظيفية كلمات مثل «لغة وظيفية Functional» و«أنواع ثابتة Static Types» و«Null Safety» وكأن كل مطور يعرف معناها. وقد يتساءل البعض: لماذا كل هذه اللغات أصلاً؟ ولماذا لا يتفق المطورون على لغة واحدة ويرتاح الجميع؟ والإجابة أن كل لغة هي مجموعة قرارات صممها أصحابها لحل نوع معين من المشاكل، فاللغة التي تجعل كتابة سكربت سريع أمراً سهلاً هي نفسها التي تترك بعض الأخطاء حتى وقت التشغيل، واللغة التي تمسك أغلب الأخطاء قبل التشغيل تطلب منك في المقابل أن تكتب أكثر وتنتظر المترجم.

وهذه القرارات لها أسماء، وأهمها سؤالان: الأول هو نمط البرمجة Programming Paradigm، أي هل تكتب للحاسوب خطوات ينفذها واحدة بعد الأخرى، أم تصف له النتيجة التي تريدها وتتركه يقرر الطريقة. والثاني هو نظام الأنواع Type System، أي متى تكتشف اللغة أنك تجمع نصاً مع رقم، وماذا تفعل عندها. وسوف نناقش في هذا المقال ما يلي:

- لماذا توجد مئات اللغات، وما هي المقايضات Trade-offs التي تختلف فيها.
- أنماط البرمجة بمثال واحد يتكرر في كل نمط، وهو حساب إجمالي طلب في متجر: الإجرائي Procedural بلغة Go، والكائني Object-Oriented بلغة Java، والوظيفي Functional بلغة JavaScript وPython وHaskell، والتصريحي Declarative بلغة SQL وTerraform، والمنطقي Logic بلغة Prolog، والقائم على الأحداث Event-Driven.
- أنظمة الأنواع: الثابتة Static والديناميكية Dynamic، والقوية Strong والضعيفة Weak، واستنتاج الأنواع Type Inference، والأنواع التدريجية Gradual Typing مع TypeScript وmypy، والنظام الاسمي Nominal والبنيوي Structural، والحماية من Null، والأنواع العامة Generics.
- جدول مقارنة لأشهر اللغات من حيث الأنماط والأنواع وطريقة التنفيذ وإدارة الذاكرة، ثم كيف تستفيد من هذا كله عند اختيار لغة لمشروعك.

وكل مثال في المقال شغلناه فعلاً داخل Container، والمخرجات والأخطاء المنسوخة هنا هي ما ظهر على الشاشة كما هو.

## لماذا توجد كل هذه اللغات؟

لنفرض أنك تريد لغة واحدة تناسب كل شيء، فسوف تطلب منها أن تكون سريعة جداً مثل C لأنك تكتب بها نظام تشغيل، وأن تكون سهلة ومرنة مثل Python لأنك تكتب بها سكربت لمرة واحدة، وأن تمنع أخطاء الذاكرة مثل Rust لأنك تكتب بها برنامجاً يتعامل مع بيانات الناس، وأن تعمل داخل المتصفح مثل JavaScript. والمشكلة أن هذه الطلبات تتعارض، فالسرعة القصوى تحتاج تحكماً مباشراً في الذاكرة، والتحكم المباشر يفتح باب الأخطاء، ومنع الأخطاء قبل التشغيل يحتاج قواعد صارمة يفحصها المترجم Compiler، والقواعد الصارمة تبطئ كتابة السكربت السريع. لذلك فكل لغة تختار نقطة على هذه المقايضات، وأهمها ما يلي:

- **سرعة التنفيذ Performance مقابل سرعة الكتابة Productivity**: لغة مثل C تعطيك أقصى سرعة وتترك لك كل التفاصيل، ولغة مثل Python تكتب بها في سطر ما يحتاج عشرة أسطر في C، ولكنها أبطأ في التنفيذ.
- **الأمان Safety مقابل الحرية**: كلما زادت القواعد التي يفحصها المترجم قلت الأخطاء التي تصل إلى المستخدم، وزاد الوقت الذي تقضيه في إرضاء المترجم.
- **البيئة التي تعمل فيها**: JavaScript موجودة لأنها اللغة التي تفهمها المتصفحات، وSwift وKotlin موجودتان لأن Apple وGoogle تدعمانهما في أنظمة الهواتف، وSQL موجودة لأن قواعد البيانات تفهمها.
- **طريقة التفكير في المشكلة**: بعض المشاكل تكتب بوضوح كخطوات، وبعضها كتحويل بيانات، وبعضها كقواعد منطقية، ولهذا ظهرت الأنماط التي سوف نشرحها.

وقصة كل لغة ومن صنعها ولماذا، تجدها في مقال [قصص لغات البرمجة ومن صنعها](https://arabroot.io/articles/%D9%82%D8%B5%D8%B5-%D9%84%D8%BA%D8%A7%D8%AA-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D9%88%D9%85%D9%86-%D8%B5%D9%86%D8%B9%D9%87%D8%A7/)، أما الفرق بين اللغة كمواصفات Specification وبين البرامج التي تنفذها مثل CPython وPyPy، ومن يقرر مستقبل كل لغة، فهو موضوع مقال [مواصفات لغات البرمجة وتطبيقاتها وحوكمتها](https://arabroot.io/articles/%D9%85%D9%88%D8%A7%D8%B5%D9%81%D8%A7%D8%AA-%D9%84%D8%BA%D8%A7%D8%AA-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D9%88%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA%D9%87%D8%A7-%D9%88%D8%AD%D9%88%D9%83%D9%85%D8%AA%D9%87%D8%A7/). وفي هذا المقال سوف نركز على الفكرتين اللتين تفرقان بين اللغات في الكود الذي تكتبه كل يوم: الأنماط والأنواع.

## المختبر: كل اللغات في Containers

لن تحتاج إلى تثبيت أي لغة على جهازك، فكل مثال يعمل داخل Container من Docker Image رسمية، يحذف بعد انتهاء الأمر بسبب الخيار `--rm`، ويقرأ الملفات من المجلد الحالي عبر `-v "$PWD":/w`. وإذا لم يكن Docker مثبتاً لديك فقد شرحنا تثبيته في دليل [تثبيت Docker على Ubuntu](https://arabroot.io/articles/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-docker-%D8%B9%D9%84%D9%89-ubuntu/). والجدول التالي يبين ال Images والإصدارات التي استخدمناها:

| اللغة أو الأداة        | Docker Image                  | الإصدار الذي ظهر في المختبر        |
| ---------------------- | ----------------------------- | ---------------------------------- |
| Go                     | golang:1.27.1-alpine3.24      | go1.27.1                           |
| Java                   | eclipse-temurin:25-jdk-alpine | OpenJDK 25.0.4.1                   |
| JavaScript وTypeScript | node:22-alpine                | Node.js v22.23.3 وTypeScript 7.0.2 |
| Python وmypy           | python:3.13-slim              | Python 3.13.16 وmypy 2.4.0         |
| Rust وC                | rust:1-alpine                 | rustc 1.99.0 وgcc 15.2.0           |
| Haskell                | haskell:9-slim                | GHC 9.14.1                         |
| Prolog                 | swipl:latest                  | SWI-Prolog 10.1.16                 |
| SQL                    | postgres:17.11-alpine         | PostgreSQL 17.11                   |
| Terraform              | hashicorp/terraform:latest    | Terraform v1.16.5                  |

ولاحظ أن كل Container يأخذ اسماً يبدأ ب `lab-lang`، والسبب أنه إذا بقي أحدها يعمل لأي سبب فسوف تجده بسهولة وتحذفه وحده دون أن تلمس Containers المشاريع الأخرى على جهازك.

## أنماط البرمجة Programming Paradigms: الخطوات أم النتيجة؟

نمط البرمجة Programming Paradigm هو طريقة التفكير التي تكتب بها الحل، وليس صياغة Syntax لغة معينة، فيمكنك أن تكتب بأكثر من نمط في نفس اللغة. والتقسيم الأشهر يبدأ بعائلتين: العائلة الأمرية Imperative، وفيها تقول للحاسوب كيف يصل إلى النتيجة خطوة بعد خطوة، وتغير قيم المتغيرات في الطريق، ومنها الإجرائية Procedural والكائنية Object-Oriented. والعائلة التصريحية Declarative، وفيها تصف النتيجة التي تريدها وتترك للغة أو للنظام تحديد الخطوات، ومنها الوظيفية Functional والمنطقية Logic ولغات الاستعلام والوصف مثل SQL وHTML. والصورة التالية تبين هذه العائلة:

![شجرة تبدأ بأنماط البرمجة وتتفرع إلى Imperative وتحتها Procedural مثل C وGo وObject-oriented مثل Java وC#، وإلى Declarative وتحتها Functional مثل Haskell وLogic مثل Prolog وQuery and config مثل SQL وHTML وHCL، وتحتها شريط للغات متعددة الأنماط مثل Python وJavaScript وKotlin وRust، وشريط للبرمجة القائمة على الأحداث](https://arabroot.io/content/images/2026/10/paradigms-types-01-paradigms.webp)

العائلة الأمرية تصف الخطوات، والتصريحية تصف النتيجة، وأغلب اللغات الحديثة تجمع أكثر من نمط

ولكي تكون المقارنة عادلة سوف نحل نفس المشكلة في كل نمط، وهي حساب إجمالي طلب في متجر فيه أربعة أسطر: قهوة بسعر 12 والكمية 2، وكعكة بسعر 18 والكمية 1، وعصير بسعر 9 والكمية 0 لأن العميل ألغاه، وماء بسعر 3 والكمية 4\. والإجمالي الصحيح هو 24 + 18 + 12 = 54، وسطر العصير يجب أن يتجاهل.

### البرمجة الإجرائية Procedural: خطوات تغير متغيراً

البرمجة الإجرائية Procedural Programming هي أقرب الأنماط لطريقة عمل المعالج نفسه: متغيرات في الذاكرة، وأوامر تنفذ بالترتيب، وحلقات Loops وشروط، ودوال Procedures تجمع الخطوات المتكررة. وهذا هو الحل بلغة Go في الملف `main.go`:

```go
package main

import "fmt"

type Line struct {
	Name  string
	Price int
	Qty   int
}

func main() {
	order := []Line{
		{"coffee", 12, 2},
		{"cake", 18, 1},
		{"juice", 9, 0},
		{"water", 3, 4},
	}

	total := 0
	for i := 0; i < len(order); i++ {
		if order[i].Qty > 0 {
			total = total + order[i].Price*order[i].Qty
			fmt.Println("after", order[i].Name, "total =", total)
		}
	}
	fmt.Println("order total:", total)
}
```

ونشغله بالأمر التالي:

```bash
docker run --rm --name lab-lang-go -v "$PWD":/w -w /w golang:1.27.1-alpine3.24 go run main.go
```

والمخرج سوف يكون كما يلي:

```
after coffee total = 24
after cake total = 42
after water total = 54
order total: 54
```

في الكود أعلاه لاحظ التالي:

- المتغير `total` يبدأ من صفر ثم تتغير قيمته في كل دورة من الحلقة، وهذا ما يسمى الحالة القابلة للتغيير Mutable State، وهي قلب البرمجة الأمرية.
- أنت من يحدد كل شيء: من أين تبدأ الحلقة، ومتى تتوقف، وكيف يتخطى الكود سطر العصير، ولهذا السبب فالكود واضح لمن يقرؤه خطوة خطوة، ولكنه أيضاً يفتح الباب لأخطاء مثل أن تبدأ العداد من 1 بدلاً من 0 فيضيع أول سطر.
- الأسطر المطبوعة تبين الحالة بعد كل خطوة: 24 ثم 42 ثم 54، ولا يوجد سطر للعصير لأن الشرط `Qty > 0` تخطاه.

ولغة C وPascal وGo هي أمثلة واضحة على هذا النمط، وإن كانت Go تضيف الواجهات Interfaces التي سوف نراها لاحقاً، ولا تحتوي على الوراثة Inheritance كما تقول [الأسئلة الشائعة للغة Go](https://go.dev/doc/faq?ref=arabroot.io).

### البرمجة الكائنية OOP: بيانات وسلوك في مكان واحد

عندما يكبر البرنامج تظهر مشكلة في الأسلوب الإجرائي، وهي أن البيانات (أسطر الطلب) في مكان، والدوال التي تعدلها في مكان آخر، وأي جزء من الكود يستطيع أن يضع كمية سالبة في الطلب دون أن يمنعه أحد. والبرمجة الكائنية Object-Oriented Programming واختصاراً OOP تحل هذا بأن تجمع البيانات والدوال التي تعمل عليها في كلاس Class واحد، وتخفي البيانات بحيث لا تتغير إلا عبر دوال الكلاس. والمثال التالي بلغة Java في الملف `Main.java`:

```java
import java.util.ArrayList;
import java.util.List;

interface Discount {
    int apply(int total);
}

class NoDiscount implements Discount {
    public int apply(int total) { return total; }
}

class PercentOff implements Discount {
    private final int percent;
    PercentOff(int percent) { this.percent = percent; }
    public int apply(int total) { return total - total * percent / 100; }
}

class Order {
    private final List<int[]> lines = new ArrayList<>();
    private final Discount discount;

    Order(Discount discount) { this.discount = discount; }

    void add(int price, int qty) {
        if (qty < 0) throw new IllegalArgumentException("qty must be >= 0");
        lines.add(new int[] {price, qty});
    }

    int total() {
        int sum = 0;
        for (int[] l : lines) sum += l[0] * l[1];
        return discount.apply(sum);
    }
}

public class Main {
    public static void main(String[] args) {
        for (Discount d : List.of(new NoDiscount(), new PercentOff(10))) {
            Order order = new Order(d);
            order.add(12, 2);
            order.add(18, 1);
            order.add(9, 0);
            order.add(3, 4);
            System.out.println(d.getClass().getSimpleName() + " -> " + order.total());
        }
        new Order(new NoDiscount()).add(5, -1);
    }
}
```

ونشغله بالأمر التالي، ولاحظ أن Java منذ الإصدار 11 تستطيع تشغيل ملف المصدر مباشرة دون خطوة `javac` منفصلة كما في [JEP 330](https://openjdk.org/jeps/330?ref=arabroot.io):

```bash
docker run --rm --name lab-lang-java -v "$PWD":/w -w /w eclipse-temurin:25-jdk-alpine java Main.java
```

والمخرج سوف يكون كما يلي:

```
NoDiscount -> 54
PercentOff -> 49
Exception in thread "main" java.lang.IllegalArgumentException: qty must be >= 0
	at Order.add(Main.java:25)
	at Main.main(Main.java:46)
```

في الكود أعلاه لاحظ التالي:

- **التغليف Encapsulation**: القائمة `lines` معرفة `private`، فلا يستطيع أي كود خارج الكلاس `Order` أن يضيف إليها مباشرة، والطريق الوحيد هو الدالة `add` التي ترفض الكمية السالبة، ولهذا ظهر الاستثناء `IllegalArgumentException` في آخر سطر من المخرج عندما حاولنا إضافة كمية `-1`.
- **تعدد الأشكال Polymorphism**: الكلاس `Order` لا يعرف نوع الخصم، فهو يتعامل مع الواجهة `Discount` فقط، ونفس الاستدعاء `discount.apply(sum)` أعطى 54 مع `NoDiscount` و49 مع `PercentOff(10)`، والسبب في 49 وليس 48.6 أن القسمة هنا على أعداد صحيحة Integers فيقطع الكسر.
- **التركيب Composition مقابل الوراثة Inheritance**: الطلب «لديه» خصم Has-a، ولم نجعل `DiscountedOrder` كلاساً يرث من `Order`. والسبب أن الوراثة تربط الكلاس الابن بتفاصيل الأب، فإذا احتجت طلباً بخصم وبشحن مجاني وبضريبة فسوف تنتهي بشجرة كلاسات لكل تركيبة، أما التركيب فتمرر فيه الكائنات التي تريدها. والوراثة لها مكانها عندما تكون العلاقة «هو نوع من» Is-a فعلاً، كما يشرح [درس الوراثة في توثيق Oracle](https://docs.oracle.com/javase/tutorial/java/IandI/subclasses.html?ref=arabroot.io).

ولاحظ أن الكائنية ما زالت أمرية في داخلها، فالدالة `total()` فيها حلقة تغير المتغير `sum` تماماً كما في Go، والفرق هو تنظيم الكود حول الكائنات وحماية حالتها.

### البرمجة الوظيفية Functional: دوال تحول البيانات

البرمجة الوظيفية Functional Programming تنظر إلى الحساب على أنه تطبيق دوال على بيانات لإنتاج Production بيانات جديدة، بدلاً من تغيير متغيرات. وفكرتها الأساسية ثلاث أدوات: `filter` تختار العناصر التي تحقق شرطاً، و`map` تحول كل عنصر إلى قيمة جديدة، و`reduce` تجمع القائمة في قيمة واحدة. والمثال التالي بلغة JavaScript في الملف `order.js`:

```javascript
const order = [
  { name: "coffee", price: 12, qty: 2 },
  { name: "cake",   price: 18, qty: 1 },
  { name: "juice",  price: 9,  qty: 0 },
  { name: "water",  price: 3,  qty: 4 },
];

const lineTotal = (line) => line.price * line.qty;   // pure function
const inStock   = (line) => line.qty > 0;

const total = order
  .filter(inStock)
  .map(lineTotal)
  .reduce((sum, x) => sum + x, 0);

console.log("order total:", total);
console.log("order unchanged:", order.length, "lines");

const applyTwice = (f, x) => f(f(x));                // higher-order function
console.log(applyTwice((n) => n * 2, 5));

const frozen = Object.freeze([1, 2, 3]);
try { frozen.push(4); } catch (e) { console.log(e.constructor.name + ":", e.message); }
```

ونشغله بالأمر التالي:

```bash
docker run --rm --name lab-lang-node -v "$PWD":/w -w /w node:22-alpine node order.js
```

والمخرج سوف يكون كما يلي:

```
order total: 54
order unchanged: 4 lines
20
TypeError: Cannot add property 3, object is not extensible
```

في الكود أعلاه لاحظ التالي:

- **الدوال النقية Pure Functions**: الدالة `lineTotal` تعطي نفس النتيجة دائماً لنفس المدخل، ولا تغير شيئاً خارجها، ولهذا يسهل اختبارها وحدها، ويسهل تشغيلها على عدة أنوية دون أقفال، وهو موضوع شرحناه في مقال [المعالج والخيوط والتزامن](https://arabroot.io/articles/%D8%A7%D9%84%D9%85%D8%B9%D8%A7%D9%84%D8%AC-%D9%88%D8%A7%D9%84%D8%AE%D9%8A%D9%88%D8%B7-%D9%88%D8%A7%D9%84%D8%AA%D8%B2%D8%A7%D9%85%D9%86-%D9%84%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6%D9%8A%D9%86/).
- **عدم التغيير Immutability**: بعد كل الحساب ما زال الطلب الأصلي فيه 4 أسطر، لأن `filter` و`map` تنتجان قوائم جديدة ولا تعدلان القائمة الأصلية. والسطر الأخير يبين أن `Object.freeze` يمنع التعديل فعلاً، فمحاولة `push` على المصفوفة المجمدة رمت `TypeError`، كما يوضح [توثيق MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global%5FObjects/Object/freeze?ref=arabroot.io).
- **الدوال عالية الرتبة Higher-Order Functions**: `applyTwice` تستقبل دالة كمعامل وتستدعيها مرتين، فأعطت 5 × 2 × 2 = 20\. وهذا ممكن لأن الدوال في JavaScript قيم عادية من الدرجة الأولى First-Class Functions كما في [MDN](https://developer.mozilla.org/en-US/docs/Glossary/First-class%5FFunction?ref=arabroot.io)، و`filter` و`map` و`reduce` نفسها دوال عالية الرتبة.

ونفس الفكرة في Python، مرة بالدوال `map` و`filter` و`reduce`، ومرة بالطريقة التي يفضلها مطورو Python وهي التعبير المولد Generator Expression، في الملف `order.py`:

```python
from functools import reduce

order = (("coffee", 12, 2), ("cake", 18, 1), ("juice", 9, 0), ("water", 3, 4))

total = reduce(lambda s, x: s + x,
               map(lambda l: l[1] * l[2],
                   filter(lambda l: l[2] > 0, order)), 0)
print("reduce:", total)
print("sum + generator:", sum(p * q for _, p, q in order if q > 0))
```

```bash
docker run --rm --name lab-lang-py -v "$PWD":/w -w /w python:3.13-slim python order.py
```

والمخرج سوف يكون كما يلي:

```
reduce: 54
sum + generator: 54
```

أما Haskell فهي لغة وظيفية نقية Purely Functional كما تصف نفسها في [موقعها الرسمي](https://www.haskell.org/?ref=arabroot.io)، أي أن الدوال فيها لا تغير شيئاً، ولا توجد فيها حلقة `for` أو متغير يتغير أصلاً، والمسألة كلها سطر واحد يقرأ تقريباً مثل تعريف رياضي: «مجموع السعر × الكمية لكل سطر كميته أكبر من صفر». وهذا هو الملف `order.hs`:

```haskell
order :: [(String, Int, Int)]
order = [("coffee", 12, 2), ("cake", 18, 1), ("juice", 9, 0), ("water", 3, 4)]

main :: IO ()
main = print (sum [p * q | (_, p, q) <- order, q > 0])
```

```bash
docker run --rm --name lab-lang-hs -v "$PWD":/w -w /w haskell:9-slim runghc order.hs
```

والمخرج سوف يكون كما يلي:

```
54
```

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

![مقارنة بمسارين: في الأعلى حلقة Go بخمس خطوات مرقمة تغير المتغير total من 0 إلى 24 ثم 42 ثم تتخطى العصير ثم 54، وفي الأسفل أنبوب JavaScript تمر فيه أسطر الطلب الأربعة على filter فتبقى ثلاثة ثم map فتصبح 24 و18 و12 ثم reduce فتصبح 54 والطلب الأصلي لم يتغير](https://arabroot.io/content/images/2026/10/paradigms-types-02-imperative-functional.webp)

نفس الإجمالي 54: حلقة تغير متغيراً، أو أنبوب ينتج قيماً جديدة

وقد يتساءل البعض: هل الأسلوب الوظيفي أبطأ لأنه ينشئ قوائم جديدة؟ والإجابة أنه قد ينشئ قوائم وسيطة فعلاً كما في JavaScript أعلاه، وهذا لا يهم في طلب من أربعة أسطر، ولكنه يهم في ملايين العناصر، ولهذا تستخدم لغات مثل Python المولدات Generators التي تمرر العنصر تلو الآخر دون قائمة وسيطة، وتستخدم Haskell التقييم الكسول Lazy Evaluation. والقاعدة العملية أن تكتب الكود الأوضح أولاً، ثم تقيس قبل أن تغيره من أجل السرعة.

### البرمجة التصريحية Declarative: صف النتيجة واترك الطريقة

في الأنماط السابقة كتبت بنفسك كيف يحسب الإجمالي، حتى في الأسلوب الوظيفي اخترت ترتيب `filter` ثم `map` ثم `reduce`. أما في البرمجة التصريحية Declarative Programming فأنت تصف ما تريده فقط، والنظام يختار الطريقة. وأشهر مثال تستخدمه كل يوم هو SQL، وهذا الملف `order.sql`:

```sql
CREATE TABLE order_lines (name text, price int, qty int);
INSERT INTO order_lines VALUES ('coffee', 12, 2), ('cake', 18, 1), ('juice', 9, 0), ('water', 3, 4);

SELECT sum(price * qty) AS order_total
FROM order_lines
WHERE qty > 0;

EXPLAIN SELECT sum(price * qty) FROM order_lines WHERE qty > 0;
```

ونشغله على PostgreSQL في Container مؤقت، ثم نحذفه:

```bash
docker run -d --name lab-lang-pg -e POSTGRES_HOST_AUTH_METHOD=trust -v "$PWD":/w postgres:17.11-alpine
docker exec lab-lang-pg pg_isready -U postgres
docker exec lab-lang-pg psql -U postgres -f /w/order.sql
docker rm -f lab-lang-pg
```

والمخرج سوف يكون كما يلي:

```
CREATE TABLE
INSERT 0 4
 order_total
-------------
          54
(1 row)

                             QUERY PLAN
--------------------------------------------------------------------
 Aggregate  (cost=27.00..27.01 rows=1 width=8)
   ->  Seq Scan on order_lines  (cost=0.00..25.00 rows=400 width=8)
         Filter: (qty > 0)
(3 rows)
```

في الإعداد أعلاه لاحظ التالي:

- الاستعلام لا يحتوي على حلقة ولا على متغير، وإنما يقول: «أعطني مجموع السعر × الكمية من الأسطر التي كميتها أكبر من صفر».
- الأمر `EXPLAIN` يكشف الخطة Query Plan التي اختارها مخطط الاستعلامات Planner في PostgreSQL: قراءة الجدول كله Seq Scan مع فلتر، ثم تجميع Aggregate. ولو كان على العمود فهرس Index وكان الجدول كبيراً فقد يختار المخطط طريقة أخرى دون أن تغير حرفاً في استعلامك، وهذا هو جوهر التصريحية كما يشرح [توثيق PostgreSQL](https://www.postgresql.org/docs/current/planner-optimizer.html?ref=arabroot.io).

ونفس الفكرة في ملفات البنية التحتية، فتوثيق Terraform يقول صراحة إن [لغة Terraform تصريحية](https://developer.hashicorp.com/terraform/language?ref=arabroot.io)، تصف الهدف المطلوب وليس الخطوات للوصول إليه، وترتيب الكتل في الملفات غير مهم. وهذا الملف `main.tf` يحسب نفس الإجمالي بلغة HCL دون أي مزود Provider، فقط لنرى الأسلوب:

```hcl
locals {
  order = [
    { name = "coffee", price = 12, qty = 2 },
    { name = "cake",   price = 18, qty = 1 },
    { name = "juice",  price = 9,  qty = 0 },
    { name = "water",  price = 3,  qty = 4 },
  ]
  total = sum([for l in local.order : l.price * l.qty if l.qty > 0])
}

output "order_total" {
  value = local.total
}
```

```bash
docker run --rm --name lab-lang-tf -v "$PWD":/w -w /w hashicorp/terraform:latest init
docker run --rm --name lab-lang-tf -v "$PWD":/w -w /w hashicorp/terraform:latest plan
```

والمخرج من الأمر `plan` سوف يكون كما يلي:

```
Changes to Outputs:
  + order_total = 54

You can apply this plan to save these new output values to the Terraform
state, without changing any real infrastructure.
```

ولاحظ أن `local.total` يستخدم `local.order` المعرف قبله، ولو نقلت تعريف `total` إلى أعلى الملف فلن يتغير شيء، لأن Terraform يبني ترتيب التنفيذ من العلاقات بين القيم وليس من ترتيب الأسطر. وفي الحياة الحقيقية تكتب بنفس الطريقة «أريد سيرفراً بهذه المواصفات وسجل DNS يشير إليه»، وTerraform يقارن الوصف بالواقع ويقرر ما الذي يجب إنشاؤه أو تعديله.

أما HTML وCSS فهما تصريحيتان بنفس المعنى: تكتب `<button>` ولا تشرح للمتصفح كيف يرسم الزر بكسلاً بكسلاً، وتكتب `display: grid` ولا تحسب مواقع العناصر بنفسك، والمتصفح يقرر الطريقة.

### البرمجة المنطقية Logic Programming: حقائق وقواعد والسؤال عليك

البرمجة المنطقية Logic Programming تأخذ التصريحية إلى أبعد نقطة: تكتب حقائق Facts وقواعد Rules، ثم تسأل سؤالاً، ومحرك اللغة يبحث بنفسه عن الإجابات التي تحقق القواعد. وأشهر لغاتها Prolog، وهذا الملف `order.pl` بلغة [SWI-Prolog](https://www.swi-prolog.org/?ref=arabroot.io):

```prolog
line(coffee, 12, 2).
line(cake,   18, 1).
line(juice,   9, 0).
line(water,   3, 4).

line_total(Name, T) :- line(Name, P, Q), Q > 0, T is P * Q.

order_total(Total) :- findall(T, line_total(_, T), Ts), sum_list(Ts, Total).
```

ونسأل المحرك عن الإجمالي، ثم عن كل سطر له إجمالي:

```bash
docker run --rm --name lab-lang-pl -v "$PWD":/w -w /w swipl:latest swipl -q \
  -g "order_total(T), format('order total: ~w~n', [T]), forall(line_total(N, X), format('~w ~w~n', [N, X]))" \
  -t halt order.pl
```

والمخرج سوف يكون كما يلي:

```
order total: 54
coffee 24
cake 18
water 12
```

في الكود أعلاه لاحظ أن القاعدة `line_total` تقرأ «إجمالي السطر Name هو T إذا كان هناك سطر سعره P وكميته Q، وكانت Q أكبر من صفر، وكانت T تساوي P × Q»، ولم نكتب حلقة تمر على الأسطر، وإنما المحرك هو الذي جرب كل الحقائق ووجد ثلاثة تحقق القاعدة، وتجاهل العصير لأن شرط `Q > 0` فشل فيه. وهذا النمط قليل في تطبيقات الويب، ولكن أفكاره حاضرة في أنظمة القواعد ومحركات السياسات، وفي لغة Datalog التي تستخدمها بعض أدوات تحليل الكود.

### البرمجة القائمة على الأحداث Event-Driven: الكود ينتظر ما يحدث

هناك أسلوب آخر لا يدخل في الشجرة السابقة لأنه يعمل فوق أي نمط، وهو البرمجة القائمة على الأحداث Event-Driven Programming. فبدلاً من أن يحدد البرنامج ترتيب التنفيذ من البداية، يسجل دوالاً تستجيب لأحداث Events: ضغطة زر في الواجهة، أو طلب HTTP وصل إلى السيرفر، أو منتج أضيف إلى السلة. ويقول [توثيق Node.js](https://nodejs.org/api/events.html?ref=arabroot.io) إن أغلب واجهات Node.js مبنية على هذا الأسلوب. وهذا المثال في الملف `order.js` داخل مجلد آخر:

```javascript
const { EventEmitter } = require("node:events");

const shop = new EventEmitter();
let total = 0;

shop.on("item-added", ({ name, price, qty }) => {
  total += price * qty;
  console.log(`event item-added: ${name} -> total ${total}`);
});
shop.on("checkout", () => console.log("event checkout: pay", total));

shop.emit("item-added", { name: "coffee", price: 12, qty: 2 });
shop.emit("item-added", { name: "cake", price: 18, qty: 1 });
shop.emit("item-added", { name: "water", price: 3, qty: 4 });
shop.emit("checkout");
```

```bash
docker run --rm --name lab-lang-ev -v "$PWD":/w -w /w node:22-alpine node order.js
```

والمخرج سوف يكون كما يلي:

```
event item-added: coffee -> total 24
event item-added: cake -> total 42
event item-added: water -> total 54
event checkout: pay 54
```

ولاحظ أن الكود الذي يحسب الإجمالي لا يعرف متى سوف يضاف المنتج القادم، وإنما يستجيب عندما يحدث الحدث. وهذا هو الأساس الذي تقوم عليه حلقة الأحداث Event Loop في Node.js والمتصفح، وقد شرحنا حدودها في مقال المعالج والتزامن. والبرمجة التفاعلية Reactive Programming تبني على نفس الفكرة، ولكنها تتعامل مع الأحداث كتدفق Stream من القيم، وتطبق عليه `map` و`filter` كما في البرمجة الوظيفية، ومن مكتباتها المعروفة [ReactiveX](https://reactivex.io/?ref=arabroot.io) مثل RxJS وRxJava.

### أغلب اللغات الحديثة متعددة الأنماط Multi-Paradigm

بعد كل هذا قد يبدو أن كل لغة تتبع نمطاً واحداً، ولكن الواقع أن أغلب اللغات التي تستخدمها اليوم متعددة الأنماط Multi-Paradigm. فتوثيق Python نفسه في [دليل البرمجة الوظيفية](https://docs.python.org/3/howto/functional.html?ref=arabroot.io) يقول إن Python وC++ وLisp لغات متعددة الأنماط، يمكنك أن تكتب بها برامج إجرائية أو كائنية أو وظيفية. وقد رأيت ذلك فعلاً في هذا المقال: Python كتبنا بها حلاً وظيفياً، وJavaScript كتبنا بها حلاً وظيفياً وحلاً قائماً على الأحداث، وJava منذ الإصدار 8 تدعم الدوال Lambdas وStreams. لذلك فالسؤال العملي ليس «ما هو نمط هذه اللغة؟» وإنما «ما هو النمط الأنسب لهذا الجزء من الكود؟»، فحساب إجمالي من قائمة يناسبه الأسلوب الوظيفي، ونموذج طلب له قواعد وحالات يناسبه الكلاس، والاستعلام عن البيانات مكانه SQL.

## مترجمة Compiled أم مفسرة Interpreted؟

قبل الدخول في الأنواع بقي سؤال يخلط بينه وبين الأنواع كثير من المبتدئين، وهو هل اللغة مترجمة Compiled أم مفسرة Interpreted. وقد شرحنا ذلك بالتفصيل في مقال [ماذا يحدث عندما تكتب برنامج Hello World وتشغله](https://arabroot.io/articles/%D8%B1%D8%AD%D9%84%D8%A9-hello-world-%D9%85%D9%86-%D9%84%D9%88%D8%AD%D8%A9-%D8%A7%D9%84%D9%85%D9%81%D8%A7%D8%AA%D9%8A%D8%AD-%D8%A7%D9%84%D9%89-%D8%A7%D9%84%D8%B4%D8%A7%D8%B4%D8%A9/#compiled-interpreted): الترجمة المسبقة Ahead-of-Time تحول الكود إلى تعليمات آلة قبل التشغيل كما في C وGo وRust، والمفسر يقرأ تعليمات وسيطة Bytecode وينفذها كما في CPython، والترجمة أثناء التشغيل JIT تترجم الأجزاء المتكررة وهي تعمل كما في Java وV8\. والذي يهمنا هنا أن طريقة التنفيذ وصفة لتطبيق Implementation معين وليست للغة نفسها، فلغة Python لها مفسر CPython ولها PyPy الذي يستخدم JIT، وأن طريقة التنفيذ لا تحدد نظام الأنواع، فاللغة المترجمة قد تفحص الأنواع قبل التشغيل وقد لا تفحصها، كما سوف ترى في الأقسام التالية.

## أنظمة الأنواع Type Systems: متى تكتشف اللغة أخطاءك؟

النوع Type هو الذي يحدد ما هي القيم المسموحة لمتغير، وما هي العمليات التي يمكن إجراؤها عليها: فالرقم يجمع ويضرب، والنص يقطع ويدمج، وجمع رقم مع نص سؤال يجيب عنه نظام الأنواع Type System في كل لغة بطريقته. وهناك سؤالان منفصلان يخلط بينهما كثيرون: متى تفحص اللغة الأنواع، وكم تتسامح عندما يختلف النوعان.

### الأنواع الثابتة Static والديناميكية Dynamic: قبل التشغيل أم أثناءه؟

في الأنواع الثابتة Static Typing يعرف المترجم نوع كل متغير وكل تعبير قبل التشغيل، ويرفض البرنامج إذا وجد عملية لا تناسب الأنواع، وتوثيق Java يقول ذلك صراحة في [مواصفات اللغة](https://docs.oracle.com/javase/specs/jls/se25/html/jls-4.html?ref=arabroot.io): كل متغير وكل تعبير له نوع معروف وقت الترجمة Compile Time. أما في الأنواع الديناميكية Dynamic Typing فالنوع يرتبط بالقيمة نفسها أثناء التشغيل Runtime، والمتغير يمكن أن يحمل رقماً ثم نصاً، والفحص يحدث فقط عندما ينفذ السطر. ولنرى الفرق بخطأ واحد في الحالتين، وهذا الملف `report.py`:

```python
def total(order):
    return sum(price * qty for price, qty in order)

def report(order, show_tax):
    t = total(order)
    if show_tax:
        return "total with tax: " + t * 1.15
    return f"total: {t}"

order = [(12, 2), (18, 1), (3, 4)]
print(report(order, show_tax=False))
print(report(order, show_tax=True))
```

```bash
docker run --rm --name lab-lang-py -v "$PWD":/w -w /w python:3.13-slim python report.py
```

والمخرج سوف يكون كما يلي:

```
total: 54
Traceback (most recent call last):
  File "/w/report.py", line 12, in <module>
    print(report(order, show_tax=True))
          ~~~~~~^^^^^^^^^^^^^^^^^^^^^^
  File "/w/report.py", line 7, in report
    return "total with tax: " + t * 1.15
           ~~~~~~~~~~~~~~~~~~~^~~~~~~~~~
TypeError: can only concatenate str (not "float") to str
```

لاحظ أن البرنامج عمل وطبع السطر الأول بنجاح، والخطأ في السطر 7 كان موجوداً من البداية، ولكن Python لم تكتشفه إلا عندما نفذ الفرع `show_tax=True`. ولو كان هذا الفرع لا ينفذ إلا في نهاية الشهر عند إصدار الفواتير، فسوف يمر البرنامج من كل اختباراتك اليدوية ويصل إلى السيرفر، ويكتشف المستخدم الخطأ قبلك. والآن نفس البرنامج بلغة Go في الملف `main.go`:

```go
package main

import "fmt"

func total(order [][2]int) int {
	sum := 0
	for _, l := range order {
		sum += l[0] * l[1]
	}
	return sum
}

func report(order [][2]int, showTax bool) string {
	t := total(order)
	if showTax {
		return "total with tax: " + float64(t)*1.15
	}
	return fmt.Sprintf("total: %d", t)
}

func main() {
	order := [][2]int{{12, 2}, {18, 1}, {3, 4}}
	fmt.Println(report(order, false))
}
```

```bash
docker run --rm --name lab-lang-go -v "$PWD":/w -w /w golang:1.27.1-alpine3.24 go run main.go
```

والمخرج سوف يكون كما يلي:

```
# command-line-arguments
./main.go:16:10: invalid operation: "total with tax: " + float64(t) * 1.15 (mismatched types untyped string and float64)
```

لاحظ أن الدالة `main` لا تستدعي الفرع الخاطئ أصلاً، فهي تمرر `false`، ومع ذلك رفض المترجم البرنامج كله ولم يطبع حتى السطر الأول، لأنه يفحص كل الكود وليس فقط الأسطر التي سوف تنفذ. والصورة التالية تبين أين يمسك كل نظام هذا الخطأ:

![خط زمني بأربع محطات مرقمة: الكتابة ثم الترجمة والفحص ثم التشغيل والاختبار ثم المستخدم، وفي مسار Go يتوقف الخطأ عند المحطة الثانية برسالة invalid operation، وفي مسار Python يمر الخطأ من الترجمة ويعمل البرنامج حتى ينفذ الفرع الخاطئ فيظهر TypeError في الاختبار أو عند المستخدم، وتحتها شريط يبين أن تكلفة الإصلاح تزيد كلما تأخر اكتشاف الخطأ](https://arabroot.io/content/images/2026/10/paradigms-types-03-static-dynamic.webp)

الخطأ نفسه: المترجم يمسكه عند المحطة الثانية، والمفسر عند تنفيذ السطر

وقد يتساءل البعض: إذا كانت الأنواع الثابتة تمسك الأخطاء مبكراً فلماذا لا تستخدمها كل اللغات؟ والإجابة أن لها ثمناً: كود أكثر لتعريف الأنواع، وخطوة ترجمة تنتظرها، وبعض الأشياء التي تكتب في سطر واحد في Python مثل قراءة JSON بشكل غير معروف مسبقاً تحتاج في Java عملاً أكثر. والأنواع الديناميكية تعطيك سرعة في البداية ومرونة في السكربتات الصغيرة، ولكن كلما كبر الكود وزاد عدد المطورين زادت قيمة المترجم الذي يمسك الأخطاء عنك، ولهذا السبب ظهرت الأنواع التدريجية التي سوف نراها بعد قليل. ولاحظ أن الاختبارات Tests تعوض جزءاً من ذلك في اللغات الديناميكية، فالخطأ أعلاه كان سوف يظهر في اختبار يغطي الفرع `show_tax=True`، ولكنها لا تفحص إلا الحالات التي كتبت لها اختباراً.

### الأنواع القوية Strong والضعيفة Weak: هل تحول اللغة الأنواع بصمت؟

السؤال الثاني مستقل عن الأول: عندما تجمع نصاً مع رقم، هل ترفض اللغة العملية، أم تحول أحدهما إلى نوع الآخر بصمت Implicit Conversion وتكمل؟ اللغة التي ترفض تسمى قوية الأنواع Strongly Typed، والتي تحول بصمت تسمى ضعيفة الأنواع Weakly Typed. وهذا المثال بلغة JavaScript في الملف `weak.js`:

```javascript
console.log("1" + 1);
console.log("5" - 2);
console.log("5" * "2");
console.log(1 == "1", 1 === "1");
console.log([] + {});
```

```bash
docker run --rm --name lab-lang-node -v "$PWD":/w -w /w node:22-alpine node weak.js
```

والمخرج سوف يكون كما يلي:

```
11
3
10
true false
[object Object]
```

لاحظ أن `"1" + 1` أعطت النص `11` لأن `+` مع نص تعني الدمج، بينما `"5" - 2` أعطت الرقم 3 لأن الطرح لا يعمل إلا على الأرقام فحولت النص إلى رقم، و`1 == "1"` صحيحة لأن `==` تحول الأنواع قبل المقارنة، و`===` لا تحول فأعطت `false`. وتوثيق [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Data%5Fstructures?ref=arabroot.io) يصف JavaScript بأنها لغة ضعيفة الأنواع تسمح بالتحويل الضمني بدلاً من رمي خطأ، ولهذا السبب ينصح دائماً باستخدام `===`. والآن نفس العملية في Python، في الملف `strong.py`:

```python
print("5" * 2)
print(int("1") + 1)
print("1" + 1)
```

```bash
docker run --rm --name lab-lang-py -v "$PWD":/w -w /w python:3.13-slim python strong.py
```

والمخرج سوف يكون كما يلي:

```
55
2
Traceback (most recent call last):
  File "/w/strong.py", line 3, in <module>
    print("1" + 1)
          ~~~~^~~
TypeError: can only concatenate str (not "int") to str
```

لاحظ أن Python ديناميكية وقوية معاً: لم تفحص الأنواع قبل التشغيل، ولكنها عندما وصلت إلى `"1" + 1` رفضت أن تخمن ما تريده ورمت `TypeError`، والتحويل لا يحدث إلا إذا طلبته صراحة ب `int("1")`. ولاحظ أيضاً أن `"5" * 2` أعطت `55`، وهذا ليس تحويلاً للأنواع، وإنما عملية معرفة لتكرار النص، فالقوة لا تعني أن كل عملية بين نوعين ممنوعة.

وقد يتفاجأ البعض بأن لغة ثابتة الأنواع مثل C ضعيفة في نفس الوقت، وهذا الملف `weak.c` يبين السبب:

```c
#include <stdio.h>

int main(void) {
    char c = 'A';
    int n = c + 1;
    double price = 12.99;
    int cents = price;
    printf("%d %d\n", n, cents);
    if (-1 < sizeof(int))
        printf("-1 is less than 4\n");
    else
        printf("-1 is NOT less than 4\n");
    return 0;
}
```

```bash
docker run --rm --name lab-lang-c -v "$PWD":/w -w /w rust:1-alpine sh -c 'gcc -o /tmp/weak weak.c && /tmp/weak && gcc -Wall -Wextra -o /tmp/weak weak.c'
```

والمخرج سوف يكون كما يلي:

```
66 12
-1 is NOT less than 4
$ gcc -Wall -Wextra -o weak weak.c
weak.c: In function 'main':
weak.c:9:12: warning: comparison of integer expressions of different signedness: 'int' and 'long unsigned int' [-Wsign-compare]
    9 |     if (-1 < sizeof(int))
      |            ^
```

في الكود أعلاه لاحظ التالي:

- الحرف `'A'` أصبح الرقم 66 بعد الجمع لأن الحروف في C أرقام صغيرة، والسعر `12.99` أصبح 12 بصمت عند وضعه في متغير `int`، دون أي خطأ.
- والأخطر أن `-1 < sizeof(int)` كانت خاطئة، فالبرنامج يقول إن -1 ليست أصغر من 4\. والسبب أن `sizeof` تعيد نوعاً بدون إشارة Unsigned، فتحول C الرقم -1 إليه بقواعد التحويلات الحسابية المعتادة Usual Arithmetic Conversions كما في [مرجع cppreference](https://en.cppreference.com/w/c/language/conversion.html?ref=arabroot.io)، فيصبح أكبر رقم ممكن.
- الترجمة العادية لم تظهر أي تحذير، والتحذير `-Wsign-compare` ظهر فقط مع `-Wall -Wextra`، ولهذا لا تقم بترجمة كود C بدون هذه الخيارات، والسبب أنها الطريقة الوحيدة لتعرف أن المترجم حول شيئاً بصمت، كما يشرح [توثيق GCC](https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html?ref=arabroot.io).

والصورة التالية تضع اللغات على السؤالين معاً:

![مربع من أربعة أجزاء: الأنواع الثابتة على اليمين والديناميكية على اليسار، والقوية في الأعلى والضعيفة في الأسفل، وفي الثابتة القوية Haskell وRust وJava وC# وKotlin وSwift وGo، وفي الديناميكية القوية Python وRuby، وفي الثابتة الضعيفة C وC++ وTypeScript مع سهم إلى JavaScript لأنها تعمل كJavaScript، وفي الديناميكية الضعيفة JavaScript وPHP مع سهم strict_types، مع أمثلة من المخرجات الحقيقية](https://arabroot.io/content/images/2026/10/paradigms-types-04-strong-weak.webp)

متى تفحص الأنواع شيء، وكم تتسامح مع خلطها شيء آخر

ولاحظ أن «قوية» و«ضعيفة» ليس لهما تعريف رسمي متفق عليه مثل «ثابتة» و«ديناميكية»، وإنما هما طيف Spectrum: فC++ أقوى من C في بعض المواضع، وPHP تصبح أكثر صرامة مع `declare(strict_types=1)` في معاملات الدوال كما في [توثيق PHP](https://www.php.net/manual/en/language.types.declarations.php?ref=arabroot.io)، وTypeScript تفحص الأنواع قبل التشغيل ثم يعمل الكود كJavaScript بكل تحويلاتها. لذلك فالمواقع في الصورة تقريبية، والمهم أن تعرف كيف تتصرف لغتك في الحالات التي تخصك.

### استنتاج الأنواع Type Inference: ثابتة دون أن تكتب النوع

من أكثر ما ينفر المبتدئين من الأنواع الثابتة أنهم يتخيلون كتابة النوع بجانب كل متغير كما في Java القديمة. ولكن أغلب اللغات الثابتة الحديثة تستنتج النوع Type Inference من القيمة التي تضعها، فتكتب كأنك في لغة ديناميكية، ويبقى الفحص ثابتاً. وهذا مثال بلغة Go في الملف `main.go`:

```go
package main

import "fmt"

func main() {
	total := 54
	price := 12.5
	name := "coffee"
	fmt.Printf("%T %T %T\n", total, price, name)
	// total = "fifty four"
}
```

```bash
docker run --rm --name lab-lang-go -v "$PWD":/w -w /w golang:1.27.1-alpine3.24 go run main.go
```

والمخرج سوف يكون كما يلي:

```
int float64 string
```

لم نكتب كلمة `int` أو `string` في أي مكان، والمعامل `:=` في Go يعرف المتغير ويستنتج نوعه كما في [مواصفات Go](https://go.dev/ref/spec?ref=arabroot.io). والآن نزيل التعليق عن السطر الأخير لنضع نصاً في المتغير `total`:

```
# command-line-arguments
./main.go:10:10: cannot use "fifty four" (untyped string constant) as int value in assignment
```

لاحظ أن النوع استنتج مرة واحدة عند التعريف، ثم أصبح ثابتاً، فالمتغير `total` هو `int` إلى الأبد. ونفس الشيء في Rust مع `let`، وهذا الملف `main.rs`:

```rust
fn main() {
    let prices = vec![12, 18, 3];
    let mut total = 0;
    for p in &prices {
        total += p;
    }
    total = "thirty three";
    println!("{total}");
}
```

```bash
docker run --rm --name lab-lang-rs -v "$PWD":/w -w /w rust:1-alpine rustc main.rs
```

والمخرج سوف يكون كما يلي:

```
error[E0308]: mismatched types
 --> main.rs:7:13
  |
3 |     let mut total = 0;
  |                     - expected due to this value
...
7 |     total = "thirty three";
  |             ^^^^^^^^^^^^^^ expected integer, found `&str`

error: aborting due to 1 previous error

For more information about this error, try `rustc --explain E0308`.
```

لاحظ أن رسالة Rust تشير إلى السطر الذي استنتجت منه النوع `expected due to this value`، ثم إلى السطر الذي خالفه، وهذا ما يجعل الاستنتاج مريحاً: المترجم يشرح لك من أين جاء بالنوع. وتوثيق Rust يقول في [فصل أنواع البيانات](https://doc.rust-lang.org/book/ch03-02-data-types.html?ref=arabroot.io) إن المترجم يستطيع غالباً استنتاج النوع من القيمة وطريقة استخدامها، ونفس الفكرة في TypeScript كما سوف ترى في القسم التالي.

### الأنواع التدريجية Gradual Typing: TypeScript وPython مع mypy

لنفرض أن لديك مشروعاً كبيراً بلغة JavaScript أو Python، وبدأت الأخطاء من نوع «undefined is not a function» تصل إلى المستخدمين، فإعادة كتابة المشروع بلغة ثابتة الأنواع ليست خياراً واقعياً. وهنا جاءت الأنواع التدريجية Gradual Typing، وفكرتها التي تشرحها [PEP 483](https://peps.python.org/pep-0483/?ref=arabroot.io) أن تضيف الأنواع إلى جزء من الكود، ويبقى الباقي ديناميكياً، ثم تزيد التغطية تدريجياً.

ولنبدأ ب TypeScript، وهي JavaScript مع أنواع، والملف `order.ts` التالي فيه خطآن مقصودان:

```typescript
interface Line {
  name: string;
  price: number;
  qty: number;
}

function total(order: Line[]): number {
  return order.filter((l) => l.qty > 0).reduce((s, l) => s + l.price * l.qty, 0);
}

let count = 4;            // inferred as number
count = "four";

const order: Line[] = [
  { name: "coffee", price: 12, qty: 2 },
  { name: "cake", price: "18", qty: 1 },
];
console.log(total(order));
```

ونثبت المترجم `tsc` داخل Container ثم نفحص الملف ونشغل الناتج:

```bash
docker run --rm --name lab-lang-ts -v "$PWD":/w -w /w node:22-alpine sh -c \
  'npm i -g typescript@7.0.2 >/dev/null 2>&1; tsc --strict --target es2022 order.ts; node order.js'
```

والمخرج سوف يكون كما يلي:

```
order.ts(12,1): error TS2322: Type 'string' is not assignable to type 'number'.
order.ts(16,19): error TS2322: Type 'string' is not assignable to type 'number'.
42
```

في المخرج أعلاه لاحظ التالي:

- الخطأ الأول في السطر 12 جاء من استنتاج النوع: `count` بدأ رقماً فأصبح `number`، والخطأ الثاني في السطر 16 لأن السعر `"18"` نص والواجهة `Line` تقول `number`.
- ومع وجود الخطأين أنشأ `tsc` الملف `order.js` وعمل وطبع 42 بدلاً من 54، فقد ضربت JavaScript النص `"18"` في 1 فحولته إلى رقم بصمت. والسبب أن الخيار [noEmitOnError](https://www.typescriptlang.org/tsconfig/?ref=arabroot.io#noEmitOnError) معطل افتراضياً، ولهذا اجعله `true` في مشروعك، أو اجعل خط CI يفشل عندما يفشل `tsc`.

وإذا صححنا الخطأين، فهذا هو ملف JavaScript الذي ينتجه `tsc` من الملف `ok.ts`:

```javascript
"use strict";
function total(order) {
    return order.filter((l) => l.qty > 0).reduce((s, l) => s + l.price * l.qty, 0);
}
const order = [
    { name: "coffee", price: 12, qty: 2 },
    { name: "cake", price: 18, qty: 1 },
];
console.log(total(order));
```

لاحظ أن الواجهة `Line` وكل `: number` اختفت تماماً، وهذا ما يسميه توثيق TypeScript [الأنواع الممحوة Erased Types](https://www.typescriptlang.org/docs/handbook/typescript-from-scratch.html?ref=arabroot.io): الأنواع موجودة فقط وقت الفحص، والذي يعمل في المتصفح أو Node.js هو JavaScript عادية لا تعرف شيئاً عنها. لذلك فالبيانات التي تأتي من خارج برنامجك، مثل جسم طلب HTTP أو ملف JSON، لا يحميها TypeScript وحده، وتحتاج إلى تحقق Validation وقت التشغيل.

وفي Python جاءت تلميحات الأنواع Type Hints مع [PEP 484](https://peps.python.org/pep-0484/?ref=arabroot.io)، ولكن لاحظ الجملة الأولى في [توثيق الوحدة typing](https://docs.python.org/3/library/typing.html?ref=arabroot.io): مفسر Python لا يفرض هذه التلميحات، وإنما تستخدمها أدوات خارجية مثل [mypy](https://mypy.readthedocs.io/en/stable/?ref=arabroot.io). وهذا الملف `app.py`:

```python
from dataclasses import dataclass

@dataclass
class Line:
    name: str
    price: int
    qty: int

def total(order: list[Line]) -> int:
    return sum(l.price * l.qty for l in order if l.qty > 0)

def receipt(order: list[Line]) -> str:
    return "total: " + total(order)

def legacy(order):
    return "total: " + total(order)

order = [Line("coffee", 12, 2), Line("cake", "18", 1)]
print(order[1])
print(total(order))
```

نشغله أولاً بـ Python مباشرة:

```bash
docker run --rm --name lab-lang-py -v "$PWD":/w -w /w python:3.13-slim python app.py
```

والمخرج سوف يكون كما يلي:

```
Line(name='cake', price='18', qty=1)
Traceback (most recent call last):
  File "/w/app.py", line 25, in <module>
    print(total(order))
          ~~~~~^^^^^^^
  File "/w/app.py", line 12, in total
    return sum(l.price * l.qty for l in order if l.qty > 0)
TypeError: unsupported operand type(s) for +: 'int' and 'str'
```

لاحظ أن ال Dataclass قبلت السعر `'18'` كنص رغم أن التلميح يقول `int`، ولم يظهر الخطأ إلا عند الجمع داخل `total`. والآن نفحص نفس الملف ب mypy قبل التشغيل:

```bash
docker run --rm --name lab-lang-mypy -v "$PWD":/w -w /w python:3.13-slim sh -c \
  'pip install -q mypy==2.4.0 && mypy app.py; mypy --check-untyped-defs app.py'
```

والمخرج سوف يكون كما يلي:

```
app.py:16: error: Unsupported operand types for + ("str" and "int")  [operator]
app.py:23: error: Argument 2 to "Line" has incompatible type "str"; expected "int"  [arg-type]
Found 2 errors in 1 file (checked 1 source file)
$ mypy --check-untyped-defs app.py
app.py:16: error: Unsupported operand types for + ("str" and "int")  [operator]
app.py:20: error: Unsupported operand types for + ("str" and "int")  [operator]
app.py:23: error: Argument 2 to "Line" has incompatible type "str"; expected "int"  [arg-type]
Found 3 errors in 1 file (checked 1 source file)
```

في المخرج أعلاه لاحظ التالي:

- mypy وجد خطأ السعر في السطر 23 قبل أن يعمل البرنامج، ووجد أيضاً خطأ في السطر 16 داخل الدالة `receipt` لم يظهر في التشغيل أصلاً لأن أحداً لم يستدعها، وهذه بالضبط فائدة الفحص الثابت.
- الدالة `legacy` فيها نفس الخطأ في السطر 20، ولكن mypy تجاهلها في الفحص الأول لأنها بدون تلميحات، فهذا هو الجزء الديناميكي من «التدريجي». ومع الخيار `--check-untyped-defs` فحص بداخلها أيضاً فظهرت ثلاثة أخطاء. لذلك لا تقم بإضافة mypy لمشروع قديم ثم تفترض أن كل الكود مفحوص، والسبب أن الدوال بدون تلميحات تبقى خارج الفحص حتى تضيف لها التلميحات أو تفعل هذا الخيار.

والصورة التالية تجمع الطريقين:

![مسار TypeScript: الملف order.ts يمر على tsc الذي يفحص الأنواع ويظهر الخطأ TS2322، ثم ينتج order.js بدون أنواع يعمل في Node.js أو المتصفح، ومسار Python: الملف app.py يذهب في طريق إلى python3 الذي يتجاهل التلميحات فيظهر TypeError أثناء التشغيل، وفي طريق آخر إلى mypy الذي يظهر الأخطاء قبل التشغيل](https://arabroot.io/content/images/2026/10/paradigms-types-05-gradual.webp)

الأنواع التدريجية فحص قبل التشغيل، والكود الذي يعمل لا يعرف عنها شيئاً

### الأنواع الاسمية Nominal والبنيوية Structural: هل يهم اسم النوع أم شكله؟

عندما تكتب دالة تستقبل «أي شيء له دالة `total()`»، يبقى سؤال: كيف تقرر اللغة أن الكائن الذي مررته مقبول؟ في النظام الاسمي Nominal Typing يجب أن يعلن الكلاس صراحة أنه يطبق الواجهة، وفي النظام البنيوي Structural Typing يكفي أن يملك الكائن الدوال المطلوبة بنفس الأشكال. وهذا الملف `Main.java`، فيه كلاس `Invoice` له دالة `total()` ولكنه لا يذكر الواجهة:

```java
interface HasTotal {
    int total();
}

class Invoice {
    public int total() { return 54; }
}

public class Main {
    static void printTotal(HasTotal x) {
        System.out.println("total: " + x.total());
    }

    public static void main(String[] args) {
        printTotal(new Invoice());
    }
}
```

```bash
docker run --rm --name lab-lang-java -v "$PWD":/w -w /w eclipse-temurin:25-jdk-alpine java Main.java
```

والمخرج سوف يكون كما يلي:

```
Main.java:15: error: method printTotal in class Main cannot be applied to given types;
        printTotal(new Invoice());
        ^
  required: HasTotal
  found:    Invoice
  reason: argument mismatch; Invoice cannot be converted to HasTotal
1 error
error: compilation failed
```

رفضت Java الكلاس رغم أن شكله مطابق تماماً، والحل الوحيد أن تكتب `class Invoice implements HasTotal`. والآن نفس الشيء بلغة Go في الملف `main.go`:

```go
package main

import "fmt"

type HasTotal interface {
	Total() int
}

type Invoice struct{}

func (Invoice) Total() int { return 54 }

func printTotal(x HasTotal) {
	fmt.Println("total:", x.Total())
}

func main() {
	printTotal(Invoice{})
}
```

```bash
docker run --rm --name lab-lang-go -v "$PWD":/w -w /w golang:1.27.1-alpine3.24 go run main.go
```

والمخرج سوف يكون كما يلي:

```
total: 54
```

قبلت Go النوع `Invoice` دون أن يذكر اسم `HasTotal` في أي مكان، لأن [الأسئلة الشائعة للغة Go](https://go.dev/doc/faq?ref=arabroot.io) تقول إن النوع يطبق الواجهة بمجرد أن يطبق دوالها، ولا توجد كلمة `implements` في اللغة أصلاً. وTypeScript بنيوية أيضاً كما يقول [توثيق التوافق بين الأنواع](https://www.typescriptlang.org/docs/handbook/type-compatibility.html?ref=arabroot.io)، وهذا الملف `structural.ts`:

```typescript
interface HasTotal {
  total(): number;
}

class Invoice {
  total() { return 54; }
}

function printTotal(x: HasTotal) {
  console.log("total:", x.total());
}

printTotal(new Invoice());
printTotal({ total: () => 42 });
printTotal({ sum: () => 42 });
```

```bash
docker run --rm --name lab-lang-ts -v "$PWD":/w -w /w node:22-alpine sh -c \
  'npm i -g typescript@7.0.2 >/dev/null 2>&1; tsc --strict --target es2022 structural.ts; node structural.js'
```

والمخرج سوف يكون كما يلي:

```
structural.ts(15,14): error TS2353: Object literal may only specify known properties, and 'sum' does not exist in type 'HasTotal'.
total: 54
total: 42
/w/structural.js:6
    console.log("total:", x.total());
                            ^

TypeError: x.total is not a function
```

لاحظ أن الكلاس `Invoice` والكائن الحرفي `{ total: () => 42 }` قبلا لأن شكلهما مطابق، أما الكائن الذي فيه `sum` بدلاً من `total` فرفضه `tsc`، ولأن الملف أنشئ رغم الخطأ كما رأينا من قبل فقد وصل الخطأ نفسه إلى وقت التشغيل. والصورة التالية تلخص الفرق:

![لوحتان: في Nominal بلغة Java تقبل الدالة printTotal الكلاس Invoice فقط إذا كتب implements HasTotal، والكلاس بدونها يرفض بخطأ Invoice cannot be converted to HasTotal، وفي Structural بلغة Go وTypeScript تقبل الدالة الكلاس Invoice والكائن الذي فيه total لأن شكلهما مطابق، وترفض الكائن الذي فيه sum بخطأ TS2353](https://arabroot.io/content/images/2026/10/paradigms-types-06-nominal-structural.webp)

النظام الاسمي يسأل: هل أعلنت أنك HasTotal؟ والبنيوي يسأل: هل عندك total()؟

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

### الحماية من Null أو Null Safety: الخطأ الذي سماه صاحبه خطأ المليار دولار

أشهر خطأ في تاريخ البرمجة هو أن تستخدم متغيراً قيمته `null` أو `undefined` وأنت تظن أن فيه قيمة. وهذا مثال بلغة Java في الملف `Main.java`، يبحث عن كوبون خصم غير موجود:

```java
import java.util.Map;

public class Main {
    static Map<String, String> coupons = Map.of("WELCOME", "10%");

    static String findCoupon(String code) {
        return coupons.get(code);
    }

    public static void main(String[] args) {
        String coupon = findCoupon("SUMMER");
        System.out.println(coupon.length());
    }
}
```

```bash
docker run --rm --name lab-lang-java -v "$PWD":/w -w /w eclipse-temurin:25-jdk-alpine \
  sh -c 'javac -g Main.java && java Main'
```

والمخرج سوف يكون كما يلي:

```
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "String.length()" because "coupon" is null
	at Main.main(Main.java:12)
```

ترجم البرنامج بدون أي تحذير لأن النوع `String` في Java يقبل `null`، وانفجر عند التشغيل. ولاحظ أن الرسالة تذكر اسم المتغير `coupon`، وهذه الرسائل المفصلة جاءت مع [JEP 358](https://openjdk.org/jeps/358?ref=arabroot.io)، ولكن اسم المتغير يظهر فقط إذا ترجمت مع الخيار `-g`، وبدونه سوف تقرأ `"<local1>" is null` كما ظهر لنا عند التشغيل المباشر ب `java Main.java`.

📌

في مؤتمر QCon London سنة 2009 قدم توني هور Tony Hoare محاضرة بعنوان [Null References: The Billion Dollar Mistake](https://www.infoq.com/presentations/Null-References-The-Billion-Dollar-Mistake-Tony-Hoare/?ref=arabroot.io)، قال فيها إنه أضاف مرجع Null إلى لغة ALGOL W سنة 1965 لأنه كان سهل التنفيذ، وسماه «خطأ المليار دولار». ومن هنا يأتي الاسم الذي تراه اليوم في توثيق [Kotlin عن Null Safety](https://kotlinlang.org/docs/null-safety.html?ref=arabroot.io).

والحل في اللغات الحديثة أن يصبح «قد لا توجد قيمة» جزءاً من النوع نفسه، فيمنعك المترجم من استخدام القيمة قبل أن تتعامل مع حالة غيابها. ففي TypeScript مع الخيار [strictNullChecks](https://www.typescriptlang.org/tsconfig/?ref=arabroot.io#strictNullChecks)، وهذا الملف `nulls.ts`:

```typescript
const coupons: Record<string, number> = { WELCOME: 10 };

function findCoupon(code: string): number | undefined {
  return coupons[code];
}

const pct = findCoupon("SUMMER");
console.log(pct.toFixed(1));
```

```bash
docker run --rm --name lab-lang-ts -v "$PWD":/w -w /w node:22-alpine sh -c \
  'npm i -g typescript@7.0.2 >/dev/null 2>&1; tsc --strict --target es2022 nulls.ts;
   tsc --strict --strictNullChecks false --target es2022 nulls.ts && node nulls.js'
```

والمخرج سوف يكون كما يلي:

```
nulls.ts(8,13): error TS18048: 'pct' is possibly 'undefined'.
/w/nulls.js:7
console.log(pct.toFixed(1));
                ^

TypeError: Cannot read properties of undefined (reading 'toFixed')
```

مع `--strict` رفض `tsc` الكود لأن النوع `number | undefined` يقول صراحة إن القيمة قد لا توجد، وعندما عطلنا `strictNullChecks` مر الفحص بنجاح وانفجر الكود عند التشغيل كما في Java تماماً. لذلك لا تقم بتعطيل `strict` في مشروع TypeScript جديد، والسبب أنك تعطل بذلك أهم فحص فيه.

أما Rust فلا يوجد فيها `null` أصلاً، والقيمة التي قد لا توجد نوعها [Option](https://doc.rust-lang.org/std/option/?ref=arabroot.io)، وهو إما `Some(value)` أو `None`. وهذا الملف `main.rs` يحاول استخدام النتيجة مباشرة:

```rust
use std::collections::HashMap;

fn find_coupon(coupons: &HashMap<&str, u32>, code: &str) -> Option<u32> {
    coupons.get(code).copied()
}

fn main() {
    let coupons = HashMap::from([("WELCOME", 10)]);
    let pct: u32 = find_coupon(&coupons, "SUMMER");
    println!("{pct}");
}
```

```bash
docker run --rm --name lab-lang-rs -v "$PWD":/w -w /w rust:1-alpine rustc main.rs
```

والمخرج سوف يكون كما يلي:

```
error[E0308]: mismatched types
 --> main.rs:9:20
  |
9 |     let pct: u32 = find_coupon(&coupons, "SUMMER");
  |              ---   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `u32`, found `Option<u32>`
  |              |
  |              expected due to this
  |
  = note: expected type `u32`
             found enum `Option<u32>`
help: consider using `Option::expect` to unwrap the `Option<u32>` value, panicking if the value is an `Option::None`
  |
9 |     let pct: u32 = find_coupon(&coupons, "SUMMER").expect("REASON");
  |                                                   +++++++++++++++++

error: aborting due to 1 previous error

For more information about this error, try `rustc --explain E0308`.
```

رفض المترجم أن تعامل `Option<u32>` كأنها `u32`، واقترح `expect` التي تنهي البرنامج إذا لم توجد قيمة، ولكن الطريقة الصحيحة أن تتعامل مع الحالتين ب `match`، كما في الملف `ok.rs`:

```rust
use std::collections::HashMap;

fn find_coupon(coupons: &HashMap<&str, u32>, code: &str) -> Option<u32> {
    coupons.get(code).copied()
}

fn main() {
    let coupons = HashMap::from([("WELCOME", 10)]);
    for code in ["WELCOME", "SUMMER"] {
        match find_coupon(&coupons, code) {
            Some(pct) => println!("{code}: {pct}% off"),
            None => println!("{code}: no such coupon"),
        }
    }
}
```

```bash
docker run --rm --name lab-lang-rs -v "$PWD":/w -w /w rust:1-alpine sh -c 'rustc ok.rs -o /tmp/ok && /tmp/ok'
```

والمخرج سوف يكون كما يلي:

```
WELCOME: 10% off
SUMMER: no such coupon
```

ونفس الفكرة في Kotlin، فالنوع `String` لا يقبل `null`، والنوع `String?` يقبله ويجبرك المترجم على فحصه أو استخدام `?.` قبل الوصول إليه، كما في [توثيق Kotlin](https://kotlinlang.org/docs/null-safety.html?ref=arabroot.io)، وفي Swift بنفس الطريقة مع ال Optionals.

### الأنواع العامة Generics: دالة واحدة لأكثر من نوع

آخر مفهوم في هذا القسم، لنفرض أنك تريد دالة `Sum` تجمع قائمة أرقام صحيحة، وأخرى تجمع أرقاماً عشرية، فبدون الأنواع العامة Generics سوف تكتب الدالة مرتين، أو تكتب دالة تقبل أي شيء فتخسر الفحص الثابت. والأنواع العامة تسمح لك بكتابة الدالة مرة واحدة مع معامل نوع Type Parameter، كما في هذا الملف بلغة Go:

```go
package main

import "fmt"

func Sum[T int | float64](xs []T) T {
	var s T
	for _, x := range xs {
		s += x
	}
	return s
}

func main() {
	fmt.Println(Sum([]int{24, 18, 12}))
	fmt.Println(Sum([]float64{12.5, 18.25}))
	fmt.Println(Sum([]string{"a", "b"}))
}
```

بدون السطر الأخير يعمل البرنامج ويكون المخرج كما يلي:

```
54
30.75
```

ومع السطر الأخير الذي يمرر قائمة نصوص يرفض المترجم البرنامج:

```
# command-line-arguments
./main.go:16:17: string does not satisfy int | float64 (string missing in int | float64)
```

لاحظ أن القيد `int | float64` يحدد الأنواع المسموحة، فالدالة عامة ولكنها ليست مفتوحة لأي شيء، والمترجم يفحص كل استدعاء بنوعه. والأنواع العامة موجودة في Java وC# وRust وTypeScript وغيرها، ويشرحها [دليل Go](https://go.dev/doc/tutorial/generics?ref=arabroot.io) و[توثيق TypeScript](https://www.typescriptlang.org/docs/handbook/2/generics.html?ref=arabroot.io).

## مقارنة أشهر اللغات

الجدول التالي يجمع ما سبق لأشهر اللغات، وكل خلية فيه من التوثيق الرسمي للغة، وعمود «قوية أو ضعيفة» تصنيف تقريبي حسب السلوك كما شرحنا، وطريقة التنفيذ هي للتطبيق الرسمي أو الأشهر لكل لغة:

| اللغة      | الأنماط Paradigms                         | الأنواع Types                                                                                                                         | التنفيذ Execution                                                                                                               | إدارة الذاكرة Memory Management                                                                                                              | الاستخدام الشائع                              |
| ---------- | ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------- |
| C          | إجرائية                                   | ثابتة، ضعيفة                                                                                                                          | ترجمة مسبقة AOT                                                                                                                 | يدوية [malloc وfree](https://en.cppreference.com/w/c/memory.html?ref=arabroot.io)                                                            | أنظمة التشغيل والأنظمة المدمجة Embedded       |
| C++        | متعددة: إجرائية وكائنية وGeneric          | ثابتة، ضعيفة نسبياً بسبب تحويلات C                                                                                                    | AOT                                                                                                                             | يدوية مع [RAII](https://en.cppreference.com/w/cpp/language/raii.html?ref=arabroot.io)                                                        | الألعاب والمتصفحات وقواعد البيانات            |
| Java       | كائنية مع عناصر وظيفية                    | ثابتة، قوية كما في [JLS](https://docs.oracle.com/javase/specs/jls/se25/html/jls-4.html?ref=arabroot.io)                               | Bytecode على JVM مع JIT                                                                                                         | [GC](https://docs.oracle.com/en/java/javase/25/gctuning/introduction-garbage-collection-tuning.html?ref=arabroot.io)                         | أنظمة المؤسسات وتطبيقات Android               |
| C#         | كائنية مع عناصر وظيفية                    | ثابتة، [قوية](https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/types/?ref=arabroot.io)                                    | [CIL على CLR مع JIT](https://learn.microsoft.com/en-us/dotnet/standard/managed-execution-process?ref=arabroot.io)               | [GC](https://learn.microsoft.com/en-us/dotnet/standard/garbage-collection/fundamentals?ref=arabroot.io)                                      | تطبيقات .NET والمؤسسات وألعاب Unity           |
| Kotlin     | كائنية ووظيفية                            | [ثابتة](https://kotlinlang.org/docs/faq.html?ref=arabroot.io)، قوية، مع Null Safety                                                   | Bytecode على JVM، وأيضاً JavaScript وNative                                                                                     | GC على JVM                                                                                                                                   | تطبيقات Android والخوادم                      |
| Go         | إجرائية مع واجهات وبدون وراثة             | ثابتة، قوية، واجهات بنيوية                                                                                                            | AOT                                                                                                                             | [GC](https://go.dev/doc/gc-guide?ref=arabroot.io)                                                                                            | الخوادم وأدوات السحابة وسطر الأوامر           |
| Rust       | متعددة: إجرائية ووظيفية مع Traits         | ثابتة، قوية، Option بدلاً من null                                                                                                     | AOT                                                                                                                             | [الملكية Ownership](https://doc.rust-lang.org/book/ch04-01-what-is-ownership.html?ref=arabroot.io) بدون GC                                   | برمجة الأنظمة وأدوات سطر الأوامر وWebAssembly |
| Swift      | متعددة: كائنية وProtocols ووظيفية         | ثابتة، قوية، Optionals                                                                                                                | AOT                                                                                                                             | [ARC عد المراجع](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/automaticreferencecounting/?ref=arabroot.io) | تطبيقات أجهزة Apple                           |
| Python     | متعددة                                    | ديناميكية، قوية، مع تلميحات اختيارية                                                                                                  | Bytecode مع مفسر CPython                                                                                                        | [عد المراجع مع جامع للدورات](https://docs.python.org/3/reference/datamodel.html?ref=arabroot.io)                                             | تحليل البيانات والأتمتة والويب                |
| JavaScript | متعددة: Prototypes ووظيفية وأحداث         | ديناميكية، [ضعيفة](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Data%5Fstructures?ref=arabroot.io)                   | محركات JIT مثل V8                                                                                                               | GC                                                                                                                                           | الويب في المتصفح والخوادم مع Node.js          |
| TypeScript | مثل JavaScript                            | ثابتة تدريجية بنيوية، تمحى عند الترجمة                                                                                                | تترجم إلى JavaScript                                                                                                            | مثل JavaScript                                                                                                                               | تطبيقات الويب الكبيرة                         |
| PHP        | إجرائية وكائنية                           | ديناميكية، [ضعيفة افتراضياً](https://www.php.net/manual/en/language.types.type-juggling.php?ref=arabroot.io) مع strict\_types اختياري | Bytecode مع OPcache، و[JIT](https://www.php.net/manual/en/opcache.configuration.php?ref=arabroot.io) معطل افتراضياً منذ PHP 8.4 | [عد المراجع مع جامع للدورات](https://www.php.net/manual/en/features.gc.php?ref=arabroot.io)                                                  | مواقع وتطبيقات الويب                          |
| Ruby       | كائنية وكل شيء فيها كائن، مع عناصر وظيفية | ديناميكية، قوية                                                                                                                       | Bytecode مع [YJIT](https://docs.ruby-lang.org/en/master/jit/yjit%5Fmd.html?ref=arabroot.io) اختياري                             | [GC](https://www.ruby-lang.org/en/about/?ref=arabroot.io)                                                                                    | تطبيقات الويب مع Rails                        |
| Haskell    | وظيفية نقية                               | ثابتة، قوية، استنتاج للأنواع                                                                                                          | AOT عبر [GHC](https://www.haskell.org/ghc/?ref=arabroot.io)                                                                     | GC                                                                                                                                           | المترجمات والبحث والتعليم                     |
| SQL        | تصريحية                                   | أعمدة لها أنواع يفحصها محرك قاعدة البيانات                                                                                            | المخطط يختار خطة التنفيذ                                                                                                        | يديرها محرك قاعدة البيانات                                                                                                                   | الاستعلام عن البيانات                         |

ولاحظ في الجدول أن عمود إدارة الذاكرة له ثلاث طرق رئيسية: يدوية كما في C حيث تحجز الذاكرة وتحررها بنفسك فتأتي معها أخطاء مثل استخدام ذاكرة بعد تحريرها، وجامع النفايات Garbage Collector كما في Java وGo وPython حيث يبحث برنامج داخل التطبيق عن الذاكرة التي لم يعد يستخدمها أحد، والملكية Ownership في Rust حيث يفحص المترجم قبل التشغيل من يملك كل قيمة ومتى تحرر، فتحصل على الأمان بدون جامع نفايات، وثمنه قواعد يتعلمها المبتدئ ببطء.

## ماذا يعني هذا عند اختيار لغة لمشروعك؟

بعد هذه الجولة لا توجد لغة «أفضل» بشكل مطلق، وإنما لغة أنسب لمشكلة وفريق معين. وهذه أسئلة عملية تساعدك:

- **حجم الكود وعمره وعدد المطورين**: سكربت لمرة واحدة يناسبه Python أو Bash، أما مشروع يعيش سنوات ويعمل عليه عدة مطورين فقيمة الأنواع الثابتة Static أو التدريجية Gradual فيه كبيرة، لأن المترجم يمسك ما ينساه المطورون عند تعديل كود لم يكتبوه.
- **البيئة التي يعمل فيها**: المتصفح يعني JavaScript أو TypeScript، وقاعدة البيانات تعني SQL، وتطبيق iPhone يعني Swift غالباً.
- **كلفة الخطأ**: برنامج يتعامل مع المال أو الذاكرة مباشرة يستحق لغة ترفض أكثر قبل التشغيل، والسبب أن الخطأ الذي يصل إلى المستخدم هناك مكلف جداً.
- **المشروع القائم**: إذا كان مشروعك بلغة ديناميكية فلا تقم بإعادة كتابته، وإنما أضف TypeScript أو mypy تدريجياً واجعل الفحص جزءاً من خط CI، وابدأ بالملفات التي تتغير كثيراً.

وتذكر أن تعلم لغة من نمط مختلف عن لغتك الأساسية يغير طريقة كتابتك حتى في لغتك، فالمطور الذي جرب Haskell أو SQL جيداً يكتب حلقات أقل ودوالاً نقية أكثر في Java وPython.

## تدريب عملي

1. أضف إلى مثال Java كلاساً ثالثاً للخصم اسمه `FixedOff` يطرح مبلغاً ثابتاً، ثم شغل البرنامج ولاحظ أن الكلاس `Order` لم يتغير فيه أي سطر.
2. غير في الملف `app.py` الدالة `legacy` بإضافة تلميحات للمعامل وقيمة الإرجاع، ثم شغل `mypy app.py` بدون `--check-untyped-defs` ولاحظ ظهور الخطأ.
3. أضف `"noEmitOnError": true` إلى ملف `tsconfig.json` في مشروع TypeScript، ثم تأكد أن `order.js` لا ينشأ عند وجود الخطأ.

وبعد التدريب تأكد أنه لم يبق أي Container من المختبر:

```bash
docker ps -a --filter name=lab-lang
```

## الخلاصة

وصلنا لنهاية الموضوع، وأهم ما فيه:

- توجد لغات كثيرة لأن كل لغة تختار نقطة مختلفة بين السرعة وسهولة الكتابة والأمان والبيئة التي تعمل فيها.
- الأمرية Imperative تصف الخطوات وتغير الحالة، ومنها الإجرائية Procedural والكائنية OOP، والتصريحية Declarative تصف النتيجة، ومنها الوظيفية Functional وSQL وTerraform والمنطقية Logic، وأغلب اللغات الحديثة تجمع أكثر من نمط.
- الأنواع الثابتة Static تمسك الخطأ قبل التشغيل حتى في الأسطر التي لا تنفذ، والديناميكية Dynamic تمسكه فقط عندما ينفذ السطر، وقد يكون ذلك عند المستخدم.
- القوة Strong والضعف Weak سؤال آخر: هل تحول اللغة الأنواع بصمت؟ وPython ديناميكية قوية Dynamic Strong، وC ثابتة ضعيفة Static Weak، وJavaScript ديناميكية ضعيفة Dynamic Weak.
- استنتاج الأنواع Type Inference يعطيك الفحص الثابت دون كتابة الأنواع في كل سطر، والأنواع التدريجية Gradual Typing مع TypeScript وmypy تضيف الفحص إلى مشروع قائم، والأنواع فيها تمحى أو تتجاهل وقت التشغيل.
- Java تقبل النوع باسمه المعلن Nominal، وGo وTypeScript تقبلانه بشكله Structural، وKotlin وRust وTypeScript مع strict تجبرك على التعامل مع غياب القيمة قبل استخدامها.

[![](https://arabroot.io/content/images/2026/10/hello-world-cover-cover.webp)دليلماذا يحدث عندما تشغل برنامج Hello World؟نتتبع برنامج C صغيراً من المحرر إلى الشاشة، فترى كل طبقة يمر بها في الجهاز حتى تظهر الجملة أمامك.](https://arabroot.io/articles/%D8%B1%D8%AD%D9%84%D8%A9-hello-world-%D9%85%D9%86-%D9%84%D9%88%D8%AD%D8%A9-%D8%A7%D9%84%D9%85%D9%81%D8%A7%D8%AA%D9%8A%D8%AD-%D8%A7%D9%84%D9%89-%D8%A7%D9%84%D8%B4%D8%A7%D8%B4%D8%A9/)[![](https://arabroot.io/content/images/2026/10/language-specs-cover-cover.webp)دليلاللغة شيء والمترجم شيء آخر: من يقرر مستقبل لغات البرمجة؟لماذا لا تصح جمل مثل «Python بطيئة»، وما الفرق بين مواصفة اللغة والبرامج التي تطبقها، ومن يقرر مستقبلها.](https://arabroot.io/articles/%D9%85%D9%88%D8%A7%D8%B5%D9%81%D8%A7%D8%AA-%D9%84%D8%BA%D8%A7%D8%AA-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D9%88%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA%D9%87%D8%A7-%D9%88%D8%AD%D9%88%D9%83%D9%85%D8%AA%D9%87%D8%A7/)[![](https://arabroot.io/content/images/2026/10/language-creators-cover-cover.webp)دليلقصص لغات البرمجة ومن صنعها، من Fortran إلى Rustمن صنع اللغات التي نكتب بها كل يوم، وما المشكلة التي دفعته إلى ذلك، وكيف تشرح قصة كل لغة قراراتها.](https://arabroot.io/articles/%D9%82%D8%B5%D8%B5-%D9%84%D8%BA%D8%A7%D8%AA-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D9%88%D9%85%D9%86-%D8%B5%D9%86%D8%B9%D9%87%D8%A7/)[![](https://arabroot.io/content/images/2026/10/cpu-concurrency-cover-cover.webp)دليلالتزامن Concurrency والخيوط: ماذا يعني لسيرفرك وتطبيقاتكلماذا يكون المعالج عند 100% والتطبيق بطيئاً، ولا تسرع الخيوط كود Python، وكيف تضبط عدد العمال وحدود ال Containers.](https://arabroot.io/articles/%D8%A7%D9%84%D9%85%D8%B9%D8%A7%D9%84%D8%AC-%D9%88%D8%A7%D9%84%D8%AE%D9%8A%D9%88%D8%B7-%D9%88%D8%A7%D9%84%D8%AA%D8%B2%D8%A7%D9%85%D9%86-%D9%84%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6%D9%8A%D9%86/)[![](https://arabroot.io/content/images/2026/10/git-basics-cover-cover.webp)دليلإدارة النسخ وGit للمبتدئين: لماذا نحتاجها وكيف تعمللماذا تضيع الملفات بأسماء final\_v2 ويمسح الزملاء عمل بعضهم، وكيف يحل Git ذلك على مشروع حقيقي من أول commit.](https://arabroot.io/articles/%D8%A7%D8%AF%D8%A7%D8%B1%D8%A9-%D8%A7%D9%84%D9%86%D8%B3%D8%AE-%D9%88-git-%D9%84%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6%D9%8A%D9%86/)

## سجل التحديثات

- أكتوبر 2026: إضافة المصطلح الإنجليزي بجانب المصطلحات العربية في العناوين والجدول والخلاصة.
- أكتوبر 2026: كتابة المقال، واختبار كل الأمثلة على Docker مع Go 1.27.1، وOpenJDK 25.0.4.1، وNode.js 22.23.3 مع TypeScript 7.0.2، وPython 3.13.16 مع mypy 2.4.0، وrustc 1.99.0، وgcc 15.2.0، وGHC 9.14.1، وSWI-Prolog 10.1.16، وPostgreSQL 17.11، وTerraform v1.16.5.