زبان برنامه نویسی Goose
https://twitter.com/ID_AA_Carmack/status/2113704269274636568 آدرس نظرات: https://news.ycombinator.com/item?id=50224100 امتیاز: 216 # نظر: 152
یک زبان سیستمی ایمن برای حافظه بدون تخصیص دهنده پشته، جمعآوری زباله، یا حاشیه نویسی مادام العمر. در معیارهای این مخزن، Goose سریعتر از C++ و Rust اجرا می شود در حالی که از حافظه کمتری استفاده می کند.
آموزش · مرجع · مشخصات · بسته ها · نمونه ها · معیارها · کتابخانه استاندارد
Goose دارای نحوی است که برای برنامه نویسان C و Rust آشناست، اما هیپ وجود ندارد. هر مقدار پویا در پشته داده ای که توسط کامپایلر مدیریت می شود، زندگی می کند. رشد یک اشاره گر را به جلو می برد و با پایان یافتن دامنه آن، حافظه آزاد می شود. نوع زبان و قوانین طول عمر، استفاده از این مدل حافظه را ایمن می کند.
آموزش تمام این موارد را با مثال شرح میدهد، مرجع کل مبحث زبان را بر اساس موضوع پوشش میدهد، مشخصات دارای قوانین دقیق است، و معیارها دارای اعداد و زیانها هستند.
این بخش بر روی آنچه Goose را متمایز می کند تمرکز می کند. آموزش این ویژگی ها را با جزئیات بیشتری توضیح می دهد و نمونه ها آنها را در سی و یک برنامه کامل نشان می دهند.
یک برنامه دارای پشته تماس بومی، داده ایستا و N پشته داده است که در آن کامپایلر N را انجام می دهد. پشته داده یک رزرو فضای آدرس بزرگ با اشاره گر ضربه ای است و حافظه دیگری وجود ندارد. حداکثر یک مقدار قابل تغییر اندازه، زنده در هر پشته است و همیشه در بالا است، بنابراین رشد هرگز چیزی را جابجا نمیکند و هرگز ظرفیت را بررسی نمیکند. همه اینها در زمان کامپایل ثابت می شود. زمان اجرا فقط نشانگرهای دست انداز را دنبال می کند.
push یک مرجع به عنصر جدید برمی گرداند، بنابراین یک برنامه می تواند عناصر را در حین ساخت آرایه به هم پیوند دهد. یک بردار<T> یا Vec<T> ممکن است دوباره تخصیص یابد و نمی تواند این تضمین را ارائه دهد. منابع پایدار چندین دستاورد معیار را به همراه دارند.
هر مرجع و برشی یک ریشه ایستا دارد: متغیری که طول عمر هدف خود را محدود می کند. یک مرجع نباید بیش از متغیری که هدفش را دارد زنده بماند یا از طریق یک نوع ناسازگار به آن هدف دسترسی پیدا کند. کامپایلر ریشه ها را استنباط می کند و توابع را برای آنها تخصصی می کند، بنابراین نیازی به حاشیه نویسی نیست. هیچ قانون نامگذاری یا انحصاری وجود ندارد. هنگامی که یک بررسی مادام العمر با شکست مواجه می شود، خطا ریشه مرجع و مقصد آن را نام می برد:
یک قطعه T[:] به یک تابع اجازه میدهد تا محدودهای از هر نوع آرایه را بدون کپی کردن عناصر آن بپذیرد. به عنوان مثال، split برشها را به ورودی خود برمیگرداند، و یک فرهنگ لغت با کلید u8[:] به جای کپی، نماهای رشتهها را ذخیره میکند.
هیچ نوع آرایه واحدی وجود ندارد. خانوادهای وجود دارد که فقط در مورد اندازه متفاوت است، و هر عضو [فراداده][عناصر...]، درون خطی و بستهبندی شده است، هرگز نشانگر یک بلوک عنصر نیست:
رشتهها فقط آرایههای u8 هستند: u8[>..] یک سازنده، u8[] یک رشته تمامشده ذخیرهشده درون خطی در هر چیزی که آن را نگه میدارد، u8[..16] یک رشته کوچک در داخل یک ساختار، u8[:] یک نمای. یک ساختار ممکن است شامل قطعات با اندازه متغیر باشد، و آنها به ترتیب اعلان در خط قرار می گیرند، بنابراین یک رکورد مجموعه ای از بایت است که هیچ چیز غیرمستقیمی در آن وجود ندارد:
این ترتیب 29 بایت و 0 تخصیص است. به صورت C++ std::string + std::vector<Item> 160 بایت و 1 تخصیص و به صورت Rust String + Vec<Item> 153 بایت و 4 است. یک کتاب سفارش کامل یک آرایه است که در حافظه پنهان جریان دارد. قیمت این است که آرایه ای از عناصر با اندازه متغیر متوالی است: می توانید آن را تکرار کنید اما آن را فهرست نکنید (مگر اینکه آرایه دومی از ارجاعات را به عنوان شاخص در آن نگه دارید).
انواع داده های جبری تنها چندشکلی پویا هستند: بدون وراثت، بدون vtable. هر ADT را می توان به دو روش ذخیره کرد که در نقطه استفاده انتخاب می شود. حالت ثابت (شکل) یک اتاق تگ به علاوه برای بزرگترین محموله، قابل نمایه سازی و بازنویسی است. حالت متغیر (شکل..) به هر مقدار دقیقا اندازه نوع آن را می دهد. آرایه متوالی می شود و یک مقدار هرگز تغییر نمی کند، و در ازای آن می توانید ارجاعات را در یک بار استفاده کنید:
توابع Case راه دیگری برای ارسال روی یک نوع ارائه میکنند: تعریف یک اضافه بار برای هر نوع. فراخوانی روی برچسب از طریق جدول پرش ارسال میشود و کامپایلر بررسی میکند که اضافهبارها همه انواع را پوشش میدهند:
T& یک آدرس ماشین است: هشت بایت، هرگز آویزان نیست. یک مرجع نسبی همان پیوند ذخیره شده به عنوان یک افست باریک است: T&<u32> از خود فیلد به یک هدف در همان آرایه اندازه گیری می شود، بنابراین ساختار مستقل از موقعیت است و T&<u32 در pool> از پایه استخر نامگذاری شده است، بنابراین آرایه های دیگر می توانند به استخر متصل شوند. T&<u32>؟ از افست 0 به عنوان تهی استفاده می کند. در اینجا یک درخت جستجوی دودویی در یک آرایه فقط رشد، با گره های 12 بایتی وجود دارد:
مراجع شفاف هستند (نه * , نه ->)، .= یکی را متصل می کند یا مجددا هدف قرار می دهد و Node? یک مرجع ابطال پذیر است که اگر , guard and assert narrow است. درج یک فشار و یک ذخیره است و فشار نمی تواند cur را باطل کند. هنگامی که طول عمر تو در تو نیست، یک استخر قابل استفاده مجدد یک آرایه فقط رشد را با یک لیست آزاد مخفی جفت می کند: alloc_ref یک شکاف را مجددا استفاده می کند یا فشار می دهد، دست ها را به عقب باز می کند، و از آنجایی که هیچ چیز واقعا آزاد نمی شود، یک شکاف قدیمی به جای اینکه حافظه را خراب کند، مقدار متفاوتی از نوع صحیح را می خواند.
پیوندهای درخت مربوط به خود هستند، بنابراین در هر کجا که قرار میگیرد به همان معناست: ذخیره کردن نوشتن بایتهای آن و بارگیری خواندن دوباره آنها است، بدون سریالساز، طرحواره و بدون اصلاح نشانگر. from_bytes قاببندی، هر برچسب، طول و هر پیوند را قبل از تبدیل شدن بایتها به یک مقدار بررسی میکند، بنابراین یک فایل خراب یا خصمانه یک مرجع نادرست و هرگز یک مرجع وحشی نیست.
ضمانت ساخت بدون کپی (مشخصات §4.3) می گوید که یک مقدار ساخته شده همیشه در خانه نهایی خود ساخته می شود و از طریق تماس ها از بالا به پایین منتشر می شود. تابعی که یک آرایه فقط رشد را بر اساس مقدار برمی گرداند، عناصر خود را مستقیما در متغیر فراخواننده یا در فیلدی از رکورد که در آرایه دیگری ساخته می شود، می نویسد، بنابراین پارامترهای خارج عمدتا ظاهر نمی شوند.
بازگشت E از f، E را به عنوان نتیجه درونیترین فراخوانی فعال f، برمیگرداند، هر چند فریم بالا باشد، و هر تابعی در این بین امضای ساده خود را حفظ میکند. این به صورت ایستا بررسی می شود، بنابراین هر فراخوانی parse_user باید در یک تماس بارگذاری قرار گیرد و هیچ چیز در زمان اجرا کشف نشده است. این یک تمایز پنهان است که در هر فریم بررسی می شود تا باز کردن، و پیام مستقیما در جایی که تماس گیرنده بار آن را می خواهد ساخته می شود. تجزیه کننده ها در نمونه ها از آن برای هر خطای نحوی استفاده می کنند.
همه چیز یک شکل است. آرگومان های نوع از انواع آرگومان استنتاج می شوند. پارامترهای غایب از آن انواع را می توان به صراحت ارائه کرد. مقادیر تابع بهعنوان پارامترهای عمومی ارسال میشوند: هر فراخوانی مستقیم و غیرقابل تغییر است، آنها نمیتوانند فرار کنند و هیچ شی بسته یا نشانگر تابعی وجود ندارد، بنابراین یک تابع مرتبه بالاتر دقیقا به حلقهای که به نظر میرسد کامپایل میشود. یک بلوک ممکن است از تابع محصور واژگانی خود بازگردد و توابع تو در تو، محلی های تابع محصور کننده را می بینند.
هیچ حافظه قابل تغییر مشترکی وجود ندارد. یک thread_fn و هر چیزی که فراخوانی میکند به عنوان برنامه خودش با پشتههای داده خودش و کپیهای خودش از جهانیها کامپایل میشود. مقادیر از صفهای تایپشده عبور میکنند، یکی برای هر نوع، و باید مسطح باشند، بدون ارجاع در هیچ عمقی. این ارزان است زیرا یک مقدار Goes مسطح به هم پیوسته است: یک کار یا یک نتیجه حاوی داده های واقعی است، پیکسل ها گنجانده شده است، و تقاطع یک memcpy است.
بررسی یا سقط ناموفق در کارگری که با thread_spawn_contained شروع شده است فقط به آن کارگر خاتمه میدهد. پیام آن به کسی که منتظر آن است می رود، نه به stderr، بنابراین یک سرپرست می تواند آن را ثبت کند و دیگری را شروع کند:
اولین thread_wait(id, msg) برای یک کارگر پیام را می گیرد. تماس گیرنده ای که آن را درخواست نمی کند آن را از دست می دهد.
یک fn خارجی یک امضای Goose را به نماد C متصل می کند. ماژول های ریاضی و سیستم عامل از این رابط استفاده می کنند. فقط انواع با نمایش C پشتیبانی شده ممکن است از مرز عبور کنند. امضاهای پشتیبانی نشده در اعلامیه رد می شوند. کامپایلر یک فایل C را برای کل برنامه منتشر می کند. باطن همراه TinyCC می تواند آن C را در فرآیند کامپایلر Goose کامپایل و اجرا کند.
بخش اعظم سرعت از مدل حافظه ناشی میشود: بدون تخصیص پشته یا حذف به ازای هر شی، دادههای پیوسته، پیوندهای باریک، و شمارههایی که برای نوع موجود در آنها اندازه میشوند. هر شاخصی با کران بررسی می شود و کامپایلر چک هایی را که ممکن است غیرضروری باشد حذف می کند. یک ادعا در طول یک برش اغلب به یک هسته اجازه میدهد تا بررسیهای باقیمانده خود را حذف کند و بردار شود. --bce-lines چک های باقی مانده را گزارش می کند. چکفری fn یک هسته را رد میکند در صورت باقی ماندن هر چک مرزی. انواع شاخص به لبه های گراف اجازه می دهند تا اعتبار انجام شده در طول ساخت را حفظ کنند.
اندازهگیریهای معیار از زمان کل فرآیند ساعت دیواری، از جمله خرابی استفاده میکنند.
خلاصه سود و زیان را توضیح می دهد. results.md همه اندازهگیریها را دارد و design.md توضیح میدهد که مجموعه چه چیزی را آزمایش میکند.
کامپایلرهای از پیش ساخته شده برای Windows (x64)، لینوکس (x64) و macOS (Apple silicon و Intel) در صفحه نسخهها قرار دارند، با بستههای گرافیکی، صوتی، رابط کاربری و فیزیک کامپایل شدهاند. یکی را در هر جایی بستهبندی کنید و دایرکتوری آن را در PATH قرار دهید: خود کامپایلر کتابخانه استاندارد را پیدا میکند، بستهها را تا زمانی که TinyCC اجرا میکند نگه میدارد. ~/.goose (GOOSE_HOME). آرشیو این اسناد، نمونه ها و پسوند VS Code را نیز در خود جای می دهد.
اجرای یک برنامه در فرآیند (goose file.goose) به هیچ چیز دیگری در ویندوز نیاز ندارد. در لینوکس به هدرهای کتابخانه C (libc6-dev یا glibc-devel) نیاز دارد. در macOS، SDK ابزارهای خط فرمان Xcode (xcode-select --install). Goose build همچنین به یک کامپایلر C نیاز دارد: Visual Studio 2022 یا جدیدتر، یا clang، در ویندوز. gcc یا صدای صدا در جای دیگر. کامپایلر لینوکس به glibc 2.35 یا بالاتر (اوبونتو 22.04، دبیان 12)، macOS one macOS 13.3 یا بالاتر نیاز دارد. macOS فایلهای بایگانی دانلود شده را تا زمانی که xattr -dr com.apple.quarantine در دایرکتوری بدون بستهبندی اجرا نشود، اجرا نمیکند.
شما به CMake 3.27 یا جدیدتر، یک کامپایلر C++20 (MSVC، clang یا gcc) و پایتون 3 برای اجراهای آزمایشی و نمونه نیاز دارید. TinyCC، که باطن در فرآیند از آن ساخته شده است، بخشی از درخت است. ماژولهای گرافیکی، صوتی، رابط کاربری و فیزیک بستههایی در مخازن خودشان هستند (goose_gfx و goose_physics؛ docs/design/packages.md): -DGOOSE_BUILTIN_LIBS=همه آنها را در تگهای پینشده (git، در زمان پیکربندی، پیکربندیشده با برنامههای کامپایلکننده) واکشی میکنند.
بدون آن، کامپایلر به تنهایی میسازد، و برنامهای که یکی از آنها را وارد میکند، اولین بار آن را در ~/.goose تعبیه میکند، که به git، cmake و یک کامپایلر C نیاز دارد.
GOOSE_BUILTIN_LIBS همچنین میتواند برخی از بستهها را نامگذاری کند (فیزیک، یا gfx، که صدا و رابط کاربری را نیز حمل میکند). در لینوکس، SDL به بستههای توسعه X11 یا Wayland نیاز دارد (third_party/SDL/docs/README-linux.md). بدون آنها بیلد ماژول gfx را بیرون می گذارد و می گوید چه چیزی را نصب کنیم.
اسکریپت های runner build/Release، سپس build/RelWithDebInfo، سپس build و سپس build/Debug را ترجیح می دهند. برای انتخاب یک کامپایلر خاص، --exe را پاس کنید. هنگام آزمایش با فعال بودن اظهارات کامپایلر، از یک Debug اجرایی صریح استفاده کنید. کامپایلر build/goose است (build/Release/goose.exe با مولد ویژوال استودیو)، و کتابخانه استاندارد را در درخت منبعی که از آن ساخته شده است، پیدا می کند. در ویندوز 10 نسخه 1903 به بعد، مانیفست تعبیه شده آن صفحه کد فرآیند UTF-8 را انتخاب می کند تا مسیرهای کامپایلر به منطقه سیستم بستگی نداشته باشد. نسخه های قبلی ویندوز صفحه کد قدیمی خود را حفظ می کنند.
فایل های اجرایی بومی تولید شده تنظیمات فرآیند خود را حفظ می کنند.
یک برنامه را مستقیما از منبع، در فرآیند اجرا کنید (goose run به صراحت همین را می گوید، و goose check فقط تایپ می کند):
یا یک فایل اجرایی بسازید که در کنار منبع قرار می گیرد (-o آن را در جای دیگری قرار می دهد). کامپایلر یک کامپایلر C را روی دستگاه پیدا میکند (غاز ابزار میگوید: MSVC یا clang در ویندوز، cc، gcc یا clang در جای دیگر؛ --cc انتخاب میکند) و C تولید شده را با زمان اجرا و هر ماژول بومی که برنامه استفاده میکند، شامل گرافیک، پیوند میدهد. برنامههایی که از رشتهها استفاده میکنند به این مسیر نیاز دارند، زیرا TinyCC نمیتواند فضای ذخیرهسازی محلی را قرار دهد.
--debug سرریز، محدوده و تگ را در C تولید شده فعال میکند، هر کدام خط منبع خود را نامگذاری میکنند (و یک سرریز یا یک کست مقادیر مربوطه)، و C را با اطلاعات اشکالزدایی میسازد. --dry-run دستوراتی را که یک بیلد اجرا می کند را برای یک سیستم ساخت خود چاپ می کند. -v آنها را در حین اجرا چاپ می کند. واسطهها زیر ~/.goose (GOOSE_HOME) قرار میگیرند، شی زمان اجرا یک بار در هر کامپایلر C کامپایل میشود.
پرچمهای مفید هر دو شکل: -O0 / -O1 / -O2 سطح inlining را تنظیم میکند (بیلد به طور پیشفرض روی -O2 است، یک اجرا روی -O1 است)، -bce-lines بررسی کرانهای نگهداشته شده در هر خط را گزارش میکند، و -DGS_DEBUG=1 چیزی است که -debug تعریف میکند. هر مقدار قابل تغییر اندازه روی یک پشته داده 2 گیگابایتی از فضای آدرس رزرو شده زندگی میکند، در صورت استفاده متعهد میشود: --stack-reserve 16G این تغییرات را برای یک برنامه تغییر میدهد، --stacks گزارش میکند که برنامه و هر یک از کارگران آن چند پشته داده میتوانند بگیرند و درپوشی که روی hardware_threads() قرار میدهد، و -DGS_STACK_STACK_STATS از آن استفاده میکند.
برنامه ای با C از خودش آن را در یک sidecar در کنار منبع، foo.build beside foo.goose : c_sources = ["impl.c"]; , با c_include , c_defines , c_libs و _windows , _linux یا _macos انواع هر کدام برای قسمت یک پلتفرم. هر دو شکل آن را میسازند: goose build آن را به آن پیوند میدهد، و goose run آن را در یک کتابخانه میسازد، اجرای در فرآیند بارگیری میشود، بنابراین یک fn خارجی در هر دو طرف به آن میرسد.
یک کتابخانه همین کار را با یک sidecar در کنار ورودی خود انجام می دهد (یا هدف کتابخانه ایستا پروژه CMake خود را نامگذاری می کند، cmake_target = "x"؛ که کامپایلر از طریق cmake می سازد)، و یک برنامه یک کتابخانه با نام import را از "source" می گیرد. در فایل اصلی آن: یک دایرکتوری ( "./lib")، یا یک مخزن git در یک برچسب یا commit ("owner/repo@v1" در GitHub، "host/owner/repo@ref")، در ~/.goose واکشی شده و یک بار در هر کامپایلر C در آنجا ساخته می شود (docs/design/packages.md).
در زیر، goose -o tour.c tour.goose C را می نویسد، که برنامه و بخشی از زمان اجرا را در آن نگه می دارد. بقیه زمان اجرا (لایه سیستم عامل، رزرو پشته داده ها، رشته ها، چاپ) یک فایل C دیگر است که goose --emit-runtime goose_runtime.c می نویسد تا یک بار کامپایل شود و با هر برنامه پیوند داده شود (cc -O2 tour.c goose_runtime.o -o tour -lm -O به Windowsclad /pthre. goose_runtime.obj). زمان اجرا را با کامپایلر بسازید و برنامه های مورد استفاده را پرچم گذاری کنید. از خود هیچ -D نمی گیرد و برنامه ای از نسخه دیگری از کامپایلر نمی تواند با آن پیوند برقرار کند.
--standalone یک فایل C می نویسد که در عوض کل زمان اجرا را نگه می دارد و به تنهایی ایجاد می کند. برنامهای که از صدا، gfx، فیزیک یا رابط کاربری استفاده میکند، لایه آن ماژول را نیز پیوند میدهد، که در یک فایل پاسخ، --audio-link msvc|cc، --gfx-link، --physics-link و --ui-link را در یک فایل پاسخ (cc cube.c goose_runtime.o -o cube @$(goose a --sxf) قرار می دهد. عبور می کند). goose build --dry-run همه اینها را برای برنامه در دست چاپ می کند.
برنامه ای با استفاده از صدا، PCM رویه ای مونو/استریو را از طریق SDL3 بدون باز کردن پنجره گرافیکی پخش می کند. مرجع صوتی را ببینید.
مجموعه آزمایشی و نمونه ها روی ویندوز، macOS و لینوکس اجرا می شوند:
برای ویرایش، پسوند VS Code برجسته سازی نحو، سرور زبان کامپایلر ( goose lsp : خطاها هنگام تایپ، شناور، رفتن به تعریف، تکمیل، تغییر نام، نمادها) و یک کلید از طریق JIT اجرا می شود:
ویرایشگرهای دیگر می توانند خود goose lsp را اجرا کنند. ویرایشگرها پیکربندی Neovim، Helix و Emacs را دارند.
غاز نو است. چیزی که امروزه وجود دارد یک کامپایلر کل برنامه از حدود سی هزار خط C++، مشخصات، کتابخانه استاندارد، نمونه ها و یک مجموعه آزمایشی است که CI روی ویندوز، macOS و لینوکس با یک کار ضد عفونی کننده اضافی اجرا می کند. فعلا عمدا خارج از محدوده: حرکت برای مقادیر قابل تغییر اندازه، بیش از یک قابل تغییر اندازه در هر ساختار، شکست برچسب شده، حریم خصوصی فضای نام، و دسترسی بیشتر به سیستم عامل/کتابخانه. موارد باز در ضمیمه B مشخصات ردیابی می شوند.
Goose تحت مجوز آپاچی، نسخه 2.0 مجوز دارد.
Wouter van Oortmerssen: طراحی زبان، طراحی کامپایلر، استانداردهای کدنویسی. صفحه اصلی، توییتر.
کلود فابل: پیاده سازی کامپایلر، محک زدن، نگارش نمونه و سند.
این مخزن تقریبا به طور کامل با هوش مصنوعی و با طراحی زبان انسانی تولید شده است. من چندین سال پیش Goose را طراحی کرده بودم و شروع به پیاده سازی آن کرده بودم، اما اجرای یک استارت آپ بازی (که بر اساس زبان برنامه نویسی دیگر من، Lobster ساخته شده است) زمانی برای اتمام آن وجود نداشت. غم انگیز بود، زیرا می دانستم Goose می تواند کارهایی را انجام دهد که زبان های دیگر نمی توانند انجام دهند و باید وجود داشته باشد.
انتظار نداشتم هوش مصنوعی در این مورد کمک کند تا زمانی که Fable منتشر شد. من فکر کردم که ممکن است به اندازه کافی توانایی انجام یک کار خوب را داشته باشد. من آن را اساسا سبک و ساختار دیگر کامپایلر اخیرم (خرچنگ) را شبیه سازی کردم، به همین دلیل است که اگر به کد نگاه کنید، تقریبا شبیه آن به نظر می رسد. طراحی اولیه من خیلی چیزها را نامشخص گذاشته بود، و رفت و آمدهای زیاد با Fable باعث شد در مورد همه آنها تصمیم بگیرم و اکنون زبان بهتری برای آن است.
همچنین آزمایشی در استفاده از هوش مصنوعی برای پیاده سازی یک زبان جدید به جای بازتولید زبان موجود است.
ممکن است تعجب کنید که چرا آن را در C++ کار کردم، اگر واضح است که می توانستم از هر زبانی مانند Rust یا Lobster خودم یا ... خود Goose استفاده کنم (این ممکن است هنوز هم اتفاق بیفتد). با Fable، مشاهده من این بود که در هر زبانی تقریبا به همان اندازه توانایی دارد، و نردههای محافظ قوی Rust یا زبانهای دیگر به اندازه گذشته فشار نمیآورند. از آنجایی که کامپایلر C منتشر می کند، احتمالا از قبل باید یک کامپایلر C/C++ در اطراف داشته باشید، بنابراین به نظر می رسد چسبیدن در یک اکوسیستم استقرار و پذیرش را ساده می کند. من همچنین libtcc و چه کسی میداند چه کتابخانههای دیگر C را در آینده یکپارچه کردهام.
و همانطور که گفتم، تقلید کد کامپایلر از کامپایلر Lobster موجود من سرگرم کننده به نظر می رسید، حداقل می توانم آن را بخوانم که انگار مال من است.
متن اصلی (انگلیسی)
The Goose Programming Language
https://twitter.com/ID_AA_Carmack/status/2113704269274636568 Comments URL: https://news.ycombinator.com/item?id=50224100 Points: 216 # Comments: 152