MALDA™ Reference Manual

The AI-First Programming Language - Version 1.0.11

4. Data Types

MALDA is a dynamically-typed language with support for primitive types, arrays, and objects. Variables can hold values of any type, and types are inferred from values.

4.1 Primitive Types

MALDA supports four primitive data types:

Integer (int)

32-bit signed integers. Examples:

42
-10
0
12345

Float (float)

64-bit floating-point numbers. Examples:

3.14
-0.5
2.0
1e-5

String (string)

Sequences of characters enclosed in double quotes ("...") or single quotes ('...'). Both forms are equivalent. Examples:

"Hello, World!"
'Hello, World!'
"Line 1\nLine 2"
'Line 1\nLine 2'
"Tab\tseparated"
"It's a test"        // Using double quotes when string contains single quote
'He said "hello"'    // Using single quotes when string contains double quote

Boolean (bool)

Logical values: true or false.

4.2 Type System

Dynamic Typing

Variables in MALDA are dynamically typed - they can hold values of any type, and the type is determined at runtime:

var x = 10;        // x is an integer
x = "Hello";      // x is now a string
x = 3.14;         // x is now a float
x = true;         // x is now a boolean

Type Inference

Variable types are automatically inferred from their initial values:

var name = "Alice";     // Inferred as string
var age = 25;          // Inferred as integer
var pi = 3.14159;     // Inferred as float
var isActive = true;   // Inferred as boolean

Type Coercion

MALDA automatically converts between compatible types in certain contexts:

Integer ↔ Float Conversion

Integers are automatically converted to floats when needed:

var x = 10;        // integer
var y = 3.14;     // float
var sum = x + y;   // x is converted to float, result is 13.14

String Concatenation

When using the + operator with strings, non-string values are converted to strings:

var name = "Alice";
var age = 25;
var message = "Name: " + name + ", Age: " + age;
// message = "Name: Alice, Age: 25"

Explicit Type Conversion

Use built-in functions for explicit type conversion (see Built-in Functions):

var num = int("123");      // Convert string to integer
var floatVal = float("3.14");  // Convert string to float
var str = string(42);     // Convert number to string

4.3 Object Types

Class Instances

Objects are instances of classes. They are created using the new keyword:

class Person {
    public var name;
    public var age;
    
    function Person(name, age) {
        this.name = name;
        this.age = age;
    }
}

var person = new Person("Alice", 25);  // person is an object

See Classes & Objects for more information.

Null

null is a special value representing the absence of an object. It can be assigned to any object variable:

var obj = null;

if (obj == null) {
    print("Object is null");
}
Warning: Accessing members of null causes a runtime error.

Reference Types

Objects are reference types - when you assign an object to a variable, both variables refer to the same object:

var person1 = new Person("Alice", 25);
var person2 = person1;  // person2 refers to the same object
person2.name = "Bob";
print(person1.name);    // Prints "Bob" (same object)

4.4 Dictionaries

Dictionaries are mutable key–value maps with string keys and values of any type. They are useful for configuration objects, dynamic maps, and aggregating data.

Dictionary Literals

Dictionaries are created with the dict { ... } literal syntax:

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

Dictionary Methods

Dictionaries are objects with built-in methods:

var d = dict { "x": 10 };

// get(key) – returns value or null
print(d.get("x"));        // 10
print(d.get("missing"));  // null

// set(key, value) – sets and returns the same dictionary
d.set("y", 20);
print(d["y"]);            // 20

// remove(key) – removes entry and returns true/false
print(d.remove("y"));     // true

// containsKey(key) – membership test
print(d.containsKey("x"));   // true

// keys() – array of string keys
var keys = d.keys();
print(keys.length);

// values() – array of values
var values = d.values();
print(values.length);

// entries() – array of [key, value] pairs
var entries = d.entries();
var first = entries[0];   // [key, value]
print(first[0]);          // key
print(first[1]);          // value

In transpiled executables, dictionary literals are backed by Dictionary<string, object?> but expose the same behavior as in the interpreter.

4.5 Sum Types (Tagged Unions)

Sum types let you define a value that can be one of several variants, each carrying optional payload data. They are useful for structured results (e.g. success vs error) and protocol-style data.

Type Declaration

Declare a sum type with type, a type name, and one or more constructors separated by |. Each constructor has a name and optional parameters. Parameters may be name-only or carry a payload type (the same SchemaType form as schema fields: primitives, [], ?, schema or sum-type names):

type Result = Ok(value) | Err(message);
type Intent = Search(query: string) | Buy(sku: string, qty: int) | Help();

Name-only constructors stay valid (Search(query)) and remain untyped in the generated JSON Schema. Mixing typed and untyped arms in one type is allowed. Payload types are not prompt-parameter typing — write prompt greet(name), never prompt greet(name: string) — see 10. Prompts.

Constructor names (e.g. Ok, Err) become global functions that build values of that variant. The same name cannot also be a schema. validate("Result", taggedDict) checks the JSON wire shape ({"tag":"Ok","value":…}) without turning the dict into a variant — see validate(). When a payload is typed, validate and typed prompts reject JSON that does not match (for example qty: "x" against qty: int).

Constructing Values

Call a constructor like a function. It returns a tagged value carrying the arguments as payload:

var r = Ok(42);           // variant Ok with payload 42
var e = Err("failed");    // variant Err with payload "failed"
var n = None();           // variant None with no payload

Matching on Variants

Use match with variant patterns: ConstructorName(binding, ...). The pattern matches if the value is that variant and binds the payload to the given names. A bare constructor name (case None:) matches that variant without binding payloads (implicit _ per slot).

type Result = Ok(value) | Err(message);
function divide(a, b) {
    if (b == 0) return Err("divide by zero");
    return Ok(a / b);
}
var r = divide(10, 2);
var result = match r {
    case Ok(v): "ok: " + v;
    case Err(msg): "error: " + msg;
};
print(result);   // "ok: 5"

See 8. Control Structures for the full match syntax. The stdlib modules result and option wrap the same Ok/Err and Some/None tags without a type declaration — see 13.19.1.

4.6 Type Checking

While MALDA is dynamically typed, you can inspect types at runtime with built-in helpers (see Built-in Functions):

var x = 10;

// Preferred: type tag from typeOf()
if (typeOf(x) == "int") {
    print("x is an integer");
}

// Any numeric value (integer or float)
if (isNumber(x)) {
    print("x is a number");
}

// Check for null
if (x == null) {
    print("x is null");
}

typeOf() returns canonical kind tags such as "int", "float", "string", "bool", "array", "dict" (for dict { }), "object" (class instances), "variant" (sum-type values), "task", "function", "class", "actor", or "null". Constructor names (e.g. Ok) are not typeOf tags — use match. Use isTag(x, "integer") during migration if older literal names are still in use.

See Also