آشنایی فنی با زبان اسکالا

زبان اسکالا خلاصهگو است. تا جایی که ممکن باشد نیازی نیست مسائل قابل تشخیص را در این زبان بیان کرد. برای مثال هنگام معرفی یک متغیر نیازی نیست نوع آن نیز تعریف شود و کامپایلر سعی میکند از چیزی که به آن منتصب شده، نوع آن را تشخیص دهد.
برای مثال کد زیر در جاوا
final ArrayList names = new ArrayList(); names.add("name1"); names.add("name2"); names.add("name3");
و یا کد زیر در سیشارپ
var names = new List { "name1", "name2", "name3" }
به این شکل در اسکالا نوشته میشود
val names = List("name1", "name2", "name3")
در این مثال باید به این نکته توجه کرد که حتی در سیشارپ که خلاصهگوتر از جاواست باید حداقل مشخص کرد که لیستی حاوی نوع دادهای رشته (String) ایجاد میشود (اشاره به انواع Generic) در حالی که کامپایلر اسکالا این مورد را نیز تشخیص میدهد و نوعی که برای شناسه names مشخص میکند List [String] (لیستی از رشته) است.
مثلا اگر لیستی از اعداد صحیح مد نظر بود، کد بالا در اسکالا به این شکل نوشته میشد
val numbers = List(1, 2, 3)
نکته بسیار مهم این است که اسکالا بررسی زمان کامپایل نوع دادهها را فدای مختصر بودن نمیکند و بدون از دست دادن Type-safety حس کار با یک زبان پویا مثل روبی را به برنامهنویس میدهد. یکی از معدود مواردی که در آن اعلان نوع داده در اسکالا اجباری است، پارامترهای متد است.
برای مثال یک متد که یک عدد صحیح را از ورودی دریافت کرده و آن را چاپ میکند به شکل زیر نوشته میشود
def printnum(num: Int) = println(num.toString)
همان طور که ملاحضه میشود تعریف یک متد نیز به سادگی و با کمترین میزان کد نویسی امکانپذیر است.
تعریف کلاس
قطعه کد زیر در جاوا یک کلاس به همراه یک سازنده و دو عضو را تعریف میکند
class MyClass { private final String name; private final int age; public MyClass(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public String getAge() { return age; } }
قطعه کد زیر در اسکالا دقیقا معادل کد جاوا است
class MyClass(val name: String, val age: Int)
برنامهنویسی تابعی (Functional)
برنامهنویسی تابعی (Functional Programming) توسط بیشتر زبانهای جدید پشتیبانی میشوند و پشتیبانی در زبان اسکالا کاملتر از زبانهایی مثل جاوا ۸ و سیشارپ است.
برای مثال با قطعه کد زیر میتوان عملگر using سیشارپ را در اسکالا شبیهسازی کرد
trait Disposable { def dispose() } def using[A <: Disposable, B](disposable: A)(block: A => B) = { try { block(disposable) } finally { disposable.dispose() } }
و سپس به شکل زیر استفاده نمود
using (new MyDisposableClass()) { d => // ... }
و البته بسیاری از متدهای پرکاربرد بر روی انواع دادههای کلکسیونی (Collections) در اسکالا وجود دارند که یک نمونه از آنها به شکل زیر است
val list = 1 to 100 list.filter(i => i % 2 == 0).map(i => println(i)) // The above code can be written like the following one list.filter(_ % 2 == 0).map(println(_)) // And can be like this (using infix notation) list filter (_ % 2 == 0) map println
در خط اول یک لیست از اعداد ۱ تا ۱۰۰ ساخته میشود و سپس در خط دوم از بین آنها فقط اعداد زوج در خروجی چاپ میشوند. البته برای خلاصهگویی بیشتر کد خط دوم میتواند به شکل خط چهارم نوشته شود. و البته با استفاده از Infix notation میتواند به شکل خط ششم نوشته شود.
انواع دادهای قوی
از آن جایی که زبان اسکالا یک زبان با بررسی نوع دادهای زمان کامپایل است (Type-safe)، وجود سیستم انواع دادهای قوی و منعطف از الزامات آن است که خوشبختانه کتابخانه استاندارد اسکالا در این زمینه بسیار قوی عمل کرده است.
برای مثال نوع دادهای Option قابل بررسی است. این نوع داده تقریبا معادل نوع Nullable در سیشارپ است اما با ویژگیها و امکانات بیشتر.
این نوع دارای دو فرزند به نامهای Some و None است. همان طور که از نامها پیداست، زمانی که وجود یا عدم وجود یک داده امکانپذیر باشد از این نوع داده استفاده میشود. برای مثال وقتی یک متد قرار است یک کاربر را بر اساس شناسه آن جستجو کند، اگر کاربری یافت نشود به جای برگرداندن مقدار Null یا ایجاد Exception، میتوان مقدار None را برگرداند. در برنامهنویسی همروند ایجاد Exception مشکلات زیادی ایجاد میکند و استفاده از Null هم به نحو دیگری مشکلزا و غلط است.
چنین متدی به شکل زیر تعریف میشود
def getUserById(id: Int): Option[User] = { if (/* There was a user with the specified ID*/) { Some(foundUser) } else { None } }
بدون شک با استفاده از نوع دادهای Option احتمال روبرو شدن با خطای NullReferenceException به صفر میرسد. برای نمونه، نویسنده این مقاله بعد از حدود یکسال کار بر روی زبان اسکالا و تولید نرمافزارهای مختلف، حتی یک بار هم به خطای NullRefrence برخورد نکرده است.
تطبیق الگو (Pattern Matching)
نوع دادهای Option که در بخش قبل مطرح شد به راحتی در هر زبان دیگر نیز قابل پیادهسازی است. اما یکی از دلایلی که باعث میشود یک برنامهنویس اسکالا هر روز از امثال این نوع داده در کدهایش استفاده کند امکان Pattern Matching است.
فرض کنیم نوع Option در سیشارپ وجود داشت. میخواهیم برنامهای بنویسیم که ابتدا متد getUserById را صدا زده و در صورت وجود کاربر، اگر نام کاربر با حرف «a» شروع میشد، جمله «Starts with A» را در خروجی چاپ کند، در غیر این صورت جلمه «Doesn’t start with A» و در صورتی که کاربر وجود نداشت جمله «Nothing to say» را چاپ کند. برنامه در سیشارپ به شکل زیر خواهد بود
var maybeUser = getUserById(userId); var user = maybeUser as Some[User]; if (user != null) { if (user.name.startsWith("a")) { Console.WriteLine("Starts with A"); } else { Console.WriteLine("Doesn't start with A"); } } else { Console.WriteLine("Nothing to say"); }
اما همین قطعه کد در اسکالا با استفاده از Pattern Matching به شکل زیر خواهد بود
getUserById(userId) match { case Some(user) if user.name.startsWith("a") => println("Starts with A"); case Some(user) => println("Doesn't starts with A"); case None => println("Nothing to say"); }
این یک مثال بسیار ساده است. میتوان تصور کرد در شرایط پیچیدهتر وجود Pattern Matching چقدر مفید خواهد بود.
ماکروها (Macros)
ماکروها در اسکالا امکان تغییر ساختار کدهای برنامه قبل از کامپایل برنامه را میدهند.
برای مثال کد زیر در چارچوب پلی (Play)1 برای تبدیل یک رشته JSON به یک شی استفاده میشود
import play.api.libs.json._ import play.api.libs.functional.syntax._ case class Person(name: String, age: Int, lovesChocolate: Boolean) implicit val personReads = ( (__ \ 'name).read[String] and (__ \ 'age).read[Int] and (__ \ 'lovesChocolate).read[Boolean] )(Person)
هر چند این قطعه کد به نظر زیادهگو میآید ولی برای تبدیل JSON به یک شی نه از Reflection استفاده میشود و نه از هیچ یک از روشهای پویا. به این ترتیب این کد یکی از سریعترین کدها برای تبدیل JSON است بدون اینکه نیاز به راهاندازی و از این قبیل داشته باشد و همه اینها به دلیل Hard-code بودن قطعه کد اصلی یعنی پنج خط آخر است.
اما مهمترین ضعف آن، زیادهگویی است که برای برنامهنویسان امروزی قابل قبول نیست. یکی از راهحلهای جالب موجود در اسکالا استفاده از ماکرو است. چارچوب پلی ماکرویی برای ایجاد این قطعه کد در زمان کامپایل ایجاد کرده تا قطعه کد بالا به شکل زیر در آید
import play.api.libs.json._ import play.api.libs.functional.syntax._ case class Person(name: String, age: Int, lovesChocolate: Boolean) implicit val personReads = Json.reads[Person]
این قطعه کد نه از Reflection استفاده میکند و نه Type-safety را از بین میبرد و دقیقا همان قطعه کد بالا را تولید میکند ولی مختصر و مفید است!
جمعبندی
موارد یاد شده بخشی از ویژگیهای زبان اسکالا است ولی جزییات فراوانی برای بررسی وجود دارد که در این مقاله نمیگنجد. خوشبختانه منابع قابل قبولی برای یادگیری و بررسی زبان و اکو سیستم اسکالا وجود دارد.
امید است این مقاله هیجان و انرژی لازم برای دنبال کردن آنها را ایجاد کرده باشد.
منبع: نشریه «سلام دنیا»، شماره دوم – نوشته امیر کریمیدرباره فرشید نوتاش حقیقت
همیشه نیازمند یک منبع آموزشی فارسی در حوزه نرمافزارهای آزاد/ متنباز و سیستمعامل گنو/لینوکس بودم. از این رو این رسالت رو برای خودم تعریف کردم تا رسانه «محتوای باز» رو بوجود بیارم.
نوشتههای بیشتر از فرشید نوتاش حقیقتThis site uses Akismet to reduce spam. Learn how your comment data is processed.
دیدگاهتان را بنویسید