🦊 Lynx Language
Lynx is a declarative modeling language for defining and solving combinatorial optimization problems using Integer Linear Programming (ILP). It allows users to define types, relationships, properties, and optimization goals in a concise and expressive syntax designed specifically for logic-based, constraint-driven models.
✨ Key Features
- Declarative syntax — no loops, no side effects
- Strong typing with support for primitive and composite types
- Expressive constraint and logic composition using built-ins like
All,Any,AtLeast,Not, etc. - Full support for
find,match,sum,solve, and other built-in functions - ILP-compatible: all model logic must compile to a linear formulation
📦 Example
// Demonstrates use of match to compute a value based on logic
type Hammer: bool {
material: string,
size: int,
cost: float
}
type Toolbox: All {
hammers: Any[Hammer],
weight: int = (t: Toolbox) -> match(t, {
Any(find((h: Hammer) -> h.material == "steel")): 10,
Any(find((h: Hammer) -> h.material == "wood")): 7,
_: 5
})
}
type Size: int
type Hammer: bool {
material: string,
size: Size,
cost: float
}
type Nail: bool {
length: int,
amount: int,
cost: float
}
type Toolbox: All {
hammers: AtLeast<1>[Hammer] = (_) -> find((h: Hammer) -> h.size >= 8),
nails: Any[Nail]?,
boxType: BoxType
}
type Carpenter: All {
name: string,
age: int,
workable: bool = (c: Carpenter) -> c.age >= 18,
toolbox: Toolbox,
salery: float = (c: Carpenter) ->
+sum(c.toolbox.hammers.cost)
+sum(c.toolbox.nails, 0.0)
-20000.0
}
Hammer hammer1 { material: "steel", size: 10 }
Nail nail1 { length: 5, amount: 100 }
Carpenter john {
name: "John Doe",
age: 30,
workable: true,
toolbox: Toolbox {
hammers: AtLeast<1> { hammer1 },
nails: Any { nail1 },
boxType: BoxType { width: 30, height: 20, depth: 15 }
}
}
a_solution = solve(john, { nail1: 1.0 }, { Not { hammer1 } })
🧠 Language Concepts
🧱 Type Declarations
- Primitive types (e.g.,
bool,int,float) can have additional metadata. - Composite types use logical constructors like
All,Any,Exactly, etc. to express relationship constraints.
type Product: All {
options: Exactly<1>[Option],
price: float = (p: Product) -> sum(p.options.price)
}
🔗 Relationships
Relationships are declared with logical and cardinality operators:
All[T]– all connected instances must be satisfiedAny[T]– at least one must be satisfiedExactly<N>[T]– exactly N instances must be selected- Optional relationships use
?after the type (e.g.Any[Nail]?)
🪮 Computed Properties
Properties can be computed using pure, side-effect-free lambda expressions:
cost: float = (t: Toolbox) -> sum(t.hammers.cost)
🔍 Find & Filter
Use find((x: Type) -> condition) to dynamically match instances:
find((n: Nail) -> n.length >= 10)
🧲 Match & Logic
Use match for piecewise logic:
weight: int = match(t, {
Any(find((h: Hammer) -> h.material == "steel")): 10,
Any(find((h: Hammer) -> h.material == "wood")): 7,
_: 5
})
🚀 Solving
The solve function initiates optimization over a variable with:
- a weighted objective
- a set of constraints
solve(john, { nail1: 1.0 }, { Not { hammer1 } })
📜 Design Constraints
Lynx compiles to Integer Linear Programs, which means:
- ❌ No loops
- ❌ No recursion
- ❌ No nonlinear math (e.g.
x * y,x / y) - ✅ All functions must return linear-compatible results
- ✅ All expressions are deterministic and pure
🔧 Built-in Functions
| Function | Description |
|---|---|
solve(...) |
Optimize an instance with objective and constraints |
find(...) |
Select instances based on filter |
sum(...) |
Add numeric values over a collection |
propagate(...) |
Forward-evaluate logical implications |
match(...) |
Piecewise logic mapping |
first(...) |
Get the first element from a list |
📃 File Format
Lynx files use the .lynx extension. The parser supports:
- Single-line comments:
// comment - Multi-line comments:
/* block comment */
📚 Learn More
💠 Roadmap
- Static linearity checker
- VSCode syntax extension
- Type and shape inference
- Partial evaluation and result caching