From Dyna
Jump to: navigation, search

Warning: Not implemented yet. However, you can use queries in the current version.


What is a pattern?

A pattern is like a term except that some of its subterms may be variables. For example, in an inference rule like

pathto(V) min= pathto(U)+edge(U,V).

the consequent pathto(V) is an item pattern, and the antecedent is an expression pattern. When a Dyna program runs, it does a lot of pattern matching.

As an advanced feature, you can also specify patterns directly

  • to create subtypes (as usual, strong typing is both safer and more efficient)
  • to specify program transformations (in which the pattern is replaced by a specialized structure type, for efficiency)
  • to enable the C++ driver program to

Declaring patterns

You can declare patterns in a Dyna program as follows.

:- pattern(mypattern, constit(np,I,I)).
:- pattern(lengthtwo, edge(U,V)+edge(V,W)).
:- pattern(deep, foo(X,Y,yada(yada(yada(X))))).

Some patterns are automatically declared for you. The inference rule shown earlier, pathto(V) min= pathto(U)+edge(U,V), results in the following automatic declarations of antecedent and consequent patterns. These are used as the return types of the antecedent and consequent iterators. For example, if you ask for the antecedents of a particular pathto item, then you will get back an iterator over terms of type pathto::antecedent.

:- pattern('pathto::antecedent', pathto(U)+edge(U,V)).
:- pattern('pathto::consequent', pathto(V)).
:- pattern('edge::consequent', pathto(V)).
Only antecedent patterns are implemented so far.

Patterns as subtypes

Just as a union declaration creates a new supertype, a pattern declaration creates a new subtype. Thus, once mypattern is declared, you can use it like other types:

:- structure(foo(mypattern p)).
:- union(bar,[mypattern,string]).

so that foo(constit(np,5,5)) is a legal term but foo(constit(s,0,8)) is not. Note that mypattern is a subtype of both constit (a structure type) and bar (a union type).

Similarly, pathto::antecedent is a subtype of expr_double (an expression type). (Assuming that pathto is a subtype of item_double, i.e., has been declared with :- item(pathto,double,0).)

Like structure types, pattern types have constructors. The constructor arguments are the variable bindings, in order of first (leftmost) mention. Thus, given the declarations from above,

:- pattern(mypattern, constit(np,I,I)).
:- pattern(lengthtwo, edge(U,V)+edge(V,W)).
:- pattern(deep, foo(X,Y,yada(yada(yada(X))))).

you can write

mypattern(5)                 % constit(np,5,5)
lengthtwo("a","b","c")       % edge("a","b")+edge("b","c")
deep([1,2],3)                % foo([1,2],3,yada(yada(yada([1,2]))))

Note that what you are constructing is not a pattern (which is a type), but a term matching that pattern (which is an instance of the type).

Patterns with type restrictions

You can restrict a pattern by specifying types for its variables. For example,

:- structure(foo(int i, list l)).
:- pattern(zerolist, foo(0,L)).  
:- pattern(zerocons, foo(0,cons L))).  

Now zerocons is a subtype of zerolist, which in turn is a subtype of foo. Both foo(0,[]) and foo(0,[3,4]) are zerolists, but only the latter is a zerocons.

As an alternative, zerocons can be defined as a pattern that matches against the one-argument zerolist constructor:

:- pattern(zerocons, zerolist(cons L)).   % equivalent to previous definition

Circular declarations using patterns

As with unions, circular declarations are allowed. Here is an ordinary binary tree type that stores an element at each leaf (external node) and each branch (internal node):

:- union(tree,[leaf,branch]).
:- structure(leaf(term element)).  
:- structure(branch(term element, tree left, tree right)).

Using patterns, we can define symmetric trees, in which the left branch equals the right branch:

:- union(symmtree,[leaf,symmbranch]).
:- pattern(symmbranch, branch(Element, symmtree Subtree, symmtree Subtree)).

In other words, a symmetric tree is either a leaf, or a branch whose left and right children are equal symmetric trees. symmtree is a subtype of tree, but the compiler can take advantage of the restrictions to store and process it more efficiently than an arbitrary tree.

Note that the following constructors can be used:

symmbranch(1,symmbranch(2,leaf(3)))  % yields branch(1,branch(2,leaf(3),leaf(3)),
symmtree(1,symmtree(2,symmtree(3)))  % same thing, thanks to constructor overloading

These constructions look a lot like constructing a list [1,2,3]. Indeed the compiler is free to transform the program so that symmtrees are stored like lists. (Even if not, it can still store them in linear space with structure sharing.)

In general, once patterns are fully implemented, checking whether a term is an element of a type (as is done by checked downcast methods in C++) will require testing it with a (hardcoded) nondeterministic top-down tree automaton. Type intersection (during declaration inference) becomes intersection of tree automata. That notion of subtyping seems to have been invented before: for some starting points (slides in 2003) see [1]. Notice that we can't define a type that matches any constant list (e.g., [4,4,4,4,4], but we can define a type that matches any list of constant type (e.g., [four,four,four,four,four] where four is an atom). The difference is that the set of types (but not the set of terms) forms a finite alphabet for the automaton.

Multi-line pattern declarations

Recall that if you declare multiple structures with the same name, you get a union type over anonymous structure types (see functor overloading). Similarly, if you declare multiple patterns with the same name, you get a union type over anonymous pattern types.

For example, the symmtree example above could have been written directly as a multiline pattern:

:- pattern(symmtree,leaf(Element)).
:- pattern(symmtree,branch(Element, symmtree Subtree, symmtree Subtree)).

This compiles into a union [symmtree1,symmtree2] (although not sure whether you'll be able to count on those names), which is equivalent to the explicit union [leaf,symmbranch] given above.

One use is in the automatically declared pattern types for antecedents and consequents. If you have two inference rules that yield constit,

constit(X,I,J) += rewrite(X,W) * word(W,I,J).
constit(X,I,K) += rewrite(X,Y,Z) * constit(Y,I,J) * constit(Z,J,K).

then the automatically declared constit::antecedent pattern will have two lines:

:- pattern('constit::antecedent', rewrite(X,W)*word(W,I,J)).
:- pattern('constit::antecedent', rewrite(X,Y,Z)*constit(Y,I,J)*constit(Z,J,K)).
Personal tools