Liking cljdoc? Tell your friends :D

Grape - Graph Rewriting and Persistence Engine

A Clojure library designed to provide support for graph rewriting based on a persistent graph store.

Usage

Import the Grape library.

(use 'grape.core)

Create a new graph transformation system and give it a name.

(gts 'example)

Graph rewriting rules are defined using the rule form. Rules consist of three parts:

  • the :read specifies the graph pattern to be matched in the host graph
  • the :delete part specifies which graph elements from the matched host graph should be deleted
  • the :create part specifies which graph elements should be created when the rule is applied

Example 1: A simple rule to create one node

The following rule creates only one node. It has an empty :read and delete part, so it matches any host graph and deletes nothing.

(rule 'create-jens! 
      {:create 
          (pattern 
            (node 'n {:label "Person" :asserts {:name "'Jens'"}}))}))

The node form is used to specify the node to be created. Grape currently supports only one (optional) type label for nodes, but multiple (optional) property definitions (asserts). Properties are defined using maps. Note that the value of the maps is always a clojure String that wraps the actual expression that defines the Grape property. Thus, if you need a String value in grape, you need to use (single) quotes within the clojure value string, as exemplified above.

The visual representation of the above rule is given in the image below. Here we use the popular "inline" notation of rewrite rules, where green coloured shapes mark those graph elements that are being created, i.e., graph elements that appear on the right hand side of the rule, but not on the left-hand side.

createJens

Applying a rule

Defining a rule results in the creation of a new function with the name of the rule. The rule can be applied by calling that function:

(create-jens!)

The call to the rule function returns true if (and only if) the rule application succeeds.

Example 2: Parameterized rules

Our first example rule was not very versatile, since it could not generate different persons. This can be improved by using parameterized rules. The following rule is more generic, as it takes the name of the person to be created as a parameter (p).

(rule 'create-person! ['p]
      {:create 
       (pattern 
        (node 'n {:label "Person" :asserts {:name "'&p'"}}))})

createJens

Formal parameters p must be actualized when the rule is applied. The rule application below creates a Person node with name "Flo". Note that the expression &p is replaced with the value of the actual parameter p at rule execution time.

(create-person! "Flo")

Example 3: A rule with a reader

The the next rule has a read as well as a create part. It matches two Person nodes with the names given as formal parameters and creates a parent-of relationship between them.

(rule 'parent_of! ['p 'c]
      { :read (pattern 
               (node 'f {:label "Person" :asserts {:name "'&c'"}})
               (node 'j {:label "Person" :asserts {:name "'&p'"}}))
        :create (pattern 
                 (edge 'e {:label "parent_of" :src 'j :tar 'f} )
                 )})

(parent_of! "Jens" "Flo")

createJens

Example 4: Isomorphic vs homomorphic rules

The following rule is similar to Example 3.

(rule 'works_for! ['e 's] 
      { :read (pattern 
                (node 'f {:label "Person" :asserts {:name "'&s'"}})
                (node 'j {:label "Person" :asserts {:name "'&e'"}}))
        :create (pattern 
                 (edge 'e {:label "works_for" :src 'j :tar 'f} ))})

createJens

It works fine when the read part maps the two nodes in the pattern to two nodes in the host graph, for example:

(works_for! "Flo" "Jens")

However, we cannot use it to express sitations where a person is self-employed, e.g.,

(works_for! "Jens" "Jens")

(Note: We are assuming here that there is only one person with name "Jens", i.e., that the person's name is a unique identifier. In that case the above rule application will not find a valid match (and return nil). This is because Grape's rule matching engine will search for isomorphic matches of the read pattern in the host graph. This means that the nodes / edges in the read pattern must match to distinct nodes / edges in the host graph. This matching semantics can be changed to homomorphic matches by adding the :homo keyword to the definition of the reader pattern:

(rule 'works_for! ['e 's] 
      { :read (pattern :homo
                (node 'f {:label "Person" :asserts {:name "'&s'"}})
                (node 'j {:label "Person" :asserts {:name "'&e'"}}))
        :create (pattern 
                 (edge 'e {:label "works_for" :src 'j :tar 'f} ))})

The above rule allows us to express our "self-employment" example.

Example 5: A rule with delete

The following rule also deletes matched graph elements. In this case it replaces a "works_for" edge with a new "Contract" node and two edges.

rule 'rewrite_contract!
      { :read (pattern
               (node 'n1)
               (node 'n2)
               (edge 'e {:label "works_for" :src 'n1 :tar 'n2}))
        :delete ['e]
        :create (pattern
                 (node 'n3 {:label "Contract" :asserts {:name "'Contract'" :with "n1.name"}})
                 (edge 'e1 {:label "employer" :src 'n3 :tar 'n2})
                 (edge 'e2 {:label "employee" :src 'n3 :tar 'n1}))})

createJens Another interesting aspect about the above rule is that the create part of the rule copies an attribute from a graph element matched in the read part of the rule (n1.name).

Example 6: Dealing with "dangling" edges

Consider the following rule whose purpose it is to "fire" an employee with a given name (by deleting the contract node).

(rule 'fire-employee! ['name] 
      {:read (pattern
              (node 'emp {:label "Person" :asserts {:name "'&name'"}})
              (node 'con)
              (edge 'e {:label "employee" :src 'con :tar 'emp}))
       :delete ['con]})

createJens

But what happens to the 'employee' edge e when the contract node con is deleted? It can't be left "dangling", as that would result in an invalid graph. Graph transformation systems may be based on different theoretical foundations. Algebraic theories for graph transformation systems may be based on different approaches, including the so-called double pushout (DPO) approach and the single pushout (SPO) approach. We won't dive into the theory here, but what is important at this point is that these approaches differ in their treatment of "dangling" edges during node deletion. DPO rules will disallow dangling edges while SPO rules resolve dangling edges by also deleting them from the host graph. The default rule semantics is SPO in Grape. However, a different rule semantics can be specified. The following rule is identical to the previous but specifies DPO semantics. Applying it to our host graph will not be allowed if the application would cause any dangling edges.

(rule 'fire-employee! ['name] 
      {:theory 'dpo
       :read (pattern
              (node 'emp {:label "Person" :asserts {:name "'&name'"}})
              (node 'con)
              (edge 'e {:label "employee" :src 'con :tar 'emp}))
       :delete ['con]})

Example 7: Rules with Negative Application Conditions (NACs)

Negative applications conditions (NACs) are conditions that, if met, inhibit a rule from being applied. Consider the works_for! rule from Example 4. You may want to specify that a 'works_for' edge is created between two persons only if there isn't already such an edge in the graph. A NAC can be used to accomplish this, as seen in the following rule:

(rule 'works_for2! ['e 's]
      { :read (pattern
               (node 'f {:label "Person" :asserts {:name "'&s'"}})
               (node 'j {:label "Person" :asserts {:name "'&e'"}})
               (NAC 1
                (edge 'e1 {:label "works_for" :src 'j :tar 'f} )))
        :create (pattern
                 (edge 'e2 {:label "works_for" :src 'j :tar 'f} ))
        })

NACs are specified using 'NAC' forms, which essentially specifiy graph patterns that, if matched in the context of the read part, will inhibit the application of the rule. Grape allows multiple NACs per rule. The visual representation of NAC's uses dashed borders, with different NACs rendered in different colours.

works_for1!

Example 8: Rule with multiple NACs

This example shows a rule with multiple (two) NACs. The rule creates a sole_employer relationship between an employee and an employer, if the employee works for only that single employer (and a sole_employer relationship does not yet exist).

(rule 'sole_employer! []
      { :read (pattern
               (node 'f )
               (node 'j )
               (edge 'e1 {:label "works_for" :src 'j :tar 'f})
               (NAC 1
                (node 'f2)
                (edge 'e2 {:label "works_for" :src 'j :tar 'f2})
                )
               (NAC 2
                (edge 'e3 {:label "sole_employer" :src 'j :tar 'f} )))

        :create (pattern
                 (edge 'e4 {:label "sole_employer" :src 'j :tar 'f} ))
        })

sole_employer!

Example 9: Rules as building blocks in Clojure programs

Of course, rules can be used within Clojure programs. For example, a rule can be applied repeatedly using the while_ form Given the following example rule that deletes any node, we can simply delete the entire graph using while:

(rule 'delete-any-node!
      {:read (pattern (node 'n))
       :delete ['n]})

(while (delete-any-node!))

createJens

However, Grape provides special control structures that support transactions. This is explained in the next few examples.

Example 10: Transactions

Grape supports atomic transactions. Consider the following rule let_one_go! as an example:

(rule 'let_one_go! ['employer]
      {
       :read (pattern
              (node 'emp {:label "Person" :asserts {:name "'&employer'"}})
              (node 'worker)
              (edge 'e {:label "works_for" :src 'worker :tar 'emp}))
       :delete ['e]})

let_one_go!

This rule "fires" once employee of a named employer (by deleting the works_for relationship). Now consider the case where you want to define a function that fires two employees. If course, you could simply call let_one_go! twice. However, if the employer only has one employee left to fire, only one would be let go. In some cases, we may want "all or nothing" semantics (ACID transactions). Grape provides this functionality with the transact and apl forms. Transactions are defined as follows:

(transact
     (apl 'let_one_go! employer)
     (apl 'let_one_go! employer))

The above form defines a transaction that applies let_one_go! twice (if possible) or makes no change at all. A transaction is invoked with the attempt function, which returns true if (and only if) the entire transaction succeeds.

(attempt
   (transact
     (apl 'let_one_go! employer)
     (apl 'let_one_go! employer)))

Of course, transactions can be used to define Clojure operations:

(defn fire-two!
  [employer]
  (attempt
   (transact
     (apl 'let_one_go! employer)
     (apl 'let_one_go! employer))))

Example 11: Backtracking

A Grape rule may have multiple possible applications in a host graph. Grape supports backtracking when working with transactions. Consider the following rules hire! and promote! as an example. The first rule (hire!) recruits a worker on the job market for a given employer name.

(rule 'hire! ['name]
      {:read (pattern
              (node 'm {:label "Employer" :asserts {:name "'&name'"}})
              (node 'w {:label "Worker"})
              (NAC
               (edge 'e {:label "works_for" :src 'w :tar 'm})))
       :create (pattern
                (edge 'e {:label "works_for" :src 'w :tar 'm}))})

hire!

The second rule (promote!) promotes one of the workers who work for employer name to become a Director, but only if that worker does not also work for a different employer.

(rule 'promote! ['name]
      {:read (pattern
              (node 'm {:label "Employer" :asserts {:name "'&name'"}})
              (node 'w {:label "Worker"})
              (edge 'e {:label "works_for" :src 'w :tar 'm})
              (NAC
               (node 'm2)
               (edge 'en {:label "works_for" :src 'w :tar 'm2}))
              )
       :delete ['w]
       :create (pattern
                (node 'd {:label "Director"})
                (edge 'f {:label "works_for" :src 'd :tar 'm})
                )})

promote!

Now consider the following transaction hire_director! that consists of hiring a worker and then promoting the worker to become a director.

(defn hire_director! [employer]
    (transact
     (apl 'hire! employer)
     ('promote! employer)))

In general, there will be many possible matches for hire!. However, only those workers can be promoted to Director, which do not also work for a different employer. Therefore, transaction hire_director! may need to backtrack in order to search for a worker that can be promoted. For example, consider the following job market that has four workers and four employers:

(rule 'setup-job-market!
      {:create
       (pattern
        (node 'w1 {:label "Worker"})
        (node 'w2 {:label "Worker"})
        (node 'w3 {:label "Worker"})
        (node 'w4 {:label "Worker"})
        (node 'm1 {:label "Employer" :asserts {:name "'Jens'"}})
        (node 'm2 {:label "Employer" })
        (node 'm3 {:label "Employer" })
        (node 'm4 {:label "Employer" })
        (edge 'e1 {:label "works_for" :src 'w1 :tar 'm2})
        (edge 'e2 {:label "works_for" :src 'w2 :tar 'm3})
        (edge 'e3 {:label "works_for" :src 'w3 :tar 'm4}))})

setup-job-market!

Attempting to hire a Director for employer "Jens" (attempt (hire_director! "Jens")) may attempt to hire any of the workers but only succeed with promoting worker w4, as all other workers also work for other employers. Grape will find this only possible match by using backtracking.

Example 12: Passing values from one rule application to another with bind and consult

At times we may want to pass values from one rule application context to another. This can be done in Grape transactions using the bind and consult forms. Consider the same starting graph as in the previous example. Now consider the following rules hire-someone! and train!.

(rule 'hire-someone! ['name]
      {:read (pattern
              (node 'm {:label "Employer" :asserts {:name "'&name'"}})
              (node 'w {:label "Worker"}))
       :create (pattern
                (edge 'e {:label "works_for" :src 'w :tar 'm}))})

hire-someone!

(rule 'train! ['name 'w]
      {:read (pattern
              (node 'm {:label "Employer" :asserts {:name "'&name'"}})
              (node 'w {:label "Worker"}))
       :create (pattern
                (edge 'f {:label "in_training" :src 'm :tar 'w})
                )})

train!

Note that the second rule (train!) receives the worker node w as a parameter. Let's now define a transaction that first hires someone and then "trains" that worker. That transaction must first apply the hire-someone! rule and then pass the hired worker (node) as a parameter to the train! rule. This can be done using bind and consult. bind binds a node that was matched in a successfully applied rule to a given name and consult uses the value bound to a given name as a parameter for another rule application. Here is the transaction:


(defn hire_and_train! [employer]
    (transact
      (apl 'hire-someone! employer)
      (bind 'new-hire 'w)
      (apl 'train! employer (consult 'new-hire))))

Example 13: Control structures: Until

Sometimes we may need additional control structures in transactions. For example, consider the following graph setup:

(rule 'setup-likes!
      {:create
       (pattern
        (node 'n1)
        (node 'n2 )
        (node 'n3 )
        (edge 'e1 {:label "likes" :src 'n1 :tar 'n2})
        (edge 'e2 {:label "likes" :src 'n1 :tar 'n3})
        (edge 'e3 {:label "likes" :src 'n2 :tar 'n1})
        (edge 'e4 {:label "likes" :src 'n2 :tar 'n3})
        (edge 'e5 {:label "likes" :src 'n3 :tar 'n2})
        (edge 'e6 {:label "likes" :src 'n3 :tar 'n1}))})

setup-likes!

Moreover, consider the following rule, which deletes a likes relationship between two arbitrary nodes.

(rule 'dislike_one!
      {:read (pattern
              (node 'n1)
              (node 'n2)
              (edge 'e {:label "likes" :src 'n1 :tar 'n2}))
       :delete ['e]})

dislike_one!

Now let's assume that we want a transaction that repeatedly deletes likes relationships until there is a unidirectional cycle of likes relationships in the graph. Formally, this condition can be expressed in the following graph test:

(rule 'chain_of_likes?
      {:read (pattern
              (node 'n1)
              (node 'n2 )
              (node 'n3 )
              (edge 'e1 {:label "likes" :src 'n1 :tar 'n2})
              (edge 'e2 {:label "likes" :src 'n2 :tar 'n3})
              (edge 'e3 {:label "likes" :src 'n3 :tar 'n1})
              (NAC 1 :homo
                   (node 'n5)
                   (node 'n6)
                   (edge 'e5 {:label "likes" :src 'n5 :tar 'n6})
                   (edge 'e6 {:label "likes" :src 'n6 :tar 'n5})))})

chain_of_likes?

This can be accomplished by using the Grape until control structure. The first argument of an until function is the completion condition (which must be side-effect free). Then then one or several Grape rules (or other control structures) can be called. Here is the program for the above example:

(attempt (until 'chain_of_likes? (apl 'dislike_one)))

Similar to loops in other programming languages, until control structures may not necessarily terminate. Of course, the above example program quite clearly terminates, as each iteration removes a likes edge from the graph - and the number of these edges is finite. However, in general, transactions that use until may loop forever.

Example 14: Control structures: Choice

Sometimes we may want to try different rule applications non-deterministically. The choice constrol structure can be used for this. Consider the following two rules:

(rule 'KimLikesJohn!
      {:read
       (pattern
        (node 'n1 {:label "Kim"})
        (node 'n2 {:label "John"}))
       :create
       (pattern
        (edge 'e1 {:label "likes" :src 'n1 :tar 'n2}))})

KimLikesJohn!

(rule 'JohnLikesKim!
      {:read
       (pattern
        (node 'n1 {:label "Kim"})
        (node 'n2 {:label "John"}))
       :create
       (pattern
        (edge 'e1 {:label "likes" :src 'n2 :tar 'n1}))})

JohnLikesKim1

and the following start graph:

(rule 'setup3!
      {:create
       (pattern
        (node 'n1 {:label "Kim"})
        (node 'n2 {:label "John"})
        (edge 'e1 {:label "likes" :src 'n1 :tar 'n2}))})

setup3!

The following program will non-deterministically choose one of the two above rules so that the graph test likeEachOther? is met:

(attempt
  (choice (apl 'KimLikesJohn!)
          (apl 'JohnLikesKim!))
  (apl 'likeEachOther?))

The graph test likeEachOther? is defined as:

(rule 'likeEachOther?
      {:read
       (pattern
        (node 'n1 {:label "Kim"})
        (node 'n2 {:label "John"})
        (edge 'e1 {:label "likes" :src 'n2 :tar 'n1})
        (edge 'e2 {:label "likes" :src 'n1 :tar 'n2}))})

likeEachOther?

Example 15: Control structures: Avoid

Sometimes we may want to specify a condition to avoid in a transaction. To some degree, this can be achieved by using Negative Application Conditions (NACs) attached to rules (see above). However, the expressiveness of NACs is limited. Therefore, Grape provides the avoid control structure.

Consider the following start graph

(rule 'setup4!
      {:create
       (pattern
        (node 'n1)
        (node 'n2 )
        (node 'n3 )
        (node 'n4 {:label "A"} )
        (node 'n5 )
        (edge 'e1 {:label "relates" :src 'n1 :tar 'n4})
        (edge 'e2 {:label "relates" :src 'n2 :tar 'n4})
        (edge 'e3 {:label "relates" :src 'n3 :tar 'n4}))})

setup4!

and the following rule that creates a relates relationship between an arbitrary node and the node with label "A".

(rule 'relate-one!
      {:read
       (pattern
        (node 'n1)
        (node 'n4 {:label "A"}))
       :create
       (pattern
        (edge 'e1 {:label "relates" :src 'n1 :tar 'n4}))})

relate-one!

The following Grape program tries out all possible (4) matches for relate-one! so that graph test double? fails (i.e., is avoided).

(attempt (transact (apl 'relate-one!) 
                   (avoid (apl 'double?))))

Graph test double? is defined below:

(rule 'double?
      {:read
       (pattern
        (node 'n1 )
        (node 'n4 {:label "A"} )
        (edge 'e1 {:label "relates" :src 'n1 :tar 'n4})
        (edge 'e2 {:label "relates" :src 'n1 :tar 'n4}))})

double?

Example 16: Attribute conditions and assignments - The Ferryman example

We already saw how simple equality conditions on node and edge attributes can be expressed. For more complex conditions on attributes (for example inequalities), Grape provides a special condition form in the read part of rules. Moreover, Grape provides an assign form in the create part of rules to revise attribute values of machted graph elements. These two concepts are exemplified with the popular Ferryman problem. Consider a ferryman who is tasked to ship a goat, a grape and a wolf from one side of the river to the other side. The ferryman can only ship one thing at a time. Moreover, if left unsupervised, the wolf will eat the goat and the goat will eat the grape, respectively. The Ferryman problem is to find a sequence of actions to safely ship all three items to the other side. In Grape it can be described in the following transaction:

  (until 'all_on_the_other_side?
         (transact (choice (apl 'ferry_one_over!)
                           (apl 'cross_empty!))
                   (avoid (apl 'wolf-can-eat-goat?)
                          (apl 'goat-can-eat-grape?))))

The start graph for the problem is shown here:

(rule 'setup-ferryman!
      {:create
       (pattern
         (node 'tg {:label "Thing" :asserts {:kind "'Goat'"}})
         (node 'tc {:label "Thing" :asserts {:kind "'Grape'"}})
         (node 'tw {:label "Thing" :asserts {:kind "'Wolf'"}})
         (node 's1 {:label "Side" :asserts {:name "'This side'"}})
         (node 's2 {:label "Side" :asserts {:name "'Other side'"}})
         (node 'f  {:label "Ferry" :asserts {:name "'Ferryman'" :coins "7"}})
         (edge 'e1 {:label "is_at" :src 'tg :tar 's1})
         (edge 'e2 {:label "is_at" :src 'tc :tar 's1})
         (edge 'e3 {:label "is_at" :src 'tw :tar 's1})
         (edge 'e4 {:label "is_at" :src 'f :tar 's1})
         )})

setup-ferryman!

... whereas the target condition of the transaction all_on_the_other_side? is specified below:

(rule 'all_on_the_other_side?
      {:read
       (pattern :homo
                (node 'tg {:label "Thing" :asserts {:kind "'Goat'"}})
                (node 'tc {:label "Thing" :asserts {:kind "'Grape'"}})
                (node 'tw {:label "Thing" :asserts {:kind "'Wolf'"}})
                (node 's2 {:label "Side" :asserts {:name "'Other side'"}})
                (edge 'e1 {:label "is_at" :src 'tg :tar 's2})
                (edge 'e2 {:label "is_at" :src 'tc :tar 's2})
                (edge 'e3 {:label "is_at" :src 'tw :tar 's2}))})

all_on_the_other_side?

The two graph tests specifying the dangerous conditions to avoid are:

goat-can-eat-grape?

wolf-can-eat-goat?

Now considering the above transaction definition (which uses an until control structure), the ferryman has a choice to ship one thing over or cross empty at any given iteration. This means that it is perfectly possible to loop endlessly. For example, the ferryman could take the goat for rides back and forth forever. Or the ferryman may cross empty forever. In order to bound the search, each trip across the river costs a coin - and we give the ferryman initially a purse of 7 gold coins to start with. Each time the ferryman crosses, it needs to be checked whether the ferryman still has a coin left - and once he reaches the other side, the number of coins needs to be reduced. This is done with Grape condition and assign forms, respectively. The following example shows their use:

(rule 'ferry_one_over!
      {:read
       (pattern
         (node 's1 {:label "Side"})
         (node 's2 {:label "Side"})
         (node 'f {:label "Ferry"})
         (node 't {:label "Thing"})
         (edge 'et {:label "is_at" :src 't :tar 's1})
         (edge 'e {:label "is_at" :src 'f :tar 's1})
         (condition "f.coins > 0"))
       :delete ['e 'et]
       :create
       (pattern
         (edge 'en {:label "is_at" :src 'f :tar 's2})
         (edge 'et2 {:label "is_at" :src 't :tar 's2})
         (assign "f.coins=f.coins-1"))})

ferry_one_over!

The rule that defines empty crosses is specified similarly.

(rule 'cross_empty!
      {:read
       (pattern
         (node 's1 {:label "Side"})
         (node 's2 {:label "Side"})
         (node 'f {:label "Ferry"})
         (edge 'e {:label "is_at" :src 'f :tar 's1})
         (condition "f.coins > 0"))

       :delete ['e ]
       :create
       (pattern
         (edge 'en {:label "is_at" :src 'f :tar 's2})
         (assign "f.coins=f.coins-1"))})

cross_empty!

Indeed, the ferryman needs at least 7 gold coins to carry out his task. In other words, the above transaction will fail with fewer crosses.

Syntax checks and static analysis

Grape implements checks for syntactical ans static semantical correctness and will through exceptions if errors are found during rule definition. For example the following rule is considered incorrect with respect to Grape's syntax definition, as the rule name is a string and not a symbol:

 (rule "testrule"
       {:create
        (pattern
         (node 'n {:label "Person" :asserts {:name "'Jens'"}}))})

An exception with the following message will be thrown in this case:

Grape syntax error: rule name must be a symbol
 Expected syntax: 
RULE          :- ( rule NAME <[PAR+]> { <:theory 'spo|'dpo> <:read PATTERN> <:delete [ID+]> <:create PATTERN> } ) 
NAME, PAR, ID :- *symbol* 
PATTERN       := (pattern ...)
 ... where <> denotes an optional element, | denotes an alternative choice, and N+ denotes a list of elements

Likewise, here is an example for a syntactically correct rule that has problems with respect to static semantics. (In this case an undeclared identifier is referenced.) Consider the following simple example rules:

(rule 'testrule
       {:create
        (pattern
         (node 'n {:label "Person" :asserts {:name "'@id'"}}))})

The exception thrown may look as follows:

Grape static analysis error: identifier id is used but not declared

Note, though, that Grape is schema-less, i.e., there is no need / ability to define a graph schema type for rules. Thus, Grape has no means of checking whether rule definitions are compliant to a particular graph class.

Modular Transformation Systems

It is possible to define the rules for a GTS in "module" (file) that is seperated from the namespace in which the GTS was defined. This can be used to share common rules between different graph transformation systems.

Rules you wish to utilize in another module must be declared in a module without a GTS already declared. Consider these "rule-only" modules. For example, a simple rule-only module might look like:

(use 'grape.core) ;; load the grape.core to allow rule definition.

;; Define rules as normal. 
(rule 'testRule!
      {:create
             (pattern ... )
      }
)

Use ("import") rule-only modules during the gts definition.

(ns grape.moduleExample (:require [grape.core :refer :all]))

;; Create a new GTS, specify paths to modules to load.
(gts 'moduleExample ["test/grape/test_module.clj"])

;; Call rules that are already associated with gts
(clear!) ;; from the default gts

(testRule!) ;; loaded from test_module.clj

This example can be found in test/grape/moduleExample.clj and test/grape/test_module.clj.

Rule Visualization and Documentation

Grape supports automatic generation of rule visualizations based on the Graphviz tool. Each rule definition automatically creates a function to emit the rule in Graphviz (dot) format. The name of that function is -dot. For example, the following function call will return the visual prepresentation of the above example rule:

(use 'grape.visualizer)
(create-jens!-dot)

Visual representations can also be saved as image files to the file system by calling function document-rule for a defined rule, or document-rules for all defined rules:

(document-rule 'create-jens!) ; saves a PNG visual representation of rule 'createJens!
(document-rules) ; saves PNG visual representations for all defined rules

Indeed, if Lighttable is used as the IDE, the visual rule representation can be "inlined" within the IDE. This function requires the NerdyPainter plugin.

Integration with Native Clojure

Grape can be integrated into a native clojure application using the Leinigen Build Tool by:

  1. Install lein if required. Avaliable for OSX via homebrew: brew install leiningen
  2. Compiling the grape source into a .jar file. Use `lein compile; lein uberjar'
  3. Installing into your local repository. Use lein install
  4. Reference Graph in your new project's dependancies, for example:
:dependencies [[org.clojure/clojure "1.7.0"]
                 [environ "1.0.2"]
                 [grape "0.1.0-SNAPSHOT"]]
  :plugins [[lein-environ "1.0.2"]]
  1. Provide an appropriate profiles.clj file to designate the Neo4j DB connection.
  2. Include grape via Clojure's usual namespace application:
(ns app.core (:require [grape.core :refer :all]))

Copyright © 2016 Jens Weber

Distributed under the Eclipse Public License either version 1.0 or (at your option) any later version.

Can you improve this documentation? These fine people already did:
Jens Weber & Simon Diemert
Edit on GitHub

cljdoc is a website building & hosting documentation for Clojure/Script libraries

× close