Manuale di riferimento MALDA™

Il linguaggio di programmazione AI-First - Versione 1.0.11

7. Espressioni

Le espressioni combinano valori, variabili, operatori e chiamate di funzione e si valutano a un singolo valore.

7.1 Espressioni aritmetiche

Le espressioni aritmetiche eseguono operazioni matematiche:

var sum = 10 + 20;        // 30
var product = 5 * 6;      // 30
var quotient = 15 / 3;    // 5
var remainder = 17 % 5;   // 2
var difference = 10 - 3;  // 7

7.2 Espressioni di confronto

Le espressioni di confronto restituiscono valori booleani. Gli operatori relazionali (<, >, <=, >=) funzionano sia con i numeri sia con le stringhe:

// Numeric comparisons
var isEqual = (x == y);
var isGreater = (a > b);
var isLessOrEqual = (x <= 10);
var isNotEqual = (x != y);

// String comparisons (lexicographic/alphabetical order)
var isBefore = ("apple" < "banana");      // true
var isAfter = ("zebra" > "apple");        // true
var isSameOrBefore = ("hello" <= "hello"); // true
var isSameOrAfter = ("hello" >= "hello");  // true

Note sul confronto tra stringhe:

7.3 Espressioni logiche

Le espressioni logiche combinano valori booleani. Il linguaggio supporta sia operatori logici basati su keyword sia operatori simbolici:

var result = (x > 0) and (y < 100);    // or: (x > 0) && (y < 100)
var isValid = not (x == 0);            // or: !(x == 0)
var condition = (a == 1) or (b == 2);  // or: (a == 1) || (b == 2)

7.4 Concatenazione di stringhe

L'operatore + concatena le stringhe:

var greeting = "Hello, " + "World!";
var message = "Value: " + x;

7.5 Ripetizione di stringhe

L'operatore * ripete le stringhe quando un operando è una stringa e l'altro è un numero:

var stars = "*" * 20;        // "********************"
var dashes = "-" * 10;       // "----------"
var pattern = "abc" * 3;     // "abcabcabc"
var count = 5;
var repeated = "x" * count;  // "xxxxx"

Caratteristiche della ripetizione di stringhe

Esempi

print("*" * 10);              // Output: **********
print(5 * "-");                // Output: -----
var n = 3;
print("ha" * n);               // Output: hahaha
var s = "=";
s *= 20;                       // s is now "===================="
print("x" * 0);                // Output: (empty string)

7.6 Interpolazione di stringhe

L'interpolazione di stringhe permette di incorporare espressioni nelle stringhe con la sintassi $"...":

var name = "Alice";
var age = 25;
print($"Hello, {name}!");  // Output: Hello, Alice!
print($"Name: {name}, Age: {age}");  // Output: Name: Alice, Age: 25
print($"Sum: {10 + 20}");  // Output: Sum: 30

Caratteristiche dell'interpolazione

Esempi con virgolette singole

var name = 'Alice';
print($"Hello, {name}!");           // Single quotes in variable
print($"Text: {'value'}");          // Single quotes in expression
print($"Nested: {'He said "hello"'");  // Single quotes avoid escaping

7.7 Operatori di incremento e decremento

Gli operatori di incremento (++) e decremento (--) modificano di 1 il valore di una variabile. Si possono usare come operatori prefissi o postfissi:

var x = 5;
++x;        // Prefix: x is now 6, expression evaluates to 6
x++;        // Postfix: x is now 7, expression evaluates to 6 (old value)
--x;        // Prefix: x is now 6, expression evaluates to 6
x--;        // Postfix: x is now 5, expression evaluates to 6 (old value)

Prefisso e postfisso

Questi operatori si possono usare con variabili, proprietà di oggetti ed elementi di array:

var arr = [10, 20, 30];
arr[0]++;           // arr[0] is now 11

var obj = { count: 5 };
obj.count++;        // obj.count is now 6

7.8 Operatori di assegnamento composto

Gli operatori di assegnamento composto uniscono un'operazione aritmetica all'assegnamento:

var x = 10;
x += 5;     // Equivalent to: x = x + 5;  (x is now 15)
x -= 3;     // Equivalent to: x = x - 3;  (x is now 12)
x *= 2;     // Equivalent to: x = x * 2;  (x is now 24)
x /= 4;     // Equivalent to: x = x / 4;  (x is now 6)

Operatori disponibili

Gli operatori di assegnamento composto funzionano con variabili, proprietà di oggetti ed elementi di array:

var arr = [10, 20];
arr[0] += 5;        // arr[0] is now 15

var obj = { value: 100 };
obj.value *= 2;     // obj.value is now 200

var d = dict { "count": 1 };
d["count"] += 1;    // d["count"] is now 2

Nota: per la concatenazione di stringhe, += accoda alla stringa:

var str = "Hello";
str += " World";    // str is now "Hello World"

Nota: per la ripetizione di stringhe, *= ripete la stringa:

var str = "x";
str *= 5;           // str is now "xxxxx"

7.9 Espressione condizionale ternaria

L'operatore ternario (? :) offre un modo conciso per scrivere espressioni condizionali. Valuta una condizione e restituisce uno di due valori a seconda che la condizione sia vera o falsa.

Sintassi

condition ? valueIfTrue : valueIfFalse

Comportamento

Esempi

var age = 18;
var status = age >= 18 ? "adult" : "minor";
print(status);  // Output: "adult"

var score = 85;
var grade = score >= 90 ? "A" : (score >= 80 ? "B" : "C");
print(grade);  // Output: "B"

var max = a > b ? a : b;  // Find maximum of two values

var unit = "fahrenheit";
var symbol = unit == "fahrenheit" ? "F" : "C";
print(symbol);  // Output: "F"

Espressioni ternarie nidificate

Le espressioni ternarie si possono nidificare, ma per la leggibilità, per condizioni complesse conviene usare istruzioni if-else:

// Nested ternary (works but can be hard to read)
var result = x > 0 ? (y > 0 ? "both positive" : "x positive") : "x not positive";

// Equivalent if-else (more readable)
var result;
if (x > 0) {
    if (y > 0) {
        result = "both positive";
    } else {
        result = "x positive";
    }
} else {
    result = "x not positive";
}

Precedenza degli operatori

L'operatore ternario ha precedenza inferiore agli operatori logici (and, or) ma superiore all'assegnamento. Usa le parentesi quando lo mescoli con altri operatori:

var result = (x > 0 and y > 0) ? "both positive" : "at least one non-positive";
var value = condition ? a + b : c - d;  // Parentheses not needed here

7.10 Letterali dizionario e indicizzazione

I dizionari usano la sintassi letterale dict { ... } e si indicizzano con parentesi quadre, come gli array ma con chiavi stringa.

Sintassi dei letterali dizionario

var d = dict { "a": 1, "b": 2 };
print(d["a"]);      // 1
print(d["b"]);      // 2

// Add or update entries
d["c"] = 3;
print(d["c"]);      // 3

// Missing keys return null
print(d["missing"] == null);  // true

Metodi dei dizionari nelle espressioni

var d = dict { "x": 10 };
print(d.get("x"));           // 10
print(d.containsKey("x"));   // true
d.set("y", 20);
print(d["y"]);               // 20

var keys = d.keys();
var values = d.values();

7.11 Comprehension di liste e dizionari

Una comprehension costruisce una collezione a partire da un iterabile esistente, in una sola espressione, con un filtro opzionale.

Comprehension di liste

La sintassi è [ resultExpression for variable in iterable ], opzionalmente seguita da if condition:

var numbers = [1, 2, 3, 4, 5];

var squares = [n * n for n in numbers];
print(squares);        // [1, 4, 9, 16, 25]

var evens = [n for n in numbers if n % 2 == 0];
print(evens);          // [2, 4]

var labels = ["item-" + n for n in numbers if n > 3];
print(labels);         // ["item-4", "item-5"]

La variabile di ciclo ha scope limitato alla comprehension e non fuoriesce nello scope circostante. Il filtro, se presente, viene valutato prima dell'espressione risultato, quindi gli elementi che non soddisfano la condizione non la raggiungono mai.

Comprehension di dizionari

La forma dizionario usa dict { keyExpression: valueExpression for variable in iterable }, di nuovo con un filtro if opzionale:

var words = ["alpha", "beta", "gamma"];

var lengths = dict { w: length(w) for w in words };
print(lengths["alpha"]);   // 5

var longOnly = dict { w: length(w) for w in words if length(w) > 4 };
print(longOnly.containsKey("beta"));   // false

Le chiavi devono valutarsi a stringhe, come in qualsiasi dizionario. Quando due iterazioni producono la stessa chiave, vince l'ultima.

Le comprehension sono supportate dall'interprete, dal transpiler C# e dal transpiler JavaScript.

7.12 Accesso null-conditional

?. e ?[ eseguono un accesso a membro o a indice solo quando il ricevente non è null. Quando è null, l'accesso si valuta a null invece di sollevare Accessing members of null:

var user = null;

print(user?.name);        // null
print(user?["name"]);     // null

// Without the operator this is a runtime error:
// print(user.name);

Ogni operatore protegge il proprio ricevente, quindi una catena richiede l'operatore a ogni passo che può essere null:

var config = dict { "db": null };

print(config["db"]?.host);       // null: the ?. guards the null value
// print(config["db"].host);     // runtime error

var response = dict { "items": null };
print(response["items"]?[0]);    // null

L'accesso null-conditional legge un valore; non è una forma di assegnamento. Usalo per attraversare dati opzionali come JSON parsato, lookup su dizionario che possono fallire e campi di oggetti popolati solo in alcuni percorsi di codice.

7.12.1 Null-coalescing ??

a ?? b si valuta a a quando a non è null; altrimenti valuta b. A differenza di or/||, solo null attiva il lato destro: 0, false e "" vengono conservati:

print(0 ?? 1);
print(false ?? true);
print("hi" ?? "x");
print(null ?? "fallback");

Combinalo con l'accesso null-conditional per catene opzionali:

var user = null;
print(user?.name ?? "guest");
print("|" + str.trimText(user?.name) + "|");

La precedenza sta tra l'OR logico e il condizionale ternario; ?? è associativo a destra.

7.13 Precedenza degli operatori

Gli operatori vengono valutati in ordine di precedenza. Vedi l'Appendice B per la tabella completa di precedenza, allineata al parser di riferimento.

7.14 Espressioni async e await

MALDA supporta operazioni asincrone first-class tramite le espressioni async e await. Permettono di lavorare con operazioni asincrone senza bloccare l'esecuzione.

Espressione async

La keyword async crea un valore Task da un'espressione. Applicata a una chiamata di funzione, avvia l'operazione asincrona e restituisce subito un Task che rappresenta l'operazione in corso:

var task = async sleep(100);  // Start sleep, get Task immediately
var task2 = async fetchData(url);  // Start async function call

Applicata a un'espressione che non è una chiamata, avvolge il valore in un Task già completato:

var task = async 42;  // Creates a Task that immediately completes with 42

Espressione await

La keyword await attende il completamento di un Task e ne restituisce il risultato:

var task = async sleep(100);
await task;  // Wait for sleep to complete

var dataTask = async fetchData(url);
var data = await dataTask;  // Wait and get the result

Importante: await si può usare solo con valori Task. Tentare di fare await di un valore che non è un Task solleva un errore a runtime.

Esempio completo

// Start an async operation
var delayTask = async sleep(50);

// Do other work here if needed...

// Wait for the operation to complete
await delayTask;
print("Done!");

// Chain async operations
var result = await async someAsyncFunction(arg1, arg2);

Casi d'uso

Concorrenza strutturata con all()

La funzione built-in all() compone più Task in un unico Task che si completa quando tutti i figli sono terminati.

Uso di base

// Variadic form: pass tasks as separate arguments
var t1 = async fetchData(url1);
var t2 = async fetchData(url2);
var allTask = all(t1, t2);
var results = await allTask;   // results is an array [data1, data2]

// Array form: pass an array of tasks
var tasks = [async sleep(10), async sleep(20)];
var allTask2 = all(tasks);
await allTask2;

Semantica

Esempio: lavoro in parallelo e poi aggregazione

function computeA() {
    sleep(10);
    return 1;
}

function computeB() {
    sleep(20);
    return 2;
}

var tA = async computeA();
var tB = async computeB();

var allTask = all(tA, tB);
var results = await allTask;      // [1, 2]
var sum = results[0] + results[1];
print(sum);                       // 3

Funzioni utente avviate a caldo in sovrapposizione che chiamano sleep (o in altro modo attendono) prima del successivo binding var tengono locali, this e stack defer isolati. L'ambiente del chiamante viene ripristinato dopo lo scheduling di ciascun task, quindi var tA = async computeA(); var tB = async computeB(); fa il bind sul chiamante. Il built-in async sleep(...) si comporta allo stesso modo. Vedi Examples/Basics/async_all_example.malda.

Nota: all() è un blocco compositivo: non avvia i task da solo, combina solo i task che crei tu (per esempio con async).

7.15 Operatore pipe (|>)

L'operatore pipe passa il valore a sinistra come primo argomento al callable a destra. Consente un flusso di dati da sinistra a destra per pipeline AI, prompt e funzioni built-in.

// Prompt: left value becomes the first parameter
prompt greet(name) {
    user: "Hello, {name}!"
}
var p = "Alice" |> greet;

// Built-in: parse JSON after an LLM response string
schema Answer { text: string; sources: string[]; }
var parsed = llmOutput |> parseJson("Answer");

// Retriever chain
var context = question |> retriever.get |> formatRetrievedDocs;

// Lambda adapter in the middle of a chain
var p = question
    |> retriever.get
    |> formatRetrievedDocs
    |> (ctx) => answerPrompt(question, ctx);

Regole:

Vedi 13. Funzioni built-in (helper per pipeline AI) e 15. VectorDB (asRetriever) per esempi RAG. Walkthrough completo: Examples/Prompts/rag_pipeline.malda.

Composizione avanzata: usa composePipe(f, g, …) per costruire una funzione sequenza riutilizzabile; parallelRun(input, { branch: fn, … }) per il fan-out in parallelo; mergeRetrievedDocs per combinare i hit di retrieval paralleli prima di formatRetrievedDocs. Esempio: Examples/Prompts/rag_compose_pipeline.malda.

Per nominare e riutilizzare una pipeline, avvolgi l'espressione pipe in una function ordinaria e fai return del risultato. Vedi Examples/Prompts/rag_function_pipeline.malda e 10. Prompt.

Vedi anche