In a matching context the result of an evaluation expression is interpreted as a guard whose truthyness determines whether or not the matching process should continue.
company ~ {"employees": [*_, {"age": <<@ > 65>>}, *_]}
The '@' character in the example above refers to the current focus (the value of "age").
In a filling context the result of the evaluation expression is the value inserted into the structure
// // Do a bit of math // {"numerator": x, "denominator": y} --> {"quotient": <<x/y>>}
>> jertl.transform('{"numerator": x, "denominator": y} --> {"quotient": <<x/y>>}', x=1.0, y=2.0).result
{'quotient': 0.5}
// // Find employees eligible for graduation // company ~ {"employees": [*_, employee<<{"age": <<@ > 65>>}>>, *_]}
The @ character in the example above refers to the current focus.
// // Get list of employee names // {"employees": employees} --> <<map({"name": name} --> name, employees)>>
This will appear in the same release having Evaluation. The functions map, filter, and reduce, will be in the set of predefined functions.
The jertl mini-language is 'functional-first' so iteration will not be available.
Eventually.
- Semantic analysis to identify potential issues beforehand.
- Informative Exceptions (location in pattern where exception happened).
- Editor support for .jertl source files.
- Structure matching optimization.
- "Eat our own dogfood" in AST generator and OpCode emitter.
- Match debugger
>>> cat find_highest_paid_male_employee.jertl module highest_paid_male // // Create a Python module with functions is_male, salary, and highest_paid_male_employee // matcher is_male: {"gender": "male"} transform salary: {"salary": salary} --> salary collate highest_paid_male_employee [company]: \\ Input to this function is a list containing a `company` data structure company ~ {"employees": employees} highest_salary := <<max(map(age, filter(is_male, employees)))>> employees ~ [*_, employee<<{"salary": highest_salary}>>, *_] >>> jertl find_highest_paid_male_employee.jertl -o generated_sources
Where we consider multiple inference rules simultaneously and can require there to be sequences of inferences in order for the rule to apply.
// // Your classic ancestors problem // ruleset ancestors rule find_ancestors [person]: person ~ {"parents": [mother, father]} --> O=O=O ancestors [person, mother] // `O=O=O`: chain to ancestors ruleset O=O=O ancestors [person, father] rule note_ancestry_and_look_deeper [person, ancestor]: person ~ {"name": person_name}, ancestor ~ {"name": ancestor_name, "parents": [ancestors_mother, ancestors_father]} --> ancestry := {"person": person_name, "ancestor": ancestor_name} O=O=O ancestors [person, ancestors_mother] O=O=O ancestors [person, ancestors_father] rule no_more_birth_records [person, parent]: person ~ {"name": person_name}, ancestor ~ {"name": ancestor_name, "parents": null} --> ancestry := {"person": person_name, "ancestor": ancestor_name}
Where working memory is a key/value store.
rule supervises [supervisor, employee] supervisor ~ {"name": supervisor_name, "underlings": [\*_, employee, \*_]} employee@ ~ {"name": underling_name} // <-- `employee` is bound to string which points to data in working memory. // The data is retrieved and the matching process continued. --> supervises := [supervisor_name, underling_name]
Where data is external to Python.
rule is_supervisor [supervisor, employee] supervisor@sql_employee_table ~ {"name": supervisor_name, "underlings": [\*_, employee, \*_]} employee@sql_employee_table ~ {"name": underling_name} // employee is key to data stored in a SQL table --> supervises := [supervisor_name, underling_name]
Where we mutate a data structure using overlays. What is an overlay you ask? Good question! Overlays and how they work need to be formally defined.
rule record_change_of_supervisor [employee_id, previous_supervisor, new_supervisor] --> previous_supervisor :- {"underlings": [*_, employee_id, *_]} // <-- remove portion of data structure matching overlay new_supervisor :+ {"underlings": [*_, employee_id]} // <-- add data described by overlay