+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)
>

Reply via email to