الیکسیر ۱.۲۰ منتشر شد: حالا زبانی با تایپ تدریجی
خلاصهٔ کاملتر
به نوشتهی José Valim، تلاش برای اضافه کردن سیستم تایپ به الیکسیر از سال ۲۰۲۲ شروع شد و حالا تو نسخهی ۱.۲۰ اولین مرحلهی توسعه کامل شده: استنتاج تایپ (type inference) و بررسی تدریجی تایپ روی هر برنامهی الیکسیر، بدون اینکه لازم باشه برنامهنویس هیچ تایپی رو دستی بنویسه. نتیجه اینه که الیکسیر کد مرده و باگهای تأییدشده رو لو میده، یعنی نقضهایی که اگه اجرا بشن قطعاً موقع runtime میترکن.
هدف نویسندهها یه سیستم تایپ بوده که سه تا ویژگی داشته باشه: sound (تایپهای استنتاجشده با رفتار واقعی برنامه بخونن)، gradual (تایپ dynamic() داشته باشه برای وقتی که تایپ موقع اجرا چک میشه) و developer friendly (همهچی با عملیات سادهی مجموعهای یعنی اجتماع و اشتراک و نقیض ساخته شده، با پیامهای خطای روشن).
قلب ماجرا تایپ dynamic()ـه که با any() تو زبونهای دیگه فرق اساسی داره. خیلی از سیستمهای تایپ تدریجی any() دارن که عملاً یعنی «هرچی شد قبوله» و هیچ خطایی گزارش نمیشه. ولی dynamic() تو الیکسیر دو تا خاصیت مهم داره: سازگاری (compatibility) و باریکسازی (narrowing). موقع صدا زدن یه تابع، الیکسیر فقط وقتی خطا میده که تایپ دادهشده و تایپ پذیرفتهشده کاملاً مجزا (disjoint) باشن. برای همینه که فقط باگهای واقعی گزارش میشن نه false positive.
مثلاً اگه یه متغیر تایپ dynamic(integer() or binary()) داشته باشه و به عملگر / که فقط عدد میخواد پاس داده بشه، چون این متغیر میتونه integer باشه و دو تا تایپ مجزا نیستن، خطایی صادر نمیشه. ولی اگه همون متغیر به Map.fetch! پاس داده بشه که map میخواد، چون با integer و binary کاملاً مجزاست، نقض تایپ گزارش میشه.
خاصیت دوم یعنی narrowing هم باعث میشه با اینکه فقط باگ قطعی گزارش میشه، بازم کلی باگ پیدا بشه. مثلاً تو تابع زیر:
def add_a_and_b(data) do
data.a + data.b
endمتغیر data که اول dynamic()ـه، چون با data.a و data.b و عملگر جمع استفاده شده، الیکسیر تایپش رو به %{..., a: number(), b: number()} باریک میکنه، یعنی یه map با فیلدهای عددی a و b. حالا اگه اشتباهی به جای data.b خود data رو بنویسی، الیکسیر میفهمه که داری یه map رو به عنوان عدد استفاده میکنی و نقض میده. به گفتهی نویسنده dynamic() عملاً مثل یه بازه کار میکنه که هرچی تو برنامه ازش استفاده بشه باریکتر میشه.
بخش زیادی از کار این نسخه روی اضافه کردن type-check و narrowing به سازههای مختلف زبون بوده: guardها (مثل is_integer و is_map_key و tuple_size)، الگوهای تطبیق، و case. تو case الیکسیر از clauseهای قبلی برای باریک کردن بعدیها استفاده میکنه، مثلاً اگه یه clause روی nil تطبیق داده شده باشه، تو clause بعدی میدونه دیگه مقدار nil نیست. همین کمک میکنه clauseهای زائد و کد مرده هم پیدا بشن. کلی تابع کتابخونهی استاندارد هم تایپدار شدن.
نویسنده میگه این نسخه زمان کامپایل رو هم بهتر کرده، مخصوصاً روی ماشینهای چندهستهای، و یه گزینهی کامپایلر جدید به اسم :module_definition اضافه شده. دربارهی آینده هم میگه سؤال بزرگ بعدی اینه که چه وقت type signatureهای واقعی با استفاده از تایپهای مجموعهای معرفی بشن، که این به حل شدن چند مسئلهی پژوهشی مثل تایپهای بازگشتی و پارامتری بستگی داره.
نکات کلیدی:
- Elixir 1.20 کل برنامه رو بدون نیاز به annotation تایپ بررسی میکنه و باگ قطعی و کد مرده رو پیدا میکنه
- تایپ dynamic() با any() فرق داره: فقط وقتی تایپها کاملاً مجزا باشن خطا میده، پس false positive خیلی کمه
- narrowing باعث میشه dynamic() مثل یه بازه عمل کنه و موقع استفاده باریکتر بشه
- type-check به guardها، الگوها و case اضافه شده و از clauseهای قبلی برای باریکسازی استفاده میشه
- زمان کامپایل بهتر شده و گزینهی :module_definition اضافه شده؛ type signatureهای واقعی هنوز در آیندهان




