السمات (Traits) في Rust وفئات الأنواع (Typeclasses) في Haskell: معركة التجريدات

مدرب بايثون أونلاين للمبتدئين

تعلم Python بسهولة دون إرهاق نظري. حل مهام عملية مع التحقق التلقائي واكتب الكود مباشرة في المتصفح.

ابدأ الدورة

مقدمة: لماذا مقارنة Rust وHaskell؟

\n\n

إذا سبق لك أن ألقيت نظرة على Stack Overflow بحثًا عن إجابة لسؤال "What is the difference between traits in Rust and typeclasses in Haskell?"، فأنت تعلم أن الموضوع ساخن. آلاف المشاهدات وعشرات الأصوات - مجتمع المطورين يريد حقًا فهم كيف يختلف هاتان الآليتان.

\n\n

للوهلة الأولى، تبدو متشابهة كالأخوة: فكل من السمات (traits) في Rust وفئات الأنواع (typeclasses) في Haskell تسمح بتعريف سلوك مشترك لأنواع مختلفة. ولكن إذا تعمقت أكثر، ستكتشف أن الفلسفة والتنفيذ والتطبيق العملي لكل منهما - عوالم مختلفة.

\n\n

في هذه المقالة، سنحلل الاختلافات الرئيسية من خلال أمثلة حقيقية لكود بلغة Rust (مع بعض الأمثلة بلغة Haskell للمقارنة)، وفي النهاية ستحصل على فهم واضح: متى تستخدم السمات (traits)، وأين من الأفضل التفكير في نهج مشابه لفئات الأنواع (typeclass).

\n\n

نظرة عامة سريعة: ما هي السمات (Traits) في Rust؟

\n\n

السمات (Traits) في Rust هي طريقة للإعلان عن مجموعة من الدوال التي يجب تنفيذها لنوع معين. هذا هو أساس البرمجة العامة (generics) وتعدد الأشكال في Rust.

\n\n

مثال بسيط: تخيل أننا نريد أن تتمكن أنواع مختلفة من "إلقاء التحية".

\n\n
trait Greeter {\n    fn greet(&self) -> String;\n}\n\nstruct Person {\n    name: String,\n}\n\nimpl Greeter for Person {\n    fn greet(&self) -> String {\n        format!("Привет, меня зовут {}!", self.name)\n    }\n}\n\nstruct Robot {\n    model: String,\n}\n\nimpl Greeter for Robot {\n    fn greet(&self) -> String {\n        format!("Бип-буп, модель: {}", self.model)\n    }\n}\n\nfn say_hello(item: &impl Greeter) {\n    println!("{}", item.greet());\n}\n\nfn main() {\n    let person = Person { name: "Анна".into() };\n    let robot = Robot { model: "R2-D2".into() };\n    say_hello(&person);\n    say_hello(&robot);\n}
\n\n

هنا Greeter هو سمة (trait). النوعان Person و Robot يقومان بتنفيذها بطريقتهما الخاصة. الدالة say_hello تقبل أي نوع ينفذ Greeter. هذا هو تعدد الأشكال عبر السمات (traits).

\n\n

نظرة عامة سريعة: ما هي فئات الأنواع (Typeclasses) في Haskell؟

\n\n

فئة النوع (Typeclass) في Haskell هي واجهة تحدد مجموعة من الدوال التي يمكن تنفيذها لأنواع مختلفة. يبدو مألوفًا، أليس كذلك؟

\n\n

هذا هو الشكل الذي سيبدو عليه المثال المماثل بلغة Haskell:

\n\n
class Greeter a where\n    greet :: a -> String\n\ndata Person = Person { name :: String }\ndata Robot = Robot { model :: String }\n\ninstance Greeter Person where\n    greet (Person n) = "Привет, меня зовут " ++ n ++ "!"\n\ninstance Greeter Robot where\n    greet (Robot m) = "Бип-буп, модель: " ++ m\n\nsayHello :: Greeter a => a -> IO ()\nsayHello item = putStrLn (greet item)\n\nmain = do\n    sayHello (Person "Анна")\n    sayHello (Robot "R2-D2")
\n\n

بناء الجملة مختلف، لكن المنطق هو نفسه. ومع ذلك، فإن الاختلافات بين السمات (traits) في Rust وفئات الأنواع (typeclasses) في Haskell تبدأ من هنا.

\n\n

الاختلاف الرئيسي رقم 1: التحويل الأحادي الشكل (Monomorphization) مقابل التغليف (Box)

\n\n

الاختلاف الأكثر جوهرية بين السمات (traits) وفئات الأنواع (typeclasses) يكمن في كيفية معالجة المترجم للكود العام.

\n\n

Rust: التحويل الأحادي الشكل (Monomorphization)

\n\n

عندما تكتب fn say_hello(item: &impl Greeter)، يقوم مترجم Rust بإنشاء نسخة منفصلة من الدالة لكل نوع محدد. وهذا ما يسمى التحويل الأحادي الشكل (monomorphization). النتيجة: تجريد بتكلفة صفرية (zero-cost abstraction) - أنت لا تدفع ثمن ما لا تستخدمه. لكن حجم الكود قد يزداد.

\n\n

Haskell: التغليف (Boxing) (افتراضيًا)

\n\n

في Haskell، يتم تنفيذ فئات الأنواع (typeclasses) عادةً من خلال قواميس الدوال (vtable). يمرر المترجم معاملًا ضمنيًا - جدولًا بمؤشرات للدوال. وهذا ما يسمى التغليف (boxing). أنت تدفع ثمن

المدونات

توصيات الكتب