Date mån 05 november 2018

It turns out that with the set implementation together with the fact that acyclic graphs are indexed and a generalized dynamic predicate functionality, we can implement a quite interesting class system that glean on common lisps CLOS. In all we do not implement the full protocoll, but we can connect the dots and get close.

The first thing to note is that set's in guile-log can also represent key value stores or maps. We could therefore define a class as a set of keys, where the values is manipulated with the usage of generalized predicates, just as in guile schemes GOOPS and Common Lisps CLOS. Normal sets and maps are equal if the keys are equal, for attributed guarded sets, full unification on values is also done in case the other variable is a var or attributed gurded set. This means that id cA={a,b,c} and we have defined f as

f(cA,1).

Then we for all objects oA (keys + values) with the same keys we have

> f(oA,X).

X = 1.

This works even if oA is an attributed set. Name clashes could be a problem if atoms where not named spaced as they are in guile-log. Hence this definition of class and objects are quite general. But as in all guile-log the unfication of strings with atoms is true e.g. a = "a" independent on which name space a is located. So one could define short cuts for e.g. operators that have the same meaning but different implementations available in different name spaces like +,-,*,/ which has an inneficient but standard compliant implementation and a faster more native guile implementation.

Class inherritance is done via the subset relation and we can identify a set oc classes with a inherritance graph via the helper function sets_to_theory in (logic guile-log guile-prolog set-theory) setting up the class system and issuing this function on a list of class sets we have a class system that essentially create an efficient implementation of the inherritance graph and also maps sets to type index via the lookup function in (logic guile-log inheritance). There is also reverse-lookup that can generate the set an index stand for. Anyway with a theory defined we can implement indexing and matching of types where A matches B if A is a subclass of B or if B ⊆ A. The compiled graph means that if we use indeces instead we will not need to evaluate the whole set, but just do a simple 'eq?' on it and use compiled decitions regarding subclass. Anyway for a typed argument you would typically do it like

f(X : tA).

With a type index tA. Where the actual object is transferred as X and clause selection is via tA. Finally the order of clauses for generilized functions are peculiar. One can't generally use append or prepend via assertz or asserta. In stead on can use a -generalized. directive in source code or assertg in dynamic context to place the clause sorted for a generalized predicate. The rule is that when asserting a generalized clause, we find the first match of the prevoius clauses and insert the new one before that. This means that more specialized codes will be executed before the less specialized ones.

So now over to an examplification.

First of all we define some helper predicates,

:- dynamic(class).

e(X,L) :-
  texp(X,XX,true,LL),
  (
    LL=true ->
      L=XX ; 
    L = (LL,XX)
  ).

texp(X,X,L,L) :- var(X),(\\+attvar(X)),!.
texp(cls(X),U,L,LL) :- !,
  LL = (L,(class(X,C),U=(X : C))).
texp(cls(X,C),U,L,LL) :- !,
  get_attr(C,'Type',[A|_]),
  revlookup(A,AA),
  AA  X,
  LL = (L,(U=(X : C))).

texp([A|B],[AA|BB],L,LL) :- texp(A,AA,L,L1),texp(B,BB,L1,LL).
texp(A(|B),AA(|BB),L,LL) :- texp(A,AA,L,L1),texp(B,BB,L1,LL).
texp({A},{AA},L,LL) :- texp(A,AA,L,LL).
texp(A,A,L,L).

Here e/2 will represent a goal transformer that replaces cls(X) with X : tX, where tX is the class index looked up from the object/set X. Or we can say that interpret X as class cA via cls(X,cA) a check for truth will be done and then the actual X : tA will be inserted in the goal.

Lets make a class graph, first prolog code,

'init-theory' :-
   A is {a},
   B is {b},
   C is AB,

   sets_to_theory([A,B,C]),

   define(aS,A),
   define(bS,B),
   define(cS,C).

And run init-theory which will define sets and place the as defined in the module as aS,bS,cS.

Now evaluare the set representatoin for fast subclass verification, see (logic guile-log inheritance).

(compile-sup-sub)
(order-the-set)
(print-theory)

with output

(({a,b} -> ({a,b} {b} {a}))
({a} -> ({a}))
({b} -> ({b})))

Now define the type objects which essentially are special attributed variables found in (logic guile-log types) e.g. `mk-type*

(<define> (mktype x y) (mk-type (lookup (<lookup> x)) y))

and prolog:

mkclass(X,C) :-
  mktype(X,C),
  asserta(class(X,C)).


tp :-
  mkclass(aS,AT),
  mkclass(bS,BT),
  mkclass(cS,CT),

  define(aT,AT),
  define(bT,BT),
  define(cT,CT).

So aT,bT,cT is the types associated with the set classes aS,bS,cS.

Now define a simple generalized predicate,

-generalized.
ftheory(X : aT) :- write(a(X)),fail.
ftheory(X : bT) :- write(b(X)),fail.
ftheory(X : cT) :- write(c(X)),fail.

Note that ordinary the last clause will be executed last, but in stead due to the generalized directive, it will be places ahead of both the others. Now lets define a simple macro expander and test code,

t(X) :- e(X,XX),(XX -> write(true(X)) ; write(false(X))),nl.

test :-
  t(ftheory(cls(aS))),
  t(ftheory(cls(bS))),
  t(ftheory(cls(cS))),

  X is {a-2},
  t(ftheory(cls(X))),

  Y is {a-2,b-4},
  t(ftheory(cls(Y,aT))).

Now running test will result in,

test.

a({a})                     false(ftheory(cls({a})))
b({b})                     false(ftheory(cls({b})))
c({a,b}) a({a,b}) b({a,b}) false(ftheory(cls({a,b})))
a(({a-2}∖∅  ))           false(ftheory(cls(({a-2}∖∅  ))))
a(({a-2,b-4}∖∅  ))       false(ftheory(cls(({a-2,b-4}∖∅  ),X0)))

To note is that the rule for types unification is that cT=aT but not the reverse. So the unification for types is not symmetric that is subs match supers.

Happy Hacking!


Comments

comments powered by Disqus

~ Follow me ~