Rule Derivation Language

You can find my slide here.

https://docs.google.com/presentation/d/1tt-JI24wW9j9pE_WUAUVhfyT2Pxlel-8WlimB4pv7l0/edit#slide=id.p

Syntax

For simplicity, I don't define the rules in my language, all rules are predefiend and put in the context in advanced.

data Rule : Set where
  _⊢_∷_ : PremiseSet  String  String  Rule

-- D : [i j k -> x]
ruleD : Rule
ruleD = ( "k" , ( "j" , ("i" , □))) ⊢ "D""x"

Expression is composed by two data type, PremiseExp and Exp.

data Exp : Set

data PremiseExp : Set where
  : PremiseExp
  _,_ : (String ⊎ Exp)  PremiseExp  PremiseExp

data Exp where
  App      : PremiseExp  RuleVar  Exp

exp1 : Exp
exp1 = (App
        (inj₁ "c" ,
        (inj₂ (App
                (inj₁ "b" ,
                (inj₁ "a" ,
                □))
              (Var "A")) ,
          □))
        (Var "D"))

Type System

For all valid expressions, it has only one conclusion to return in the end. Therefore, it has only one type. However, we can also take the type system as a static check, to rule out some "invalid" expressions.

In my type system, I have only one type that call "valid" in type system. An expression need to satisify certain conditions to meet the "valid" type. For example, the number of the premises in "App" expression should be larger than that required by the rule.

Operational Semantic

I do the big step and small step operational semantic for this language.

In big step, I implement an exception. If there is invalid rule application, then exception will arise.

In small step evaluation, I only implemented valid path, because implemented exception is quite tedious. Just like we do in the class, we need to bubble up the exception to the surface by adding new state transition for all existing cases.

Both implementation is the same, and you can the example of reduction in the slides I provide at beginning.

The whole implementation is at PDL.agda

Extend language with properities

Now, I finish a language but it is not so interesting. Therefore, I try to add more thing into this language.

There are many other languages have similar derivation system like I did here, and they are more sophisticated and powerful. Prolog is a good example, it is a logic language which have a rule and unification system to solve logic complex relation.

For example, Prolog has the following syntax.

mortal(X) :- human(X)

It means "For a given X, X is mortal if X is human." In our language, we don't have universial variables to achieve this. It may be interesting if we could extend our language like this.

Rule mortal : ([x] -> ("mortal" x))
Rule human : ([x] -> ("human" x))
Rule humanIsMortal:  ([("human" x)] -> ("mortal" x))

In rule human: if you give me a premise x, then I will return a conclusion x is human. In rule a: if you give me a premise x is human, then I will return a conclusion x is mortal.

That is to say, now conclusions or premises can have properties attached to them.

Let us see another complex example.

Rule parent : ([x y] -> ("parent" x y))
Rule male : ([z] -> ("male" z))
Rule father:  ([("parent" x y) ("male" x)] -> ("father" x y))

Now you can see the property "parent" could have two premises inside. It says :

In rule parent: if you give me premises x and y, then I will return a conclusion x is the parent of y. In rule male: if you give me a premise z, then I will return a conclusion z is male. In rule B: if your give me premise that "x is the parent of y" and "x is a male", then I will return a conclusion that "x is the father of y."

Now we can create an expression

parent("Vader", "Luke")
male("Vader")
father(x, "Luke")

It should return (x = "Vader") in the ends.

However, execute such expressions will need unification, and that is hard to implement. Therefore, we constraint our expression a little bit to let only return boolean in the end.

(
  [
    ["Vader" "Luke"] -> parent("Vader", "Luke")
    ["Vader"] -> male("Vader")
  ]
  -?
  father("Vader", "Luke")
)

The following is another example

Rule parent : ([x y] -> ("parent" x y))
Rule grandparent :  ([("parent" x y) ("parent" y z)] -> ("grandparent" x z))

(
  [
    ["John", "Mose"] -> parent("John", "Mose")
    ["Mose", "Inca"] -> parent("Mose", "Inca")
  ]
  ?-
  grandparent("Ada", "Inca")
)

It should return false.

For implementing such features, we will need more syntaxs.

New Syntax

data Relation : Set where
  Unary  : String → Relation
  Binary : String → String → Relation

data Property : Set where
  Prop : String → Property

data Premise : Set where
  S : String → Premise
  P : Property → Relation → Premise  

data PremiseSet : Set where
  □   : PremiseSet
  _,_ : Premise → PremiseSet → PremiseSet

Now we will need relation and property in premise. With such syntax, we can represent ([x y] -> ("parent" x y) by saying this premise's property is parent and that a binary relation between x and y.

In addition, in order to write fact and assumption. We will need the following syntax

data ParamSet : Set where
  □   : ParamSet
  _,_ : String → ParamSet → ParamSet

data Fact : Set where  
  App      : ParamSet → RuleId → Fact

data FactSet : Set where
  □   : FactSet
  _,_ : Fact → FactSet → FactSet

data Assumption : Set where
   Asump   : Premise → Assumption

data Exp : Set where
  Check    : FactSet → Assumption → Exp

Check Mechanism

For this implementation, We can only do the shallow check. That is, we will find the rule that can generate the assumption, and instantiate the required premise by assumptions. Finally, we check if all instantiated premises are qualified by facts provided.

Limitation

We don't do the recursively check and unification check here because they will cause non-terminating problem in Agda.

The whole implementation is at PDL2.agda