Dienstag, 4. Dezember 2012

A shift of perspective - hacking HypergraphDB HGHandle

The Symptom - inverted perspectives

The JVM is a great platform. Java, the language on the other hand, is pretty limited and suffers a serious boilerplate syndrom: you have to write tons of redundant code. The actual intention of the code is often burried deeply in that boilerplate. HypergraphDB suffers that Java syndrom too.
Furthermore, although HGHandle and HGLink are the very central elements of HypergraphDB, almost no functionality is encapsulated into them. There certainly are good reasons for that design, which I do not want to get into here, but as a consequence the usage pattern of many java frameworks and of hypergraphDB in particular are kind of "inverted", in my opinion.

This blog post is part of a loose series of posts about scala hacks that make the usage of hypergraphDB more intuitive, by encapsulating common operations right into the objects you are dealing with most. Today, it's about handles, the following post will focus on links.

Well, so currently we have code like this:

    HGALGenerator alGen = new DefaultALGenerator(graph, null,
    null, true,
    true, false);
    HGTraversal trav= new HGDepthFirstTraversal(startHandle, alGen);
    while (trav.hasNext()){
    Pair<HGHandle,HGHandle> pair = trav.next();
    System.out.println(graph.get(pair.getSecond()));
    }

Question:
Unless you are an expert, how much time did it take you to understand what this is about? Probably till "HGTraversal" indicated that this has to do with traversals. How much time to notice that little "startHandle" variable? Well, that is were the traversals starts. I wonder, why not start from where it starts?
Why not simply startHandle.traverse(args...) ?

This is just one example where Java's limited toolset often forces developers to write code, that confronts users with a lot of unnecessary stuff for our brains to filter out. This eats a lot of karma better spent on dealing with the actual tasks!
Correction: of course those parameters are not at all unnecessary, but most of them don't change often. So, there should be defaults that should be overridden on demand.
To improve the situation with Java a bit, we could at least define some usability methods on new implementations of HGHandle or HGLink, respectively. However, most of the time we get our HGHandle / HGLink objects out of HypergraphDB classes such as HGQuery. Hence thoses classes would need to return those new handle/link implementations. That requires the HyperGraphDB community to agree on the necessity of this issue and the implied code changes. However, even then that wouldn't help with reducing complexity of operations such as traversals, since with Java we can not define default parameters. That is where scala comes in.

With the hacks presented below, the same traversal as shown above will not only become as short as one line of code, while keeping all functionality accessible in form of overridable default parameters. Additionally, simply being a method on handle objects, it corresponds better to the intuitive understanding of a traversal, which is that a graph traversal starts from a given start handle. So among other things, we get startHandle.traverse(args...).

Remedies 

Quick demo of used scala tools

With scala there are several new mechanisms to simplify code, help reuse of code and augment existing code that one has no control of. Here is a quick demo of the  scala features used for the handle hacks presented further below.

named default parameters

Define it like this:
def fun(actualParam: Int, param:String = "default " ):String = param + actualParam

Use it like that:
fun(4) 
or
fun(5, param="i'm overriding defaults")

implicit parameters

Define it like this:
def fun(actualParam: Int)(implicit param:String ):String = param + actualParam 

Use it like that:
implicit val nameDoesntMatter:String = "default"
.... //  
fun(5)

Note that we got a similar effect as for named default parameters here, but implicit parameters are available to all methods in scope. Furthermore, there can be only one implicit parameter for each type.

implicit conversions

Define it like this:
object StringHack {
    implicit def string2put(s:String) = new { def print = println(s) }
    }

Use it like that:
import HackString._
"someString".print

Enriching any HGHandle

The following table show some common operations. On the left, the current HypergraphDB API used with Java. On the right, the identical operation with scala, using some implicit conversions.
Notes:
- often parameters on the left don't even show up on the right. However, you can always override them when needed, as shown below.
- the ".h", ".hh", and ".hhh" are actually hacks described in a previous post
- code for demos and implementation can be found below.

current HGDB API - Java scala api

    HyperGraph graph = new HyperGraph("/home/.../bje");

    implicit val graph = new HyperGraph("/home/.../bje")
    // defining a default graph. Can always be overriden.
    

    HGHandle h = graph.add("hello");
    HGHandle h2 = hg.assertAtom(graph, 5);
    HGHandle h3 = graph.getHandle(5);
    

    val h = "hello".hhh
    val h2 = 5.hh
    val h3 = 5.h
    //add 5 to otherGraph (override defaults)
    val h3 = 5.hhh(otherGraph)
    

// DEREFERENCING
    System.out.println(graph.get(h));
    String s1 = graph.get(h);
    String s2 = ((String) graph.get(h)).toUpperCase();
    int ten = 5 + (Integer) graph.get(graph.add(5));
    

    println(h.d)
    val s1 :String = h.d
    val s2 = h.d[String].toUpperCase
    val ten = 5 + 5.hhh.d[Int]
    

// LINKS
    HGHandle wo = graph.add("World");
    HGHandle pLi = graph.add(new HGPlainLink(h, wo);
    HGHandle aRel = graph.add(new HGRel("hw", h, w ));
    

    val pLink = h <-> "World".hhh
    val aRel = h rel ("hw", w)
        

// TYPES & TYPE BASED QUERYING

        graph.getTypeSystem().getClassForType(graph.getType(aRel));
    

    aRel.getType
    

    int j = 1;
    List<HGHandle> intH = new ArrayList<HGHandle>(10);
    // we'll need those later
    for (int i = j; i<11; i++){
        intH.add(graph.add(i));
    }

    val one2ten = (1 to 10).map(_.hhh)
    // we'll need those later

// print all atoms of same type as given atom  a
    HGHandle a = intH.get(0);
    HGHandle intTypeHandle = graph.getType(a);
    List typealikes = hg.getAll(graph, hg.type(intTypeHandle));
    for (Object hh : typealikes){
        System.out.println(hh);
    }

    one2ten.head.typeAlikes.
            foreach(hh => println(hh.d[Int]))

// querying very often constrains on type + some other constraint:
    System.out.println("\n print all greater than 4");
    List gt4 = hg.findAll(graph,hg.and(hg.type(typeInt), hg.gt(4)));
    for (Object hh : gt4){
        System.out.println(graph.get((HGHandle)hh));
    }

    1.h.queryOnSameType(hg.gt(4)).foreach(hh => println(hh.d[Int]))

    // or

    hg.getAll(graph, 1.h.sameTypeQC).foreach(println)
    

// preparing some links to traverse
    for (int i = 0; i < intH.size()-1; i++){
        graph.add(new HGPlainLink(intH.get(i), intH.get(i+1)));
    }

    one2ten.zipWithIndex.
            map{case (handle, index) =>
                if (index < one2ten.size-1)
                    handle <-> one2ten(index+1)}

    HGALGenerator alGen = new DefaultALGenerator(graph, null,
                                                null, true,
                                                true, false);
    HGTraversal trav= new HGDepthFirstTraversal(intH.get(0), alGen);
    boolean continueIt = true;
    while (trav.hasNext() && continueIt){
        Pair<HGHandle,HGHandle> pair = trav.next();
        System.out.println(graph.get(pair.getSecond()));
    }


    1.h.traverse().foreach(p => println(p.getSecond.d[Int]))

    // hidden as named default parameters can be overriden.
    // Traversing only numbers smaller than 6:

    1.h.traverse(sibling = hg.lt(6)).
        foreach(p => println(p.getSecond.d[Int]))


    // Traversing Links of type L & atoms of type A
    1.h.typeTraverse[HGRel, Integer]().
        map(p => p.getSecond.d[Int] ).
        foreach(println)




Here is the code implementing the implicits:
Loading ....

2 Kommentare:

  1. Der Kommentar wurde von einem Blog-Administrator entfernt.

    AntwortenLöschen
  2. You can do much better if you use scala implicit classes and put your scala hypehgraphdb wrapper somewhere to github so others will benefit from it. Java is extremely ugly and dumb language, I wonder why people do not write (hyper)graph databases purely on Scala?

    AntwortenLöschen