Predicates and Symbolic Functions#
EQL is highly extensible. You can define your own logic and integrate it into queries using Predicates for boolean checks and Symbolic Functions for transforming data.
Predicates#
A Predicate is a special class that represents a boolean condition.
When you call it with symbolic variables, it doesn’t execute immediately; instead, it returns an InstantiatedVariable
that becomes part of the query’s execution graph.
The HasType Predicate#
One of the most useful built-in predicates is HasType, which checks if a variable is an instance of a specific class.
from krrood.entity_query_language.predicate import HasType
# Filter 'v' to only include objects that are instances of 'Handle'
query = entity(v).where(HasType(v, ExampleHandle))
Hint
variable(Type, domain=...) already includes an implicit HasType check. Use the predicate explicitly when
you need to check the type of a Attribute for example.
Symbolic Functions#
A Symbolic Function is a regular Python function decorated with @symbolic_function. When called with symbolic
arguments, it defers execution until the query is evaluated.
from krrood.entity_query_language.predicate import symbolic_function
@symbolic_function
def is_even(n: int) -> bool:
return n % 2 == 0
# Use it in a query
query = entity(r).where(is_even(r.battery))
Note
EQL provides a built-in length() symbolic function for
checking the size of collections.
Full Example: Custom Logic#
Let’s define a custom predicate and a symbolic function to find robots with specific capabilities.
from dataclasses import dataclass
from krrood.entity_query_language.factories import variable, entity, an, Symbol
from krrood.entity_query_language.predicate import symbolic_function, Predicate
@dataclass
class ExampleRobot(Symbol):
name: str
load: float
@symbolic_function
def calculate_stress(load: float) -> float:
return load * 1.5
@dataclass(eq=False)
class ExampleIsOverloaded(Predicate):
robot: ExampleRobot
limit: float = 10.0
def __call__(self) -> bool:
# This is where the actual logic happens during evaluation
return calculate_stress(self.robot.load) > self.limit
# Data
robots = [ExampleRobot("Heavy", 8.0), ExampleRobot("Light", 2.0)]
r = variable(ExampleRobot, domain=robots)
# Query using custom logic
query = an(entity(r).where(ExampleIsOverloaded(r)))
for robot in query.evaluate():
print(f"Overloaded Robot: {robot.name} (Load: {robot.load})")
Overloaded Robot: Heavy (Load: 8.0)