تصور کنید در حال عیبیابی یک خطای بحرانی در محیط عملیاتی هستید و گزارش خطا فقط به شما میگوید مشکل در یک تابع ۵۰ خطی است، اما دقیقاً نمیگوید کدام خط. این کابوس برای توسعهدهندگان Gleam با انتشار نسخه ۱.۱۹.۰ به پایان رسید.
طبق اعلام تیم توسعه، زمان ساخت (Build Time) پروژههای Gleam که روی ماشین مجازی Erlang اجرا میشوند، بهطور محسوسی کاهش یافته است. این زبان حالا بهجای تولید کد منبع Erlang، از فرمهای انتزاعی (Abstract Forms) استفاده میکند؛ تغییری که باعث میشود کامپایلر بهطور کامل از مراحل ابتدایی کامپایلر Erlang عبور کند.
تغییر به سمت فرمهای انتزاعی
برای درک این موضوع، باید بدانید که Gleam تا پیش از این مانند یک ترنسپایلر (Transpiler) — شبیه به مترجمی که یک کتاب را از زبانی به زبان دیگر برمیگرداند تا خواننده بتواند آن را بفهمد — عمل میکرد و سینتکس ایمن از نظر نوع (Type-safe) خود را به کد Erlang تبدیل میکرد که برای انسان قابل خواندن بود. همانطور که در تحلیلهای قبلی ما دربارهی اکوسیستم زبانهای تابعی اشاره کردیم، این رویکرد با وجود پایداری، باعث ایجاد شکافی بین کد اصلی Gleam و بایتکد نهایی BEAM میشد. این فاصله اغلب منجر به ارائه شماره خطوط غیردقیق در گزارشهای کرش (Crash Reports) میشد، بهطوری که گزارشها فقط به نزدیکترین تابع اشاره میکردند، نه خط دقیق وقوع خطا.
به نقل از مستندات این نسخه، جاکومو کاوالیه (Giacomo Cavalieri) طی چند ماه گذشته، تولیدکننده کد Erlang را بهطور کامل بازنویسی کرده است. طراحی جدید، فرمهای انتزاعی تولید میکند که در واقع یک نمایش میانی (Intermediate Representation) مورد استفاده در کامپایلر Erlang است. این ساختار، درختی از متادیتا است که سینتکس Erlang را نمایندگی میکند و معمولاً توسط توکنایزر و پارسر خودِ Erlang تولید میشود.
از آنجایی که این فرمها دارای رمزگذاری باینری با استفاده از فرمت ترم خارجی Erlang هستند، Gleam میتواند کد تولید شده را مستقیماً بارگذاری کند. این یعنی کامپایلر دیگر نیازی به مراحل توکنسازی و تجزیه (Parsing) در کامپایلر Erlang ندارد و بهطور کامل از این مراحل میپرد.
نتیجه این تغییر، ارائه استکتریسها (Stack Traces) و شماره خطوط کاملاً دقیق در گزارشهای BEAM است. همچنین این مسیر برای پشتیبانی کامل از دیباگرهایی مانند edb هموار شده است، هرچند تیم توسعه هنوز این قابلیت را پیادهسازی نکرده است.
فراتر از عملکرد، این بازنویسی کیفیت کلی کد کامپایلر را بالا برده است. تولیدکننده قبلی یکی از قدیمیترین بخشهای کدبیس بود که دیگر با استانداردهای مدرن و کنوانسیونهای فعلی همخوانی نداشت. جایگزین جدید، سطح کیفی کل کامپایلر را ارتقا داده است. تیم Gleam با شوخی اشاره کرده است که با این تغییر، «دیگر هرگز مجبور نیستند بشنوند که کسی از واژه ترنسپایلر بهعنوان یک اصطلاح تحقیرآمیز استفاده کند.»
جهش در عملکرد
برای سنجش این جهش، تیم توسعه از پروژه langcompilebench استفاده کرد که توسط خوزه والیم (José Valim) ایجاد شده است. این محک، زمان کامپایل ۱۰۰ ماژول را اندازه میگیرد که هر کدام شامل ۱۰۰ تابع ساده هستند که یک رشته "hello world" را برمیگردانند. اگرچه این یک تست مصنوعی است و تنها از زیرمجموعه کوچکی از ویژگیهای زبان استفاده میکند، اما مقایسهای عملی و عادلانه (Like-for-like) بین زبانهای مختلف ارائه میدهد.
مقایسه نسخه ۱.۱۷.۰ با ۱.۱۹.۰ نشان میدهد که زمان ساخت کامل از صفر (Full Build) بهطور قابلتوجهی کاهش یافته است. با وجود اینکه Gleam برای توسعه روزمره از کامپایل افزایشی (Incremental Compilation) استفاده میکند، این تغییر در خط پایه ثابت میکند که تولیدکننده جدید اساساً کارآمدتر است.
در ادامه این بررسی، تیم توسعه محک را به زبانهای محبوب دیگر و هدف جاوااسکریپت Gleam گسترش داد. نتایج نشان میدهد Gleam در محیط Erlang بسیار رقابتی است و در جایگاهی بسیار جلوتر از Elixir، Rust و C# قرار دارد. هدف جاوااسکریپت این زبان همچنان سریعتر از هر دو مورد است.
چرا مستقیماً بایتکد BEAM تولید نمیشود؟
شاید بپرسید چرا Gleam مستقیماً بایتکد BEAM تولید نمیکند تا کاملاً از کامپایلر Erlang بینیاز شود؟ تیم توسعه استدلال میکند که این کار برای یک پروژه جامعهمحور (Community-funded) ناپایدار است. بایتکد BEAM یک استاندارد ثابت نیست و با هر نسخه از ماشین مجازی تکامل مییابد و قابلیتهایی به آن اضافه یا از آن حذف میشود.
هدف قرار دادن مستقیم بایتکد مستلزم موارد زیر است:
- تعهد به بهروزرسانی دائمی و همگام با تکامل VM.
- همکاری بسیار نزدیک با نگهداران Erlang برای آماده شدن در برابر تغییرات آتی.
- بازسازی دههها بهینهسازی که پیشتر در کامپایلر Erlang پیاده شده است، حتی با کمک تحلیل استاتیک Gleam.
پروژه Gleam توسط حامیانی اداره میشود که ماهانه مبالغ کمی (بین ۵ تا ۲۰ دلار) پرداخت میکنند و این پروژه تنها منبع درآمد خالق آن است. Gleam متعلق به هیچ شرکت یا مؤسسه آکادمیک نیست، بنابراین باید از منابع خود بهطور بهینه استفاده کند. در نتیجه، هدف قرار دادن فرمهای انتزاعی یک «نقطه بهینه هزینه-فایده» است و همان مسیری است که زبان Elixir (برادر بزرگتر Gleam) طی کرده است.
بهینهسازیهای جاوااسکریپت و تایپاسکریپت
در کنار BEAM، نسخه ۱.۱۹.۰ بهینهسازیهای مهمی برای محیط جاوااسکریپت آورده است. جان داونی (John Downey) نحوه کامپایل تطبیق الگو (Pattern Matching) — شبیه به یک فیلتر هوشمند که ورودی را با چندین الگو میسنجد تا درستترین مسیر را پیدا کند — را بهبود بخشید. پیش از این، یک عبارت case ساده، مجموعهای از دستورات if تو در تو با متغیرهای میانی متعدد تولید میکرد.
بهعنوان مثال، تابعی که Wibble(1, 2) را بررسی میکرد، پیشتر بلوک بزرگی از کد JS با چندین بررسی if تو در تو و متغیرهای موقتی مانند $ و $1 تولید میکرد:
export function go(x) {
if (isWibble(x)) {
let $ = x[0];
if ($ === 1) {
let $1 = x[1];
if ($1 === 2) {
return 1;
} else {
return 2;
}
} else {
return 2;
}
} else {
return 2;
}
}
اکنون کامپایلر اینها را به دستورات تکشرطی تخت با استفاده از عملگر && تبدیل میکند:
export function go(x) {
if (isWibble(x) && x[0] === 1 && x[1] === 2) {
return 1;
} else {
return 2;
}
}
این تغییر تأثیر ناچیزی بر اندازه نهایی فایل (به دلیل gzip) دارد، اما شاخههای کمتری برای بهینهسازی در اختیار موتورهای جاوااسکریپت قرار میدهد.
مدیریت لیستهای تحتاللفظی (List Literals) نیز بهینه شده است. لیستهای تغییرناپذیر و پایدار Gleam با آرایههای متغیر و متوالی جاوااسکریپت متفاوتاند. پیش از این، تمام لیستها از طریق تابع arrayToList تبدیل میشدند، مثلاً: const numbers = arrayToList([1, 2, 3]).
اکنون کامپایلر بر اساس طول لیست تصمیم میگیرد:
- لیستهای کوتاه: بهصورت فراخوانیهای مستقیم
prependتولید میشوند (مثلاًconst numbers = prepend(1, prepend(2, prepend(3, empty)))). این موضوع برای پروژههایی که از کتابخانههایی مثل Lustre استفاده میکنند، باعث افزایش سرعت میشود. - لیستهای بلند: همچنان از روش
array-to-listاستفاده میکنند چون بهبود خاصی برای توالیهای طولانیتر ثبت نشد.
برای کاربران TypeScript، کامپایلر اکنون Overloadهای API برای بررسیهای نوع سفارشی ارائه میدهد. پیش از این، بررسی اینکه آیا یک مقدار یک واریانت خاص است (مثلاً Box$isFull) باعث میشد پارامتر نوع به unknown تعمیم یابد و اطلاعات نوع از دست برود. سیستم جدید تا حد امکان نوع خاص I را حفظ میکند:
export function Box$isFull<I>(value: Box$<I>): value is Full<I>;
export function Box$isFull(value: any): value is Full<unknown>;
اکوسیستم و ابزارها
برای ادغام بهتر با Mix (در Elixir) و rebar3 (در Erlang)، فایل اجرایی gleam اکنون بخشهای بیشتری از کارهای سخت را بر عهده میگیرد. از آنجایی که BEAM نیاز دارد تمام بستهها یک فایل منبع .app داشته باشند، gleam اکنون این فایلها را بهطور خودکار در حین کامپایل BEAM تولید میکند.
بهروزرسانیهای دیگر ابزاری شامل موارد زیر است:
compile-package: افزودن پرچم--no-devبرای نادیده گرفتنdev_dependenciesو بارگذاری کد فقط از دایرکتوریsrc.- دستورات Export: دستورات
export package-informationوexport package-interfaceاکنون میتوانند خروجی را بهجای فایل، در stdout چاپ کنند. - خروجیهای Prelude: دستورات
export javascript-preludeوexport typescript-preludeاکنون میتوانند مستقیماً در یک فایل بنویسند. - Language Server: آلیستر اسمیت (Alistair Smith) پشتیبانی کامل از برچسبها (Labels) را اضافه کرد که قابلیتهای go-to-definition، find-references و تغییر نام (renaming) را برای برچسبهای فیلد و آرگومان فعال میکند.
- فرمتبندی در مرورگر: یک بیلد WebAssembly از کامپایلر اکنون شامل تابع
format_sourceاست که اجازه میدهد فرمتکننده Gleam داخل محیط Playground مرورگر اجرا شود.
بهبود تجربه عیبیابی
پیامهای خطا برای کاهش استرس برنامهنویس بهطور جامع بازنویسی شدهاند. کامپایلر اکنون اشتباهات رایج خاصی را تشخیص میدهد تا راهنماییهای واضحتری ارائه کند:
- خطاهای سینتکسی: خطاهای ویژهای زمانی فعال میشوند که نشانگرهای تداخل ادغام گیت (git merge conflict markers) در کد پیدا شوند.
- بهروزرسانی رکوردها: اگر رکورد اصلی در جایگاه اشتباه قرار گیرد (مثلاً
User(score: 10, ..lucy)بهجایUser(..lucy, score: 10))، یک خطای اختصاصی ظاهر میشود. - عملگرهای نامعتبر: پیامهای راهنما برای عملگرهای رویهای که در Gleam وجود ندارند، مانند
+=و*=فعال شدهاند. - تطبیق الگو: یک خطای سفارشی برای زمانی که عملگر
|به روشی استفاده شود که در زبانهایی مثل Java معتبر است اما در Gleam نامعتبر است، اضافه شد. - عبارات ثابت: جاکومو کاوالیه خطاهایی برای عملگرهای باینری که در عبارات ثابت مجاز نیستند اضافه کرد و تحمل خطای کامپایلر را هنگام وقوع این اشتباهات بهبود بخشید. این موضوع حیاتی است زیرا کامپایلر باید حتی زمانی که کد در وضعیت نامعتبر است، اطلاعات ارائه دهد تا از Language Server پشتیبانی کند.
- حریم خصوصی: آندری کوژف (Andrey Kozhev) بافتی (Context) را برای زمانی که یک ماژول سعی میکند از یک نوع یا مقدار خصوصی از ماژول دیگر در همان بسته استفاده کند، اضافه کرد. این اطلاعات برای بستههای وابستگی (Dependency packages) حذف میشود تا جزئیات داخلی کد لو نرود.
در نهایت، جیمز دولان (James Dolan) بررسیکننده نوع (Type Checker) را بهبود بخشید تا تعریف نادرست یک Alias نوع باعث ایجاد زنجیرهای از خطاهای بیربط در تمام کاربردهای آن Alias نشود.
این نسخه جایگاه Gleam را بهعنوان یک زیربنای قابل اعتماد برای نرمافزارهای مقیاسپذیر تقویت میکند. با بهینهسازی پل ارتباطی بین دنیای ایمن از نظر نوع و محیطهای BEAM/JS، تیم توسعه پایداری بلندمدت را بر میانبرهای آزمایشی ترجیح داده است.
گام بعدی شما
- اگر از Gleam استفاده میکنید، فوراً به نسخه ۱.۱۹.۰ ارتقا دهید تا از سرعت بالاتر کامپایل و خطاهای دقیقتر بهرهمند شوید.
- در پروژههای جاوااسکریپت، خروجی کد را بررسی کنید تا تأثیر بهینهسازیهای تطبیق الگو را ببینید.
- از قابلیتهای جدید Language Server برای مدیریت بهتر پروژههای بزرگ با برچسبهای زیاد استفاده کنید.
اما داستان بهینهسازیهای زیرساختی به اینجا ختم نمیشود؛ بررسی اثر این تغییرات بر مصرف حافظه در محیطهای توزیعشده را در گزارش بعدی دنبال کنید.




گفتگو