سكالا مقابل جافا: لماذا يجب أن أتعلم سكالا؟

نشرت: 2022-03-11

من المسلم به أن هناك بعض الحقيقة في العبارة القائلة بأن "سكالا صعب" ، لكن منحنى التعلم يستحق الاستثمار. بعض الميزات الأكثر تعقيدًا للغة (Tuples ، Functions ، Macros ، على سبيل المثال لا الحصر) تسهل على المطور كتابة كود أفضل وزيادة الأداء من خلال البرمجة في Scala. بصراحة ، نحن مبرمجون ، وإذا لم نكن أذكياء بما يكفي لتعلم لغة بها بعض التعقيد ، فنحن في العمل الخطأ.

Scala هي لغة JVM آمنة من النوع تدمج كلاً من البرمجة الموجهة للكائنات والوظيفية في لغة موجزة للغاية ومنطقية وقوية للغاية. قد يفاجأ البعض بمعرفة أن سكالا ليست جديدة تمامًا كما كانوا يعتقدون ، حيث تم تقديمها لأول مرة في عام 2003. ومع ذلك ، فقد بدأ سكالا بشكل خاص في خلال السنوات القليلة الماضية في تطوير أتباع مهم. الذي يطرح السؤال "لماذا سكالا؟".

تبحث هذه المقالة في مزايا Scala ، خاصةً مقابل Java (حيث تمت كتابة Scala للتشغيل في JVM). Scala ليست المحاولة الوحيدة لإنشاء "Java أفضل". سارت بدائل مثل Kotlin و Ceylon أيضًا في هذا المسار ، لكنهم اتخذوا القرار الأساسي بالبقاء قريبين جدًا في بناء الجملة من لغة Java نفسها ، وذلك لتقليل منحنى التعلم. قد تبدو هذه فكرة رائعة ، لكنها في النهاية هزيمة ذاتية إلى حد ما لأنها تجبرك على البقاء ضمن عدد من نماذج Java نفسها التي كانت سببًا في الرغبة في إنشاء "Java أفضل" في المقام الأول.

في المقابل ، تم إنشاء Scala خصيصًا بهدف أن تكون لغة أفضل ، والتخلص من تلك الجوانب من Java التي اعتبرتها مقيدة أو مملة للغاية أو محبطة للمطور. نتيجة لذلك ، هناك بالفعل اختلافات في الكود وتحولات في النموذج يمكن أن تجعل التعلم المبكر لبرمجة Scala أكثر صعوبة ، ولكن النتيجة هي لغة أنظف ومنظمة بشكل جيد يسهل استخدامها في النهاية وتزيد من الإنتاجية.

عند مقارنة Scala مقابل Java جنبًا إلى جنب ، تكون كفاءة Scala واضحة.

سكالا مقابل جافا: أيهما أكثر تعقيدًا حقًا؟

في حين أن بساطة لغة جافا كانت جزءًا من نجاحها ، فمن المفارقات أنها ساهمت أيضًا في تعقيدها. بالتأكيد ، يمكنك كتابة أي شيء تقريبًا في Java ، ولكن سطور التعليمات البرمجية المطلوبة للقيام بذلك قد تكون شاقة. البرمجة في Scala ، من ناحية أخرى ، لديها بنية أكثر تعقيدًا بعض الشيء. ولكن إذا كان بإمكانك كتابة سطر واحد أكثر تعقيدًا من التعليمات البرمجية بحيث يحل محل 20 سطراً "أبسط" من Java ، فأي سطر أكثر تعقيدًا حقًا؟

الحقيقة هي أن Java غالبًا ما تكون مطولة جدًا. في Scala ، يكون المترجم ذكيًا بشكل لا يصدق ، لذا فإن هذا يتجنب المطور الحاجة إلى تحديد تلك الأشياء التي يمكن للمترجم استنتاجها بشكل صريح. قارن ، على سبيل المثال ، هذا "Hello World!" البسيط برنامج Java مقابل Scala:

مرحبًا بالعالم بجافا:

 public class HelloJava { public static void main(String[] args) { System.out.println("Hello World!"); } }

مرحبًا بالعالم في سكالا:

 object HelloScala { def main(args: Array[String]): Unit = { println("Hello World!") } }

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

للحصول على مثال عملي أكثر ، دعنا نلقي نظرة على إنشاء قائمة بسيطة من السلاسل النصية:

جافا:

 List<String> list = new ArrayList<String>(); list.add("1"); list.add("2"); list.add("3");

سكالا:

 val list = List("1", "2", "3")

بالتأكيد هناك بعض الحيل في Java لتقصير الرمز قليلاً ، لكن ليس في الاستخدام القياسي.

الآن ضع في اعتبارك حالة لدينا فيها قائمة من السلاسل التي هي أرقام ، لكننا نريد تحويل تلك القائمة إلى قائمة من الأعداد الصحيحة:

جافا:

 List<Integer> ints = new ArrayList<Integer>(); for (String s : list) { ints.add(Integer.parseInt(s)); }

سكالا:

 val ints = list.map(s => s.toInt)

بفضل الخصائص الوظيفية لـ Scala ، يصبح هذا التحويل بسيطًا للغاية.

مثال على فئة: Java مقابل Scala

لنأخذ الأمور خطوة إلى الأمام ونقارن بين إنشاء كائن Java القياسي / العادي القديم (POJO) في Java و Scala.

أولاً ، إصدار Java:

 public class User { private String name; private List<Order> orders; public User() { orders = new ArrayList<Order>(); } public String getName() { return name; } public void setName(String name) { this.name = name; } public List<Order> getOrders() { return orders; } public void setOrders(List<Order> orders) { this.orders = orders; } } public class Order { private int id; private List<Product> products; public Order() { products = new ArrayList<Product>(); } public int getId() { return id; } public void setId(int id) { this.id = id; } public List<Product> getProducts() { return products; } public void setProducts(List<Product> products) { this.products = products; } } public class Product { private int id; private String category; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getCategory() { return category; } public void setCategory(String category) { this.category = category; } }

تفو. كود لوتا.

الآن إصدار Scala:

 class User { var name: String = _ var orders: List[Order] = Nil } class Order { var id: Int = _ var products: List[Product] = Nil } class Product { var id: Int = _ var category: String = _ }

أي لغة قلنا كانت أكثر تعقيدًا ؟!

هل نحن عادلون؟

إذا كنت قد وصلت إلى هذا الحد ، وكنت مبرمج جافا ، فقد تفكر في هذه المرحلة أنني أقوم بإجراء مقارنة غير عادلة للكود. بعد كل شيء ، لا يوجد ما يمنعني من إنشاء متغيرات عامة في Java ثم التخلص من المحسّنات والمحددات.

ومع ذلك ، إذا كنت تفكر في التفكير في المنطق الكامن وراء الحاصلون والمحددون في Java ، فهو على وجه التحديد للتدقيق في المستقبل. أي ، إذا احتجت لاحقًا إلى إضافة بعض المنطق إلى الحصول على المتغيرات أو إعدادها ، فسيتعين عليك إعادة كتابة تلك المتغيرات العامة لاستخدام الأساليب بدلاً من ذلك (وهذا هو السبب في تشجيع استخدام المحصلات والمحددات للبدء بها في Java ). ومع ذلك ، في برمجة Scala هذا ليس هو الحال. بسبب تصميم اللغة ، يظل التجريد سليماً دون الحاجة إلى أدوات التعرف على اللغة. ضع في اعتبارك ، على سبيل المثال ، فئة User المعدلة هذه في Scala والتي NullPointerException إذا حاولت تعيين الاسم على null:

 class User { private var _name: String = _ var orders: List[Order] = Nil def name = _name def name_=(name: String) = { if (name == null) { throw new NullPointerException("User.name cannot be null!") } _name = name }

ولا يزال بإمكانك تعيين الاسم مثل هذا:

 user.name = "John Doe"

لاحظ أن هذا يزيل تمامًا الحاجة إلى التكوين المسبق لموصلي الطريقة.

علاوة على ذلك ، نظرًا لأن Scala يفضل الثبات ، يمكنني كتابة هذا في Scala بشكل أكثر إيجازًا مع فئات الحالة:

 case class User(name: String, orders: List[Order]) case class Order(id: Int, products: List[Product]) case class Product(id: Int, category: String)

مجنون كم أقل من التعليمات البرمجية التي يجب أن أكتبها.

أخذ المثال قليلا

الآن ضع في اعتبارك سيناريو مع الفئات المذكورة أعلاه حيث أريد إضافة طريقة صغيرة أنيقة في فئة User تُرجع قائمة بجميع Products التي طلبها User :

في عالم Java المطول:

 public List<Product> getProducts() { List<Product> products = new ArrayList<Product>(); for (Order order : orders) { products.addAll(order.getProducts()); } return products; }

لحسن الحظ ، تحتوي java.util.List على طريقة addAll ، أو أن getProducts() قد تكون أطول في Java.

من ناحية أخرى ، في سكالا ، كل ما نحتاجه هو:

 def products = orders.flatMap(o => o.products)

يمكنك أن ترى مدى صغر تطبيق لغة Scala. نعم ، قد يبدو الأمر أكثر تعقيدًا بالنسبة لمبتدئ Scala ، ولكن بمجرد فهمك الكامل للمفاهيم الكامنة وراءها ، ستبدو شفرة Scala أكثر بساطة من كود Java.

لنكن أكثر تعقيدًا هنا. ماذا لو أردنا الحصول على Products ضمن Category معينة فقط؟

في هذه الحالة ، لا يمكننا الاستفادة من طريقة addAll في java.util.List ، لذا تصبح الأمور أقبح في Java :

 public List<Product> getProductsByCategory(String category) { List<Product> products = new ArrayList<Product>(); for (Order order : orders) { for (Product product : order.getProducts()) { if (category.equals(product.getCategory())) { products.add(product); } } } return products; }

ومع ذلك ، في Scala ، تظل الشفرة واضحة إلى حد ما. نحن ببساطة نستخدم flatMap لدمج قوائم المنتجات من كل Order تم تسويته في قائمة واحدة ، ثم نقوم بالتصفية لتشمل فقط تلك التي تطابق الفئة:

 def productsByCategory(category: String) = orders.flatMap(o => o.products).filter(p => p.category == category)

ديناميكي مقابل ثابت

من المؤكد أنه لم يكن هناك نقص في اللغات الجديدة على مدى السنوات القليلة الماضية ، ولكن في حين أن جميع اللغات الأخرى التي ظهرت مؤخرًا تقريبًا ديناميكية ، فإن Scala مكتوبة بشكل ثابت.

بصفتي مطورًا محترفًا - على الرغم من معرفتي بالعديد من اللغات الديناميكية واستخدامها - فإنني أرى أن عمليات التحقق من وقت الترجمة مهمة للغاية لكتابة تعليمات برمجية صلبة. في لغة ديناميكية ، لا يمكنك أبدًا التأكد من أن التعليمات البرمجية الخاصة بك خالية من الأخطاء بشكل كافٍ وقوية حتى تقوم بتشغيلها بالفعل في مجموعة واسعة من السيناريوهات. يمكن أن يؤدي هذا إلى عيوب خطيرة محتملة في الكود لا تتحقق أبدًا حتى يتم إنتاج الكود.

يتم إحتوائه

نأمل أن تجمع هذه المقالة بين Java مقابل Scala بما يكفي لتعطيك إحساسًا أوليًا بقوة وقدرات Scala وتثير شهيتك لتعلم اللغة. إنها ليست فقط لغة رائعة يمكن أن تجعل البرمجة أقل إرهاقًا وأكثر إمتاعًا ، ولكنها تستخدم أيضًا من قبل بعض أكبر الشركات في العالم (LinkedIn و Twitter و FourSquare و The Guardian ، على سبيل المثال لا الحصر).

تزداد شعبية واستخدام Scala بسرعة ، كما يتضح من العدد المتزايد باستمرار من المراكز المفتوحة لمطوري Scala. إذا لم تكن قد قمت بذلك بالفعل ، فسيكون الآن هو الوقت المناسب لبدء ركوب الموجة والتوقف عن السؤال "لماذا تعلم سكالا؟"

ذات صلة: تقليل كود Boilerplate باستخدام Scala Macros و Quasiquotes