Preskoči na glavno vsebino

S-Expressions: JSON funkcijskega programiranja (ki dejansko smisli)

· 4 min branja
Osama Alghanmi
Soustanovitelj in tehnični vodja

S-Expressions, JSON in funkcijska renesansa

Zakaj smo izbrali Lisp-style S-expressions namesto JSON za definicijo logike in zakaj bi tudi vi.

Vsi imamo radi JSON, ampak ko potrebujete logiko, končate z string templates ali JavaScriptom. Kaj če bi bil vaš format podatkov tudi vaš format logike?

Omejitve JSONa

JSON je odličen za podatke:

{
"name": "John",
"age": 30,
"hobbies": ["coding", "reading"]
}

Ampak kaj pa logika? Imate nekaj možnosti:

Možnost 1: String Templates

{
"condition": "user.age >= 18 && user.verified"
}
  • ❌ Nagnjeno k napakam (napake v nizih)
  • ❌ Brez validacije
  • ❌ Tveganje vbrizgavanja

Možnost 2: Custom DSL

{
"condition": {
"and": [
{ "gte": ["user.age", 18] },
{ "eq": ["user.verified", true] }
]
}
}
  • ✅ Strukturirano
  • ❌ Verbetno
  • ❌ Težko za brati

Možnost 3: JavaScript Functions

const condition = (user) => user.age >= 18 && user.verified;
  • ✅ Berljivo
  • ❌ Ni serializabilno
  • ❌ Varnostno tveganje (eval)

Vstopijo S-Expressions

S-expressions (simbolične izraze) obstajajo od leta 1958 z Lispem. So preprosti:

(operator operand1 operand2 ...)

V JSON-friendly obliki:

["operator", "operand1", "operand2", ...]

S-Expressions v Almadarju

Almadar uporablja S-expressions za guards in effects:

Guards: Pogojna logika

{
"from": "pending",
"to": "approved",
"event": "APPROVE",
"guard": ["and",
[">=", "@user.roleLevel", 5],
["not", "@entity.isFlagged"],
[">", "@entity.amount", 0]
]
}

To je enakovredno:

if (user.roleLevel >= 5 && !entity.isFlagged && entity.amount > 0) {
// Dovoli prehod
}

Ampak je:

  • ✅ Serializabilno
  • ✅ Validabilno
  • ✅ Varno (brez eval)
  • ✅ Cross-platform

Effects: Spremembe stanja

{
"effects": [
["set", "@entity.status", "approved"],
["set", "@entity.approvedAt", "@now"],
["set", "@entity.approvedBy", "@user.id"],
["persist", "update", "Order", "@entity.id", "@entity"]
]
}

Vsak effect je S-expression:

  • ["set", target, value] — Nastavi vrednost
  • ["persist", operation, entity, id, data] — Shrani v bazo
  • ["emit", event, payload] — Emitiraj dogodek

Zakaj je to pomembno

1. Homoikoničnost (koda kot podatki)

S-expressions so podatki, ki izgledajo kot koda. To pomeni:

["+", "@entity.count", 1]

Je oboje:

  • Podatkovna struktura (array nizov)
  • Izvajljiva koda (prištej 1 k count)

2. Kompozabilnost

S-expressions lahko poljubno gnezdite:

["if",
["and",
[">", "@entity.score", 100],
["=", "@entity.status", "active"]
],
["emit", "ACHIEVEMENT_UNLOCKED", { "level": "gold" }],
["emit", "ACHIEVEMENT_PROGRESS", { "needed": ["-", 100, "@entity.score"] }]
]

3. Serializacija

Ker so S-expressions samo arrayi, se serializirajo popolno:

// 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();

Kontekst vezave

S-expressions v Almadarju uporabljajo posebne predpone za kontekst:

PredponaPomenPrimer
@entity.fieldPolje trenutnega entity"@entity.status"
@payload.fieldEvent payload"@payload.userId"
@stateIme trenutnega stanja state machine"@state" (npr. "Browsing")
@user.fieldTrenutni uporabnik"@user.id"
@nowTrenutni timestamp"@now"

To ustvarja deklarativni vezalni sistem:

{
"guard": ["=", "@entity.ownerId", "@user.id"],
"effects": [
["set", "@entity.updatedAt", "@now"],
["set", "@entity.updatedBy", "@user.id"]
]
}

Primerjava iz resničnega sveta: Excel formule

Če ste uporabljali Excel, ste uporabljali S-expressions:

=IF(AND(A1>100, B1="active"), "Gold", "Silver")

V Almadarju:

["if",
["and", [">", "@entity.score", 100], ["=", "@entity.status", "active"]],
"Gold",
"Silver"
]

Excel formule so S-expressions. So:

  • Deklarativne (poveste kaj, ne kako)
  • Kompozabilne (funkcije kličejo funkcije)
  • Varno (brez poljubnega izvajanja kode)

Standardni operatorji

Almadarjeva standardna knjižnica vsebuje:

Primerjava

["=", "a", "b"]        // enakost
["!=", "a", "b"] // ni enako
[">", "a", "b"] // večje od
[">=", "a", "b"] // večje ali enako

Logika

["and", "a", "b", "c"] // vsi morajo biti true
["or", "a", "b", "c"] // vsaj en true
["not", "a"] // negacija

Matematika

["+", "a", "b", "c"]   // vsota
["-", "a", "b"] // razlika
["*", "a", "b"] // produkt
["/", "a", "b"] // količnik

Array

["count", "@array"]    // dolžina arraya
["contains", "@array", "item"] // članstvo
["filter", "@array", ["predicate"]]

String

["concat", "a", "b"]   // združi
["length", "str"] // dolžina niza
["matches", "str", "regex"]

Poskusite: Zgradite Guard

Ustvarimo guard za approval workflow:

{
"from": "pending",
"to": "approved",
"event": "APPROVE",
"guard": ["and",
["or",
[">=", "@user.roleLevel", 5],
["=", "@user.id", "@entity.ownerId"]
],
["not", "@entity.isLocked"],
[">", "@entity.amount", 0],
["<", "@entity.amount", 10000]
]
}

To se prevede v:

if (
(user.roleLevel >= 5 || user.id === entity.ownerId) &&
!entity.isLocked &&
entity.amount > 0 &&
entity.amount < 10000
) {
// Dovoli odobritev
}

Ampak z:

  • ✅ Deklarativno sintakso
  • ✅ Avtomatično validacijo
  • ✅ Brez tveganja vbrizgavanja kode
  • ✅ Serializabilno za audit loge

Spoznanje

S-expressions niso samo Lisp-radovednost — so praktična rešitev za "kako damo logiko v JSON?"

Dajejo vam:

  • Moč kode (kompozabilnost, izraznost)
  • Varnost podatkov (serializacija, validacija, brez eval)
  • Jasnost Excela (deklarativno, berljivo)

Naslednjič, ko vas bo zamikalo uporabiti eval() ali string templates za dinamično logiko, pomnite: obstaja 60 let stara rešitev, ki dejansko deluje.

Želite raziskati več? Preverite standardne knjižnične operatorje.

Nedavne objave