Fix docs wording and cleanup typos. Amend content
This commit is contained in:
parent
e88003f95e
commit
9cd2d67056
|
|
@ -33,7 +33,7 @@ _(the production from `ConvertsTo` query)_
|
|||

|
||||
_(the production from `Typecheck` query)_
|
||||
|
||||
Typechecking itself starts with activating of `checkAll()` constraint, triggering the productions responsible for assigning types to literals, `this` expression, as well as processing type annotations — ensuring that these are built without violating bounds.
|
||||
Typechecking itself starts with activating the `checkAll()` constraint, triggering the productions responsible for assigning types to literals, `this` expression, as well as processing type annotations — ensuring that these are built without violating bounds.
|
||||
|
||||
The rule for variable declaration is quite trivial: the type annotation gets expanded to dataform and assigned to the source location with `typeOf()` constraint.
|
||||
|
||||
|
|
|
|||
|
|
@ -6,39 +6,41 @@ weight: 10
|
|||
|
||||
# Introduction
|
||||
|
||||
This project is an attempt to bring logic programming to JetBrains MPS[^mps] in order to facilitate tasks related to source mdoel analysis, and which require logical inference of some kind to operate. Examples include type checking and control flow.
|
||||
This project is an attempt to bring logic programming to JetBrains MPS[^mps] in order to facilitate tasks related to source model analysis, and which require logical inference of some kind to operate. Examples include type checking and control flow.
|
||||
|
||||
Type system in MPS is traditionally defined with help of type checking rules, in particular inference rules, which allow to make logical statements about types, such as «*is a*» or «*is a subtype of*», enabling the internal engine to infer the specific type, deriving it from a collection of such statements, which are referred to as type equations and inequalities.
|
||||
Type system in MPS is traditionally defined with help of type checking rules, in particular inference rules, which allow for making logical statements about types, such as «*is a*» or «*is a subtype of*», enabling the internal engine to infer the specific type, deriving it from a collection of such statements, which are referred to as type equations and inequalities.
|
||||
|
||||

|
||||
_(statement in `j.m.lang.typesystem` language)_
|
||||
|
||||
Albeit brief and concise, this notation leaves many questions unanswered when it comes to how exactly the system of equations and inequalities is processed. In other words, type inference is — for the most part — left up to the internal engine to decide. This limits the options for the author of type system to control how exactly subtyping is defined, and what happens with type parameters when computing sub- or supertype. Java, for instance, has several kinds of «conversions» with clearly defined rules controlling how types are transformed and what types are compatible in certain situations. All of this has to be emulated with «strong» and «weak» subtyping in MPS.
|
||||
|
||||
Another example is all too well known *when_concrete*, which is basically a suspended block that gets executed when the type, that serves as its parameter, is computed. Sometimes this never happens during type inference, which results in numerous «*when concrete is never concrete*» warnings, leaving the user wondering what went wrong. This, however, is very much a necessary evil, since there are no other possibilities to hook into the engine in order to spy on types, and knowing the exact form of a type is sometimes required for further inference.
|
||||
Another example is the all too well known *when_concrete*, which is basically a suspended block that gets executed when the type, that serves as its parameter, is computed. Sometimes this never happens during type inference, which results in numerous «*when concrete is never concrete*» warnings, leaving the user wondering what went wrong. This, however, is very much a necessary evil, since there are no other possibilities to hook into the engine in order to spy on types, and knowing the exact form of a type is sometimes required for further inference.
|
||||
|
||||
Consider how the type of a method call is calculated: the details aside, in essence *when_concrete* has to be applied to the type of each argument. Then we should either turn to inequalities and rely on the inference engine, or analyse the type structure and run closing computation when the *last* unknown type is finalised.
|
||||
Consider how type of a method call is calculated: details aside, in essence *when_concrete* has to be applied to types of each argument. Then we should either turn to inequalities and rely on the inference engine, or analyse the type structure and run closing computation when the *last* unknown type is finalised.
|
||||
|
||||

|
||||
_(example of processing method arguments)_
|
||||
|
||||
Code rules may have a solution to these and other issues. The core idea is to employ a **production system** to process facts and relations, collectively known as constraints, with the user being in full control of what productions to generate for given source code (model). With **logical variables** representing unknowns, and with support for **pattern matching** making use of term algebra and unification, it is pretty straightforward to define the core of type inference or similar framework without having to rely on opaque implementation and pre-defined relations.
|
||||
Code rules may have a solution to these and other issues. The core idea is to employ a **production system** to process facts and relations, collectively known as constraints, with the user being in full control of what productions to generate for given source code (model). With **logical variables** representing unknowns, and with support for **pattern matching** making use of term algebra and unification, it is pretty straightforward to define the core of type inference or a similar framework without having to rely on opaque implementation and pre-defined relations.
|
||||
|
||||
Typechecking assignment expression could look like the following. What code says, is basically once left and right sides of the assignment have types (not necessarily ground types), test if left side’s type converts to right side’s. Constraint `convertsTo/2` is defined in the same aspect, and the typesystem author has full control how it deals with type parameters, for example.
|
||||
To illustrate the idea, let’s look at a couple of examples. These are taken from the type system implementation for BaseLanguage.
|
||||
|
||||
Typechecking assignment expression could look like the following. What the code says is basically: once left and right sides of the assignment have types (not necessarily ground types), test if left side’s type converts to right side’s. Constraint `convertsTo/2` is defined in the same aspect, and the typesystem author has full control how it deals with type parameters, for example.
|
||||
|
||||

|
||||
_(typechecking assignment with constraint production)_
|
||||
|
||||
Instead of waiting until a type has been finalised in order to access its structure, *pattern matching* is used to trigger productions only on types of certain form. The example below shows how `convertsTo/2` is resolved for an instance of `LowerBoundType`, which requires contravariance.
|
||||
Instead of waiting until a type has been finalised in order to access its structure, *pattern matching* is used to trigger productions only on types of certain form. The example below shows how `convertsTo/2` is resolved for an instance of `LowerBoundType`.
|
||||
|
||||

|
||||
_(resolution of `convertsTo/2` constraint using pattern matching)_
|
||||
|
||||
The following is the list of features made available to users of MPS with *code rules* plugin.
|
||||
|
||||
- A language `jetbrains.mps.lang.coderules` with concept definitions for building rules that serve as production templates, and as regular «checking» rules. The rules may be concept-specific or standalone, if there is a need to provide constraints that are invariant for every invocation.
|
||||
- A language for building rules that serve as production templates, and as regular «checking» rules. The rules may be concept-specific or standalone, if there is a need to provide constraints that are invariant for every invocation.
|
||||
|
||||
- *Constraint Processing System* — an extension of CHR[^chr] semantics allowing the use of unification in production’s head with automatic binding of logical variables on successful match. This extension also supports alternative body branches, as well as calling arbitrary Java code. Representing the inference in the form of logical productions helps achieve extensibility, as productions defined by extension languages can be easily blended in.
|
||||
- *Constraint Processing System* — an extension of CHR[^chr] semantics allowing the use of unification in production’s head with automatic binding of logical variables on successful match. This extension also supports alternative body branches, as well as calling arbitrary Java code. Representing type inference in the form of logical productions helps achieve extensibility, as productions defined by extension languages can be easily blended in.
|
||||
|
||||
- A framework for executing a constraints program, with support for error reporting from production’s body, as well as a façade interface for accessing the results of a constraints program execution. The UI also includes a tracing tool for providing insights into how constraints are processed.
|
||||
|
||||
|
|
|
|||
|
|
@ -46,39 +46,37 @@ Keyword `extends` allows to specify another handler that is to be extended. All
|
|||
|
||||
Only declared constraints are allowed to be used in the heads of productions in rules of this handler or its extensions. Handler should declare one or more constraints, unless it only uses the constraints from handlers which it extends. Constraint declaration consists of name and *arity*, which together constitute a *constraint symbol*. Constraint’s arity is fixed.
|
||||
|
||||
Rules can specify applicable concept, either exactly or including its subconcepts. One can also declare *standalone* rules, which are applied automatically on every evaluation of code rules.
|
||||
An applicability condition, if specified, is checked before applying the rule.
|
||||
|
||||

|
||||
_(example of rule matching concept instances with condition block)_
|
||||
|
||||
An applicability condition, if specified, is checked before applying the rule.
|
||||
Rules can specify applicable concept, either exactly or including its subconcepts. One can also declare *standalone* rules, which are applied automatically on every evaluation of code rules.
|
||||
|
||||

|
||||
_(example of standalone rule without input)_
|
||||
|
||||
Contents of a rule is a block of code that gets executed when the rule is applied to the source model. If the input is specified, the declared parameter is available in rule’s body.
|
||||
Rule’s contents is a block of code that gets executed when the rule is applied to the source model. If the input is specified, the declared parameter is available in rule’s body.
|
||||
|
||||
Rules may affect the scope of model locations that are processed during an evaluation session. Suppose a production responsible for typechecking a particular location in source model relies on presence of productions that are only available if some other location is processed as well. To guarantee that a certain model location is processed during the evaluation session, one uses `require` statement.
|
||||
|
||||

|
||||
_(example of using `require` statement)_
|
||||
|
||||
#### Constraint productions
|
||||
#### Productions
|
||||
|
||||
*Constraint productions* can be created at any place within rule body. A production has three main parts called *head*, *guard*, and *body*. Head defines what constraint trigger this production, and it can only contain constraints defined by the handler or one of the handlers it extends. Body can contain any visible constraints, as well as *predicates*. A production must include either the body or the head, no production can omit both. Guard is optional, and it can only contain predicates.
|
||||
*Productions* can be created at any place within rule body. A production has three main parts called *head*, *guard*, and *body*. Head defines what constraints trigger this production, and it can only contain constraints defined by the handler or one of the handlers it extends. Body can contain any visible constraints, as well as *predicates*. A production must include either the body or the head, no production can omit both. Guard is optional, and it can only contain predicates.
|
||||
|
||||
Both head and body can declare *logical variables*. By default a logical variable ranges over *terms*, although any POJO may be serve as a value.
|
||||
|
||||

|
||||
_(constraint production declaring logical variables)_
|
||||
_(production declaring logical variables)_
|
||||
|
||||
A production with an empty head, not declaring any constraints to serve as its input, is considered an *automatic* production and is triggered automatically on start of constraints program execution.
|
||||
|
||||
In a production’s head constraints can be declared as either *kept* or *replaced*. Briefly put, the constraints that are *kept* are left alone after a production is fired, whereas the *replaced* ones are discarded after the production’s head has been matched. Replaced constraints are marked with tilde `~`.
|
||||
In a production’s head constraints can be declared as either *kept* or *replaced*. Briefly put, the constraints that are *kept* are left alone after a production is fired, whereas the *replaced* ones are discarded after the production’s head has been matched. Replaced constraints are marked with a tilde `~`.
|
||||
|
||||
A production is triggered when there are constraint occurrences matching all constraints specified in production’s head. These occurrences include the active constraint, plus any additional matching constraints that are currently alive, filling the other vacant slots.
|
||||
|
||||
Productions to match a particular constraint are selected from a handler that has declares this constraint, and handlers that are its extensions. The order of productions within the handler matters, the ones declared on top are matched first. Productions from extending handlers are prepended to the list of productions of the handler they extend.
|
||||
Productions to match a particular constraint are selected from a handler that declares this constraint, and handlers that are its extensions. The order of productions within the handler matters, the ones declared on top are matched first. Productions from extending handlers are prepended to the list of productions of the handler they extend.
|
||||
|
||||
When a constraint with logical variable as one of its arguments is matched, that variable becomes bound to the corresponding argument of the matching occurrence. The scope of such binding is this production’s guard and body.
|
||||
|
||||
|
|
@ -94,7 +92,7 @@ _(example of constraint argument referring to a model location)_
|
|||
|
||||
Whereas the production’s head can only contain constraints, there are also other logical clauses that are used in guard and body.
|
||||
|
||||
A *predicate* is a built-in construct that serves two purposes: when used in guard, it serves to query if the condition is satisfied, and when invoked from body, it asserts the condition. An example is `unifies` predicate, displayed as `=`, which tests that its arguments can be unified — if used in guard, and invokes the actual unification if called from body. There are also predicates that can only be queries, such as `isFree/1` or `isBound/1`, testing if a logical variable is free or has value assigned.
|
||||
A *predicate* is a built-in construct that serves two purposes: when used in guard, it serves to query if the condition is satisfied, and when invoked from body, it asserts the condition. An example is `unifies` predicate, displayed as `=`, which, if used in guard, only tests that its arguments can be unified, otherwise if called from body, it invokes the actual unification. There are also predicates that can only be queries, such as `isFree/1` or `isBound/1`, testing if a logical variable is free or has value assigned.
|
||||
|
||||
Production’s guard can only contain predicates, and the body can contain both constraints and predicates.
|
||||
|
||||
|
|
@ -106,10 +104,11 @@ Arbitrary Java code can be called with `eval/1` predicate. It accepts either an
|
|||

|
||||
_(example of using `eval/1` predicate)_
|
||||
|
||||
In order to make use of macro definitions, which are essentially parameterised production templates extracted to a separate root, one of two pseudo predicates can be used: `expand` and `call`. The former accepts a node, whereas the second expects the argument that are substituted as macro parameters.
|
||||
In order to make use of macro definitions, which are essentially parameterised production templates extracted to a separate root, one of two pseudo predicates can be used: `expand` and `call`. The former accepts a node, whereas the second expects the argument that are substituted as macro parameters.
|
||||
|
||||
***Examples of expand/call pseudo predicates***
|
||||
|
||||
***Substitution — passing parameters via context***
|
||||
|
||||
***Template fragments within production template body***
|
||||
|
||||
|
|
|
|||
|
|
@ -10,16 +10,16 @@ Analysis of source code or model with *code rules* can be described as a two-pha
|
|||
|
||||
In the first phase, languages used by the model being analysed and their prototypes (meaning the languages that are being *extended*, but not used directly) are surveyed for the appropriate *code rules* aspect model, which is `types` in case of type checking, for example. Contents of this aspect model is a contribution of this particular language. All rules within this model are applied to the source model, with rules coming from extension languages having higher priority.
|
||||
|
||||
The outcome of this phase is a *constraints program*, which is a collection of *handlers*, which in turn represent lists of *productions*. This «program» however, exists in memory only, it does not have any textual representation. Aside from generating productions, the rules can also process the model normally, reporting errors as usual.
|
||||
The outcome of this phase is a *constraints program*, which is a collection of *handlers*, which in turn represent lists of *productions*. This «program» however, exists in memory only as it does not have any textual representation. Aside from generating productions, the rules can also process the model normally, reporting errors as usual.
|
||||
|
||||
The first phase runs in «read action», blocking potential writes, which means the editor may become unresponsive if a write action is requested. Ideally the rules should finish quickly and postpone all heavy load to the next phase, which can be run in the background, as the access to `SModel` is no longer necessary.
|
||||
The first phase runs in «read action», blocking potential writes, which means the editor may become unresponsive if a write action is requested. Ideally the rules should finish quickly and postpone all heavy load to the next phase, which can then be run in the background, as the access to `SModel` is no longer required.
|
||||
|
||||
In the second phase the constraints program that was created in phase one is executed. Execution starts with a query, which serves as an entry point to the program. For example, type system defines `TYPECHECK` and `CONVERT` queries, aimed at running type checking and testing if a type can be converted to another type, correspondingly. Queries are declared in the same aspect model.
|
||||
In the second phase the constraints program that was created in phase one is executed. Execution starts with a query, which serves as an entry point to the program. For example, type system defines `TYPECHECK` and `CONVERT` queries, intended for running type checking and testing if a type can be converted to another type, correspondingly. Queries are declared in the same aspect model.
|
||||
|
||||

|
||||
_(example of a query)_
|
||||
|
||||
In the above example, the query defines two logical variables (A and B) of type `term`, which serve to represent types internally. First, both query parameters `to` and `from` are *expanded*, meaning that their `SNode` representations are converted to terms, and then constraint `convertsTo(A, B)` is activated, kicking off processing of constraints, which is the essence of constraints program execution. In case productions triggered by `convertsTo/2` constraint all evaluate to true, the query is deemed successful, and if there is at least one production that evaluates to false, the query fails accordingly.
|
||||
In the above example, the query defines two logical variables (A and B) of type `term`, which serve to represent types internally. First, both query parameters `to` and `from` are *expanded*, meaning that their `SNode` representations are converted to terms, and then constraint `convertsTo(A, B)` is activated, kicking off the processing of constraints, which is the essence of constraints program execution. In case productions triggered by `convertsTo/2` constraint all evaluate to true, the query is deemed successful, and if there is at least one production that evaluates to false, the query fails accordingly.
|
||||
|
||||
One nice feature of using code rules is the ability to abstract away from type structure defined by the language. For example, one may decide to represent all primitive types of BaseLanguage as a term `primitive(kind=<specific kind>)`. Terms can also incorporate values as regular Java objects, so creating an inference rule which checks if a particular constant fits the given type, be it an `int` or a `char`, is trivial.
|
||||
|
||||
|
|
@ -28,7 +28,7 @@ Having an internal representation for types also means, that if type system is r
|
|||

|
||||
_(example of a query production)_
|
||||
|
||||
Here, the first constraint `checkAll/0` fires type checking, whereas the second `recoverAll/0` is responsible for restoring terms to `SNode` instances and assigning them to the source model locations. Joining the two constraints with a conjunction establishes the order in which these are evaluated.
|
||||
Here, the first constraint `checkAll/0` fires type checking, whereas the second `recoverAll/0` is responsible for restoring types to `SNode` instances and assigning them to the source model locations. Joining the two constraints with a conjunction establishes the order in which these are evaluated.
|
||||
|
||||
An of course, if something can go wrong, it will. In case type inference is unsuccessful, the second part of the query has no chance of being executed. To account for that, a feature was added to the language of constraint productions, which helps recover from certain failures.
|
||||
|
||||
|
|
@ -37,12 +37,12 @@ _(example of a query production with alternative body)_
|
|||
|
||||
Here, `recoverAll/0` constraint is moved out to an «*alternative branch*» of production body, which allows it to be activated even if there was an error while processing the main branch.
|
||||
|
||||
To illustrate how automatic binding of logical variables work, consider the following example. Constraint `typeOf/2` associates a type with a location in source model, and `convertsTo/2` ensures its 1st argument can be converted to the 2nd, which must both be types.
|
||||
To illustrate how automatic binding of logical variables work, consider the following example. Constraint `typeOf/2` associates a type with a location in source model, and constraint `convertsTo/2` ensures its 1st argument can be converted to the 2nd, which must both be types.
|
||||
|
||||

|
||||
_(example of rule with production)_
|
||||
|
||||
This production is triggered when both locations referred to by `ae.lValue` and `ae.rValue` have their types assigned, as both `typeOf/2` constraints must be present for a match to be successful. Once production’s head is matched, both logical variables `LType` and `RType` become bound to whatever was the 2nd argument of either occurrences of `typeOf/2` constraint.
|
||||
This production is triggered when both locations referred to by `ae.lValue` and `ae.rValue` have their types assigned, as both `typeOf/2` constraints must be present for a match to be successful. Once production’s head is matched, both logical variables `LType` and `RType` become bound to whatever was the 2nd argument of corresponding occurrences of `typeOf/2` constraint.
|
||||
|
||||
It’s important to note, that although on successful match both `lValue` and `rValue` have types, it’s not guaranteed that these types are *ground*. A type may be represented by a free logical variable, or a term containing free variables. Another very important thing to notice is that a logical variable enjoys full privileges of being an argument to a constraint. Which means, if in the above example both variables are free, and `LType = RType`, then both locations will have essentially the *same* type (in the sense of «same instance»), not just matching types.
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ weight: 50
|
|||
|
||||
This section briefly overlooks how constraint processing works. The system described here follows loosely the CHR specification, and has been in particular heavily influenced by JCHR[^jchr] implementation. The distinctive features are built-in support for logical variables, terms, and pattern matching. Alternative body branches are a deviation from the standard CHR.
|
||||
|
||||
## Terms and unification
|
||||
### Terms and unification
|
||||
|
||||
Constraints processing relies heavily on the use of *terms* as data type. Abstractly speaking, terms are functions of 0 or more arguments. Any opaque value captured by a term must be a POJO.
|
||||
|
||||
|
|
@ -34,7 +34,7 @@ A term variable ranges over terms. A substitution is a mapping of variables to t
|
|||
|
||||
Pattern matching is possible when variables are only used by one of the terms, which is then serves as pattern. To test if a pattern matches a given term can be implemented by a linear time algorithm, whereas full unification is slightly more complicated.
|
||||
|
||||
## Logical variables
|
||||
### Logical variables
|
||||
|
||||
Logical variables serve to identify an object that is to be determined in the future. They are *monotonic*, in the sense that once a variable is assigned a particular value, it stays assigned to that value. In addition, they implement a *union-find data structure*[^uf] a.k.a. «disjoint set». Any free logical variable can be assigned a class by setting its «parent» field to point to the class’s representative. All logical variables belonging to the same class are treated as exactly one variable. Logical variables notify observers when they become ground and when their *parent* (class representative) changes.
|
||||
|
||||
|
|
@ -69,7 +69,7 @@ A term variable can also be a logical variable, so that when two terms are unifi
|
|||
assertTrue(X.find().value() == term("h"))
|
||||
```
|
||||
|
||||
## Constraints and predicates
|
||||
### Constraints and predicates
|
||||
|
||||
Constraints are, simply put, tuples with fixed arity and a symbol attached. In some respects constraints correspond to rows in a database table. Logically they can be understood as facts, relations, or propositions. An argument to a constraint can be a term, a logical variable, or any POJO, except another constraint.
|
||||
|
||||
|
|
@ -82,13 +82,6 @@ A successfully fired production, which declares one or more of constraints in it
|
|||

|
||||
_(lifecycle of a constraint)_
|
||||
|
||||
To illustrate the idea of using *stored* constraints to fill vacant positions when matching a production, let’s consider an example — a production resolving `containedIn/2` constraint, which corresponds to *type parameter containment* relation of BaseLanguage types.
|
||||
|
||||

|
||||
_(example of a multi-head production)_
|
||||
|
||||
When `containedIn/2` constraint is activated, the production above matches, but two slots in its head must be filled in order for production to fire. The processor looks in the *store* for all *different* occurrences of constraint `hasBound/2` that could be matched, and substitutes them in these slots. The production will be fired for every matching triple of constraints.
|
||||
|
||||
#### Predicates
|
||||
|
||||
Whereas a constraint serves to embody a fact or a relation among objects simply by being a witness of such a fact or a relation, a *predicate*[^pred] helps to establish a fact or a relation, or check if one exists, by means of executing a procedure. Same is true for facts and propositions.
|
||||
|
|
@ -99,13 +92,17 @@ Predicates must implement ask/tell protocol. If a predicate is invoked from prod
|
|||
|
||||
***Example of ask/tell***
|
||||
|
||||
## Constraint production
|
||||
### Constraint productions
|
||||
|
||||
Constraints program is built from productions. Each production has three parts: the part that is responsible for triggering the production, called «head», the part that checks for pre-conditions, called «guard», and the part that is evaluated when production is fired, which is called «body».
|
||||
|
||||
#### Head
|
||||
|
||||
Head is a set of constraints which are all required to be alive in order for production to fire. This set is divided into «kept» part and «replaced» part, the latter containing constraints that are to be discarded as soon as the production fires.
|
||||
|
||||
There is some terminology inherited from CHR that can be useful when discussing the kinds of productions.
|
||||
A production is triggered when there are constraint occurrences matching all constraints specified in production’s head. These occurrences include the active constraint, plus any additional matching constraints that are currently alive, filling the other vacant slots.
|
||||
|
||||
There is some terminology inherited from CHR that can be useful when discussing the kinds of productions. In the following table `E` and `E'` are the set of constraints in production’s head, `C` is a conjunction of predicates serving as guard, and `G` is a conjunction of predicates and constraints in production’s body.
|
||||
|
||||
| «kept» set `E` | «replaced» set `E’` | Notation | Designation |
|
||||
|:--|:--|:--:|:--|
|
||||
|
|
@ -113,10 +110,25 @@ There is some terminology inherited from CHR that can be useful when discussing
|
|||
| non-empty | empty | `E => C | G` | Propagation |
|
||||
| non-empty | non-empty | `E \ E’ <=> C | G` | Simpagation |
|
||||
|
||||
Essentially, a production with only «kept» constraints in its head is a «propagation», the one with only «replaced» constraints is a «simplification», and the one that has both «kept» and «replaced» constraints is a combination of the two.
|
||||
|
||||
In addition, we define a fourth kind of production, an «auto» production with an empty head. As the name implies, such production is triggered automatically on start of constraints program execution.
|
||||
To illustrate the idea of using *stored* constraints to fill vacant positions when matching a production, let’s consider an example — a production resolving `containedIn/2` constraint, which corresponds to *type parameter containment* relation of BaseLanguage types.
|
||||
|
||||

|
||||
_(example of a multi-head production)_
|
||||
|
||||
When `containedIn/2` constraint is activated, the production above matches, but two slots in its head must be filled in order for production to fire. The processor looks in the *store* for all *different* occurrences of constraint `hasBound/2` that could be matched, and substitutes them in these slots. The production will be fired for every matching triple of constraints.
|
||||
|
||||
#### Guard
|
||||
|
||||
Guard is a conjunction of predicates, which are checked before a production is fired. Predicates in a guard are *queried*.
|
||||
|
||||
#### Body
|
||||
|
||||
Body is a conjunction of predicates and constraint activations. When triggered, each body clause is evaluated in order, with predicates serving as *assertions* and constraint activations producing new constraints. Each newly activated constraint is checked against any productions that can be fired, and so on.
|
||||
|
||||
|
||||
***Alternative body***
|
||||
|
||||
## Semantics of constraints program
|
||||
|
|
@ -138,7 +150,7 @@ Propagation is different from simplification in that the set of constraints $E$,
|
|||
(E^L \multimap E^L\otimes\exists\bar{y}G^L)) \\]
|
||||
|
||||
|
||||
[^jchr]: K.U.Leuven JCHR System https://dtai.cs.kuleuven.be/CHR/JCHR/
|
||||
[^uf]: https://en.wikipedia.org/wiki/Disjoint-set_data_structure
|
||||
[^jchr]: K.U.Leuven JCHR System [https://dtai.cs.kuleuven.be/CHR/JCHR/](https://dtai.cs.kuleuven.be/CHR/JCHR/)
|
||||
[^uf]: [https://en.wikipedia.org/wiki/Disjoint-set_data_structure](https://en.wikipedia.org/wiki/Disjoint-set_data_structure)
|
||||
[^pred]: a.k.a. «built-in constraints» in CHR literature
|
||||
[^lls]: Betz, H. and Frühwirth, T., 2005, October. A linear-logic semantics for constraint handling rules. In International Conference on Principles and Practice of Constraint Programming (pp. 137-151). Springer, Berlin, Heidelberg.
|
||||
|
|
|
|||
|
|
@ -8,3 +8,5 @@ layout: welcome
|
|||
Type inference and type checking is a problematic area for language authors — users of JetBrains MPS, because of inherent intricacies of the problem and sometimes inadequate support from the framework.
|
||||
|
||||
*Code Rules* is a new technology that brings logic programming in the form of constraints processing as a vehicle for implementing type inference.
|
||||
|
||||
Thanks to its flexibility and independence from any specifics of type checking, this technology can be applied in other areas of model analysis where logical inference can be useful.
|
||||
|
|
|
|||
Loading…
Reference in New Issue