اگر برنامهنویس Go هستید و میخواهید بدون ترک این اکوسیستم، از انعطافپذیری زبانهای Lisp بهره ببرید، Glojure پلی مستقیم برای شماست. این ابزار که بهصورت متنباز در گیتهاب منتشر شده، به شما اجازه میدهد یک مفسر Clojure را مستقیماً در فایلهای باینری Go جایگذاری کنید یا از آن به عنوان یک ابزار خط فرمان مستقل استفاده کنید.

سالها بود که Clojure با ماشین مجازی جاوا (JVM) گره خورده بود تا از کتابخانههای عظیم آن استفاده کند. Glojure تلاش میکند همین رابطهٔ «میزبانی» را بازسازی کند، اما جای جاوا را با Go عوض کرده است. در زمینه Clojure روی JVM، «میزبان بودن» به این معناست که تمام مقادیر جاوا، مقادیر Clojure هستند و بالعکس. Glojure تلاش میکند دقیقاً همین رابطه را با Go حفظ کند؛ به این معنا که تمام مقادیر Go میتوانند به عنوان مقادیر Glojure استفاده شوند و برعکس.
این یعنی به جای هدف قرار دادن JVM، کد Clojure شما روی یک محیط زمان اجرای مبتنی بر Go اجرا میشود. این امر باعث میشود کتابخانههای Go به همان اندازه برای Clojure در دسترس باشند که چارچوبهای جاوا برای Clojure استاندارد در دسترس هستند. طبق مستندات پروژه، این سیستم در مراحل اولیه توسعه است. کاربران باید منتظر باگها، نبود برخی ویژگیها و عملکرد محدود باشند، زیرا سازگاری با نسخههای قدیمی تا انتشار نسخه ۱ تضمین نشده است. با این حال، این ابزار در حال حاضر بخش بزرگی از کتابخانه هسته Clojure (در حالت تغییریافته) را اجرا میکند و در چندین پروژه تفننی با موفقیت به کار گرفته شده است.
پیادهسازی فنی و نصب
برای شروع، شما به نسخه ۱.۱۹ یا بالاتر Go نیاز دارید، هرچند پروژه بهطور مشخص برای نصب، Go ۱.۲۴ را میطلبد. میتوانید ابزار را از طریق خط فرمان نصب کنید:
$ go install github.com/glojurelang/glojure/cmd/glj@latest
پس از نصب، دستور glj یک تجربه کامل REPL (Read-Eval-Print-Loop) را فراهم میکند. شما میتوانید نشست را با اجرای ساده $ glj آغاز کنید. برای مثال، وارد کردن (println "Hello, world!") رشته را چاپ کرده و مقدار nil را برمیگرداند.
این نشست تعاملی شامل چندین ویژگی توسعه حرفهای است:
- حالتهای ویرایش: پشتیبانی از هر دو حالت Vi (پیشفرض) و Emacs؛ پیکربندی از طریق فایل
~/.inputrcانجام میشود. - ورودی هوشمند: ویرایش چندخطی که در آن عبارات ناقص در خط بعدی با تورفتگی خودکار ادامه مییابند. تکمیل خودکار (Tab completion) برای نمادها، فضای نامها و نامهای مستعار با برچسبهای توصیفی در دسترس است.
- مدیریت نشست: تاریخچه پایدار که در فایل
~/.glj_historyدر طول نشستها ذخیره میشود و قابلیت Bracketed Paste برای درج فوری بلوکهای کد. - کنترل: کنترل شغلی استاندارد از طریق Ctrl+Z برای تعلیق و
fgبرای بازگشت. وقفهها از طریق Ctrl+C برای لغو ورودی یا متوقف کردن ارزیابی مدیریت میشوند.
استفاده از خط فرمان و اسکریپتنویسی
ابزار glj فراتر از یک REPL تعاملی، تطبیقپذیر است. شما میتوانید نسخه را با دستور $ glj --version بررسی کنید که در حال حاضر را به عنوان glojure v0.3.0 شناسایی میکند. در داخل REPL، نسخه از طریق نماد *glojure-version* قابل بررسی است که یک نقشه (Map) برمیگرداند: {:major 0, :minor 3, :incremental 0, :qualifier nil}.
برنامهنویسان میتوانند عبارات را مستقیماً از شل با استفاده از پرچم -e ارزیابی کنند. برای مثال:
$ glj -e '(println "Hello, World!")'$ glj -e '(apply + (range 3 10))'(نتیجه ۴۲ را برمیگرداند)$ glj -e ' (defn factorial [n] (if (<= n 1) 1 (* n (factorial (dec n))))) (factorial 5)'(نتیجه ۱۲۰ را برمیگرداند)
علاوه بر این، شما میتوانید اسکریپتهای Clojure را اجرا کنید. فایلی به نام hello.glj حاوی کد (println "Hello," (first *command-line-args*)) را میتوان با دستور $ glj hello.glj World اجرا کرد که منجر به چاپ "Hello, World" میشود. شما حتی میتوانید برنامههای اجرایی بسازید، مانند یک سرور که از net:http.ListenAndServe ":8080" nil برای مدیریت درخواستها از طریق یک echo-handler تعریف شده استفاده میکند.
جایگذاری و تعامل دوطرفه
یکی از قدرتمندترین جنبههای Glojure، قابلیت جایگذاری (Embedding) در برنامههای Go با استفاده از بسته github.com/glojurelang/glojure/pkg/runtime است. این قابلیت برای افزودن پیکربندیهای اسکریپتی، اجازه دادن به کاربران برای گسترش برنامهها با پلاگینهای Clojure، ترکیب عملکرد Go با بیانپذیری Clojure، یا کنترل محیط اجرا از طریق Sandbox و ورودی/خروجی سفارشی مفید است.
تعامل در این سیستم دوطرفه است. شما میتوانید یک تابع Go تعریف کنید و آن را با استفاده از glj.Var در دسترس زمان اجرای Clojure قرار دهید. برای مثال، یک تابع Go مانند greet(name string) میتواند از طریق greetVar.SetRoot(greet) به یک متغیر Clojure به نام greet-from-go نگاشت شود. برعکس، شما میتوانید یک تابع Clojure مانند (defn add [x y] (+ x y)) تعریف کنید و آن را از داخل Go با استفاده از متد .Invoke(10, 32) فراخوانی کنید که مقدار ۴۲ را برمیگرداند.
ادغام با بستههای Go
Glojure دارای تعامل داخلی برای بسیاری از بستههای کتابخانه استاندارد است. برای جلوگیری از تداخل نام با استفاده Clojure از اسلش (/) برای فضای نامها، نام بستههای Go «تغییر شکل» (munged) داده میشوند؛ به این صورت که موارد / در نام بستهها با دونقطه (:) جایگزین میشوند.
بستههای پشتیبانیشده پیشفرض عبارتاند از:
- ابزارهای هسته:
fmt,io,os,errors,flag,reflect,strconv,unicode. - سیستم فایل و شبکه:
io/fs,io/ioutil,net/http. - ریاضیات و منطق:
math,math/big,math/rand,regexp,sort. - همزمانی و زمان:
sync,sync/atomic,time,context,os/exec,os/signal.
برای بستههای سفارشی، توسعهدهندگان میتوانند از ابزار gen-import-interop استفاده کنند. این ابزار یک فایل .go تولید میکند که تابعی را برای افزودن بسته به نقشه بستههای Glojure صادر میکند. دستور طبق این الگو است:$ go run github.com/glojurelang/glojure/cmd/gen-import-interop -packages=:comma-separated-package-list: > your/package/gljimports/my_package_map.go
مقایسه با سایر نسخههای Go
برخلاف دیگر پیادهسازیهای Clojure مبتنی بر Go، Glojure خود را از طریق ماهیت «میزبانی» متمایز میکند. جدول زیر آن را با Joker و let-go مقایسه میکند:
| ویژگی | Glojure | Joker | let-go |
|---|---|---|---|
| میزبان (Hosted) | بله | خیر | خیر |
| تعامل گسترده با Go | بله | خیر | خیر |
| همزمانی | بله | بله (با GIL) | بله |
| ابزارهای Clojure | خیر | بله | خیر |
| نحوه اجرا | مفسر Tree-walk | مفسر Tree-walk | مفسر Bytecode |
در حالی که سایر پورتها ممکن است به عنوان کامپایلرهای مستقل عمل کنند، Glojure تضمین میکند که تمام مقادیر Go اساساً مقادیر Clojure باشند. این ابزار از یک مفسر Tree-walk استفاده میکند و در این مرحله، انعطافپذیری مدل میزبان را بر سرعت اجرای خام ترجیح داده است.
نگاشت انواع داده
برای حفظ سازگاری، Glojure انواع داده Clojure را به صورت زیر به انواع Go نگاشت میکند:
- Long:
int64 - Double:
float64 - Float:
float32 - Byte:
byte(توجه: بایتهای Go بدون علامت هستند، برخلاف بایتهای علامتدار JVM) - Int:
int(توجه: Intهای JVM ۳۲ بیتی هستند، در حالی که Intهای Go بسته به پلتفرم ۳۲ یا ۶۴ بیتیاند) - Char: نوع
runeعلامتگذاری شدهtype Char rune(۳۲ بیتی در Go در مقابل ۱۶ بیتی در JVM) - BigInt: پوشش داده شده توسط
*big.Int - BigDecimal: پوشش داده شده توسط
*big.Float - Ratio: پوشش داده شده توسط
*big.Rat
این نگاشت تضمین میکند که محاسبات با دقت بالا و اعداد بزرگ در مرز بین دو زبان کاربردی باقی بمانند.
این پروژه این فرض را که Clojure حتماً باید به یک ماشین مجازی سنگین وابسته باشد، تغییر میدهد. با آوردن انعطافپذیری Lisp به همزمانی بهینه و توزیع باینری Go، توسعهدهندگان میتوانند سیستمهای بسیار پویا بسازند که همچنان به یک فایل اجرایی واحد و قابل حمل تبدیل میشوند. در واقع، Go به میزبان یک موتور اسکریپتینگ قدرتمند تبدیل شده است.
اگر میخواهید با این گردش کار آزمایش کنید، سعی کنید یک سرور کوچک Go بسازید و از Glojure برای مدیریت پویا منطق مسیریابی (Routing) بدون نیاز به ریاستارت کردن پروسه استفاده کنید.
گام بعدی شما
- اگر از Go استفاده میکنید، سعی کنید یک سرور کوچک بسازید و از Glojure برای مدیریت پویا و لحظهای مسیرهای (Routing) سرور بدون نیاز به ریاستارت استفاده کنید.
- مستندات
gen-import-interopرا برای متصل کردن کتابخانههای شخصی خود به محیط Clojure بررسی کنید. - عملکرد مفسر Tree-walk را در مقایسه با اجرای مستقیم Go در پروژههای کوچک تست کنید تا نقاط گلوگاه را بشناسید.
اما تأثیر این رویکرد بر مدیریت حافظه در مقیاس بالا موضوع دیگری است؛ برای درک بهتر مدیریت حافظه در زبانهای مدرن، تحلیل ما دربارهی Go و Rust را بخوانید.




گفتگو