+1 I also need some kind of encapsulation for generic inner traversals in my application. On יום ה׳, 27 באוג׳ 2015 at 20:13 Matt Frantz (JIRA) <[email protected]> wrote:
> > [ > https://issues.apache.org/jira/browse/TINKERPOP3-716?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=14717084#comment-14717084 > ] > > Matt Frantz commented on TINKERPOP3-716: > ---------------------------------------- > > The motivation for this RFE derives from my application design, which > consists of traversals that are independently specified and tested, and > then used to compose other traversals. > > Suppose I compose the _Bar_ algorithm from the _Foo_ algorithm. Each is > implemented as a {{Traversal}}. The Foo algorithm is the _sub-traversal_ > and the Bar algorithm is the _super-traversal_. The relationship is > similar to a parent-child traversal relationship, but with additional > restrictions on shared state. > > There are four principle state variables that we'll consider in the > following sections: > * Object (value of the traverser) > * Path > * Sack > * Side-Effects > > We seek _encapsulation_ of this state, such that a sub-traversal's state > does not blend with the super-traversal's state except as explicitly > designated by the traversal author. > > *Object* > Encapsulating the logic that modulates Object is already handled via > {{map}} and {{flatMap}}. The {{sub}} step would act similarly. Since > {{flatMap}} is more general, {{sub}} should probably behave as {{flatMap}}. > > *Path* > Path encapsulation involves the step label namespace and the visibility of > the path at both ends of the sub-traversal. The guiding principles for > proper encapsulation are as follows: > * A sub-traversal should (by default) have no visibility into the path of > the super-traversal. > * A super-traversal should (by default) have no visibility into the path > of the sub-traversal. > * A super-traversal MAY provide visibility into specific step labels of > the super-traversal when invoking the sub-traversal. > * A sub-traversal MAY require visibility into specific step labels of the > super-traversal through a binding/renaming option. > * A sub-traversal MAY provide visibility into specific step labels of the > sub-traversal through an exporting option. > * A super-traversal MAY require visibility into specific step labels of > the sub-traversal through a binding/renaming option. > > These operations can be efficient, since they involve labeling steps, and > possibly constructing a {{Path}} "view" with limited visibility into the > underlying "real path". > > _Path Importing_ > No matter what Foo (the sub-traversal) does, it should not have access to > the path of Bar (the super-traversal), unless Bar exports it. That is, Foo > could require explicitly importing a portion of Bar's path using something > like a {{withPath}} modifier of the {{sub}} step. > > {code} > Traversal computeFoo() { > // Use path steps 'x', 'y', 'z' provided from super-traversal. > Traversal t = select('x', 'y', 'z'); > ... > return t; > } > > Traversal computeBar() { > Traversal t = ...; > // Provide Foo with the x-y-z path it requires from our own a-b-c steps. > t.sub(computeFoo()) > .withPath('x', 'y', 'z') > .by(select('a')) > .by(select('b')) > .by(select('c')); > ... > return t; > } > {code} > > _Path Exporting_ > No matter what Bar (the super-traversal) does, it should not have access > to labeled steps of Foo (the sub-traversal), unless Foo exports them. That > is, Foo could explicitly export a portion of its path using something like > a {{deselect}} (a.k.a. {{makePath}}) step. > > {code} > Traversal computeFoo() { > Traversal t = ...; > return t.deselect('x', 'y'); > } > > Traversal computeBar() { > Traversal t = ...as('a'); > t.sub(computeFoo()).as('foo'); > t.path(); // would see steps 'a', 'foo.x', and 'foo.y' > t.select('a', 'foo.x', 'foo.y'); // OK > return t; > } > {code} > > *Sack* > The sack feature is useful, but it is limited because the sack type can > only be specified at the source. Here is a use case that involves a > "scoped sack" implementation. The "Foo" algorithm uses sack with a > {{FooSack}} instance. However, "Bar" also wants to use sack, but wants to > use its own {{BarSack}} instance. > > {code} > Traversal computeFoo() { > return __.withSack(new FooSack())...; > } > > Traversal computeBar() { > Traversal bar = __.withSack(new BarSack())...; > bar.sub(computeFoo())...; > return bar; > } > {code} > > Thus, this issue is in part about being able to perform the {{withSack}} > step on some interior traversal. > > *Side-Effect* > As a global namespace, the side-effects are powerful, but it can also be > difficult to coordinate partitioning of this global namespace in a modular > traversal. The {{sub}} step would enforce encapsulation of the side-effect > namespace by default. Each {{sub}} step would effectively provide a new > {{SideEffects}} object. > > {code} > Traversal computeFoo() { > return __...store('a')...; > } > > Traversal computeBar() { > Traversal t = ...; > t.sub(computeBar()); > ... > t.store('a'); // This is NOT the same 'a' that Foo sees! > ... > return t; > } > {code} > > You could selectively populate the side-effect of the sub-traversal using > {{withSideEffect}} as a modifier. > > {code} > Traversal computeFoo() { > return __...select('a')...; // Access side-effect 'a' which must be > provided by super-traversal. > } > > Traversal computeBar() { > Traversal t = ...; > // Provide Foo with its required side-effect key 'a' from our own 'x'. > t.sub(computeBar()) > .withSideEffect('a', select('x')); > ... > return t; > } > {code} > > If you want to allow exporting of side-effects directly from the > sub-traversal into the super-traversal, then something like {{deselect}} or > {{makeSideEffects}} could be used. > > {code} > Traversal computeFoo() { > Traversal t = ...; > t.store('x')...store('y')...; > return t.deselect('x', 'y'); > } > > Traversal computeBar() { > Traversal t = ...as('a'); > t.sub(computeFoo()).as('foo'); > > // OK, since we resolve from our own path for 'a', plus scoped, exported > side-effect of Foo. > t.select('a', 'foo.x', 'foo.y'); > > return t; > } > {code} > > > > subroutine step > > --------------- > > > > Key: TINKERPOP3-716 > > URL: > https://issues.apache.org/jira/browse/TINKERPOP3-716 > > Project: TinkerPop 3 > > Issue Type: Improvement > > Components: process > > Affects Versions: 3.0.0-incubating > > Reporter: Matt Frantz > > Assignee: Marko A. Rodriguez > > > > In our application, we build complex traversals. We use a modular > technique in which lower-level traversals are used to compose higher-level > traversals. For example, we might have an API like this: > > {code} > > void computeFoo(GraphTraversal t); > > {code} > > The assumption is that the "root" traversal has some state in the form > of its traverser and path (and possible sack and side effects, although > we're not using those at the moment). Importantly, all of this state is > not fully realized, as the goal is to build another traversal before > executing it all at once. To use the "foo" traversal in the "bar" > traversal, I might do this: > > {code} > > void computeBar(GraphTraversal t) { > > // Perform some part of the "bar" calculation, including setting up > the parameters of "foo" > > t.out()...; > > // Attach the "foo" logic. > > computeFoo(t); > > // Continue with more "bar" logic. > > t.blah()...; > > } > > {code} > > In the implementation of these modular traversals, we end up exposing > certain implementation details, especially in the form of labeled steps > (path keys). It is often the case that we don't necessarily want to leak > this state. We often want these traversals to operate as if they were > single steps, discarding any evidence of graph wandering that might have > occurred. > > What if there were a step which acted as a "stack" for the path? (It > might also act as a stack for the sack; we figured we would need that if we > ever decide to use sack.) Not only would this provide encapsulation, it > would allow the path to be pruned and thus improve runtime efficiency. > > We want traversal subroutines. How about this signature? > > {code} > > GraphTraversal sub(Traversal subTraversal); > > {code} > > What this would do is guarantee that no path state (or sack state) would > escape. The only effect would be on the traverser. I think it could be > implemented using a {{MapStep}}, or possibly {{FlatMapStep}} in order to > realize a subTraversal that branches. > > What state would subTraversal have access to? By default, it could only > see the traverser. It has its own key namespace for the path, so it cannot > see the outer path. It has its own sack. It should probably have its own > side effect key namespace, too. > > If you want to opt-in state into the subTraversal, then perhaps you > could list the keys to form a pseudo-path: > > {code} > > GraphTraversal sub(Traversal subTraversal, > String...stepOrSideEffectLabels); > > {code} > > One of the interesting things that this might provide (eventually) is > the ability to hang OLTP traversal off of OLAP traversals. I had discussed > this need a while ago in the context of vertices that act as OLAP > "computation loci". A "sub" could use a different engine, and thus embed a > standard traversal within a computer traversal. > > This would change our modular programming paradigm. We could safely > redesign each module to produce an anonymous traversal like so: > > {code} > > GraphTraversal computeFoo(); > > GraphTraversal computeBar() { > > // Perform some part of the "bar" calculation, including setting up > the parameters of "foo" > > GraphTraversal t = __.out()...; > > // Attach the "foo" logic. > > t.sub(computeFoo(t)); > > // Continue with more "bar" logic. > > return t.in()...; > > } > > {code} > > > > -- > This message was sent by Atlassian JIRA > (v6.3.4#6332) >
