S-Expressions: صيغة JSON للبرمجة الوظيفية (التي تكون منطقية فعلاً)

لماذا اخترنا S-expressions (تعبيرات رمزية بأسلوب Lisp) بدلاً من JSON لتعريف المنطق، ولماذا قد تفعل ذلك أيضاً.
الجميع يحب JSON، لكن عندما تحتاج منطقاً، ينتهي بك المطاف بقوالب نصية أو JavaScript. ماذا لو كانت صيغة البيانات هي صيغة المنطق؟
قيود JSON
JSON ممتاز للبيانات:
{
"name": "John",
"age": 30,
"hobbies": ["coding", "reading"]
}
لكن ماذا عن المنطق؟ لديك عدة خيارات:
الخيار 1: قوالب نصية
{
"condition": "user.age >= 18 && user.verified"
}
- عُرضة للأخطاء (أخطاء مطبعية في النصوص)
- بدون تحقق
- خطر الحقن
الخيار 2: لغة مخصصة
{
"condition": {
"and": [
{ "gte": ["user.age", 18] },
{ "eq": ["user.verified", true] }
]
}
}
- مُهيكل
- مُطوَّل
- صعب القراءة
الخيار 3: دوال JavaScript
const condition = (user) => user.age >= 18 && user.verified;
- سهل القراءة
- غير قابل للتسلسل
- خطر أمني (eval)
ادخل عالم الـ S-Expressions
الـ S-expressions (تعبيرات رمزية موجودة منذ 1958 مع Lisp) بسيطة للغاية:
(operator operand1 operand2 ...)
بصيغة متوافقة مع JSON:
["operator", "operand1", "operand2", ...]
الـ S-Expressions في Almadar
يستخدم Almadar الـ S-expressions للـ guards (شروط تتحقق قبل السماح بالانتقال) والـ effects (تغييرات تحدث بعد الـ transition):
الـ Guards: المنطق الشرطي
{
"from": "pending",
"to": "approved",
"event": "APPROVE",
"guard": ["and",
[">=", "@user.roleLevel", 5],
["not", "@entity.isFlagged"],
[">", "@entity.amount", 0]
]
}
هذا يعادل:
if (user.roleLevel >= 5 && !entity.isFlagged && entity.amount > 0) {
// Allow transition
}
لكنه:
- قابل للتسلسل
- قابل للتحقق
- آمن (بدون eval)
- متعدد المنصات
الـ Effects: تغييرات الحالة
{
"effects": [
["set", "@entity.status", "approved"],
["set", "@entity.approvedAt", "@now"],
["set", "@entity.approvedBy", "@user.id"],
["persist", "update", "Order", "@entity.id", "@entity"]
]
}
كل effect عبارة عن S-expression:
["set", target, value]— تعيين قيمة["persist", operation, entity, id, data]— حفظ في قاعدة البيانات["emit", event, payload]— إرسال حدث
لماذا هذا مهم
1. الـ Homoiconicity (التماثل بين الكود والبيانات)
الـ S-expressions عبارة عن بيانات تبدو كالكود. هذا يعني:
["+", "@entity.count", 1]
هي في الوقت ذاته:
- هيكل بيانات (مصفوفة من نصوص)
- كود قابل للتنفيذ (أضف 1 إلى العداد)
2. قابلية التركيب
يمكنك تضمين الـ S-expressions بلا حدود:
["if",
["and",
[">", "@entity.score", 100],
["=", "@entity.status", "active"]
],
["emit", "ACHIEVEMENT_UNLOCKED", { "level": "gold" }],
["emit", "ACHIEVEMENT_PROGRESS", { "needed": ["-", 100, "@entity.score"] }]
]
3. التسلسل
لأن الـ S-expressions مجرد مصفوفات، فإنها تُسلسَل بشكل مثالي:
// JavaScript
const guard = [">=", "@user.age", 18];
JSON.stringify(guard); // '[">=","@user.age",18]'
# Python
guard = [">=", "@user.age", 18]
json.dumps(guard) # '[">=","@user.age",18]'
// Rust
let guard = json!( [">=", "@user.age", 18] );
serde_json::to_string(&guard).unwrap();
سياق الـ Binding
تستخدم الـ S-expressions في Almadar بادئات خاصة للسياق:
| البادئة | المعنى | مثال |
|---|---|---|
@entity.field | حقل الـ entity الحالي | "@entity.status" |
@payload.field | حمولة الحدث | "@payload.userId" |
@state | اسم حالة الـ state machine الحالية | "@state" (مثلاً "Browsing") |
@user.field | المستخدم الحالي | "@user.id" |
@now | الطابع الزمني الحالي | "@now" |
هذا يُنشئ نظام binding (ربط تصريحي):
{
"guard": ["=", "@entity.ownerId", "@user.id"],
"effects": [
["set", "@entity.updatedAt", "@now"],
["set", "@entity.updatedBy", "@user.id"]
]
}
تشبيه واقعي: صيغ Excel
إذا استخدمت Excel، فقد استخدمت S-expressions:
=IF(AND(A1>100, B1="active"), "Gold", "Silver")
في Almadar:
["if",
["and", [">", "@entity.score", 100], ["=", "@entity.status", "active"]],
"Gold",
"Silver"
]
صيغ Excel هي S-expressions. وهي:
- تصريحية (تقول ماذا، لا كيف)
- قابلة للتركيب (دوال تستدعي دوالاً)
- آمنة (لا تنفيذ كود عشوائي)
المعاملات القياسية
تتضمن الـ standard library (المكتبة القياسية) في Almadar:
المقارنة
["=", "a", "b"] // المساواة
["!=", "a", "b"] // عدم المساواة
[">", "a", "b"] // أكبر من
[">=", "a", "b"] // أكبر من أو يساوي
