Re: test for default history content

Ate,
   Here's an example that shows why we have to dereference history 
states when computing the transition domain.  Consider a state S with a 
deep history state H1, plus two child  states S1 and S2, which are both 
deeply nested (have a number of descendent states.) Now add a transition 
to S1 with target H1 and type 'internal'.  What is the domain of this 
transition?  I think that it depends on whether the deep history value 
contains a descendent of S1 or of S2.  If the deep history value is a 
descendent of S1, then the target of the transition is a descendent of 
S1, so the transition is internal and the domain is S1.  However if the 
deep history value is a descendent of S2, the transition is not internal 
and the domain is S.  So I think that we do need to dereference the 
actual history value to compute the domain.

- Jim
On 4/8/2014 12:26 PM, Ate Douma wrote:
> On 08-04-14 08:19, Jim Barnett wrote:
>> Ate,
>>    I'm getting ready to update the spec, and I think that your 
>> solution for computeEntrySet works well. However, I don't understand 
>> how getTransitionDomain is supposed to work.  You test whether the 
>> source of the transition is a history state. However that can occur 
>> only for the default entry transition, and I don't think that 
>> getTransitionDomain is ever called on it (the transition is 
>> deferenced in computeEntrySet of addDescendentStatesToEnter.)  
>> Futhermore, your version of getTransitionDomain never dereferences 
>> the _target_ of the transition.  This may work out fine, because the 
>> real (= dereferenced) target of a transition with a history state as 
>> its target is always a set of substates of the history state's parent 
>> state, so the calculation of the domain may work out correctly if we 
>> use the history state as the target, but I think it would be clearer 
>> to explicitly deference it.
>>
> Hi Jim,
>
> Thanks for looking into my proposal.
>
> I agree that getTransitionDomain won't be called for a transition with 
> a history source. That is: not through the current algorithm.
> So you may ignore the check for it like I proposed.
>
> It is however still useful to keep if you consider the 
> getTransitionDomain as a general purpose function, which for instance 
> can be used 'upfront' like I do, to compile the transition domain 
> already at parse time. I think it makes sense and more clear to keep 
> such check in place.
>
> The dereferencing of possible history targets of a transition in 
> getTransitionDomain I would definitely not do.
> Formally it might be more clearer, but it will have NO benefit, as 
> like you indicated: these can only be targeting substates of the 
> history itself, so for the transition domain calculation have no 
> consequence at all.
> But more critically, adding history dereferencing would force the 
> getTransitionDomain function to become 'stateful' and thus no longer 
> usable to compile the transition domain upfront.
>
> My suggestion would be to just add some comments clarifying such 
> history targets are explicitly and safely ignored in the function 
> description instead.
>
> Ate
>
>> - Jim
>>
>> -----Original Message-----
>> From: Ate Douma [mailto:ate@douma.nu]
>> Sent: Wednesday, April 02, 2014 5:27 AM
>> To: www-voice@w3.org
>> Subject: Re: test for default history content
>>
>> On 02-04-14 00:10, Jim Barnett wrote:
>>> Ate,
>>>      There is a problem if getTargetStates does not dereference 
>>> history states.
>>> computeEntrySet calls it to produce the initial list of states for
>>> addDescendentStatesToEnter to expand.  If getTargetStates doesn't
>>> dereference history states, they will get added to the statesToEnter
>>> list before addDescendentStatesToEnter gets called - and history
>>> states should not be on statesToEnter. Furthermore, computeExitSet
>>> calls getTargetStates, and it will also need to know what the actual 
>>> dereferenced target state set is.
>>
>> Right. And I realize I already solved this differently, and IMO 
>> elegantly, in my implementation :)
>>
>> The 'hint' leading to my implementation was the fact that 
>> addDescendantStatesToEnter (also) stores the state parameter in 
>> statesToEnter.
>> And is expected to do so as it recursively is invoked for derived 
>> states which were not yet initially added to the statesToEnter.
>>
>> What I thus did instead is the following (in pseudo language):
>>
>>     procedure computeEntrySet(transitions, statesToEnter, 
>> statesForDefaultEntry)
>>        enterableTargets = new OrderedSet()
>>        historyTargets = new OrderedSet()
>>        for t in transitions:
>>            for s in getTargetState(t.target)):
>>             if isHistoryState(s):
>>               historyTargets.add(s)
>>             else
>>               enterableTargets.add(s)
>>        for s in enterableTargets:
>> addDescendantStatesToEnter(s,statesToEnter,statesForDefaultEntry)
>>        for h in historyTargets:
>> addDescendantStatesToEnter(h,statesToEnter,statesForDefaultEntry)
>>        // unmodified below
>>        for t in transitions:
>>             ancestor = getTransitionDomain(t)
>>             for s in getTargetStates(t.target)):
>>                 addAncestorStatesToEnter(s, ancestor, statesToEnter,
>> statesForDefaultEntry)
>>
>> This way, no history state is added to enterableStates because 
>> addDescendantStatesToEnter only does this for non-history states.
>>
>> Note that addAncestorStatesToEnter will work just fine with a history 
>> state as parameter.
>>
>> Finally, concerning computeExitSet: this also isn't a problem in my 
>> implementation because I already 'compile' the source of a history 
>> transition to that of its parent state, and actually (thus) can 
>> 'compile' the transitionDomain and the targets of a transition at 
>> document load time.
>>
>> In pseudo-language in the algorithm, this also can easily be catered 
>> for if you update getTransitionDomain as follows:
>>
>>     function getTransitionDomain(t)
>>       tstates = getTargetStates(t.target)
>>       if not tstates
>>           return t.source
>>       state = t.source
>>       if isHistoryState(t.source):
>>         state = t.source.parent
>>       if t.type == "internal" and isCompoundState(state) and 
>> tstates.every(lambda
>> s: isDescendant(s,state)):
>>           return state
>>       else:
>>           return findLCCA([state].append(tstates))
>>
>> The above can further be optimized by filtering out the history 
>> targets from being passed to the lambda expression or from the 
>> findLCCA(stateList), but it isn't necessary either.
>>
>> With the above minor improvements, the getTargets(transition) as well 
>> as the
>> getTransitionDomain(transition) procedures can remain 'stateless' 
>> (not history state aware) and can be compiled at load time.
>> If you add history dereferencing to getTargets(transition) this no 
>> longer will be possible, so IMO is something we should try not to do.
>>
>>>
>>> It's a big gap in the algorithm that getTargetStates isn't defined -
>>> I'm assuming that it was in an earlier version, and got lost somewhere.
>>>
>>> In any case we need multiple fixes so that: 1) history states don't
>>> end up on the statesToEnter list and 2) default history executable 
>>> content gets recorded.
>>
>> See my above proposed changes, AFAICT it solves 1) elegantly and then 
>> 2) no longer is an issue (already solved by the earlier update to the 
>> algorithm).
>>
>> Regards,
>> Ate
>>
>>>
>>> - Jim
>>> On 4/1/2014 2:45 PM, Ate Douma wrote:
>>>> On 29-03-14 20:26, Zjnue Brzavi wrote:
>>>>> On Sat, Mar 29, 2014 at 1:24 PM, Jim Barnett <1jhbarnett@gmail.com
>>>>> <mailto:1jhbarnett@gmail.com>> wrote:
>>>>>
>>>>>      Zjnue,
>>>>>        I agree that we need more tests, but why would 
>>>>> defaultHistoryContent need
>>>>>      to get passed to getTargetStates?  It's just an accessor 
>>>>> function.  Though I
>>>>>      do think we have to consider how getTransitionDomain and 
>>>>> findLCCA should
>>>>>      handle history states. They ignore them at the moment, and
>>>>> _maybe_ that's ok...
>>>>>
>>>>>      - Jim
>>>>>
>>>>>
>>>>> Hi Jim,
>>>>>
>>>>> Perhaps as quick background, my implementation follows (atm) the
>>>>> proposed algorithm pretty much verbatim, in order to aid debugging
>>>>> for cases like this and to make getting started with SCXML a 
>>>>> little easier for myself and others.
>>>>>
>>>>> Simply put, the recently published changes to the algorithm for
>>>>> defaultHistoryContent support are not satisfying test579.
>>>>>
>>>>> While the getTargetStates function seems pretty harmless, it is also
>>>>> hisory-aware and can redirect a transition target.
>>>>
>>>> Hi Zjnue,
>>>>
>>>> Sorry for chiming in this late, and going back to this earlier
>>>> message, but I'm puzzled by the above statement.
>>>>
>>>> I can find no indication in the algorithm nor in the specification
>>>> text that the getTargetStates function is supposed to do anything
>>>> more than just returning the direct target(s) of a transition.
>>>>
>>>> It seems to me that the problems you encountered are actually
>>>> *caused* by the the history-awareness and dereferencing in your 
>>>> getTargetStates implementation.
>>>>
>>>> AFAICT the pseudo algorithm expects it will take care of the history
>>>> dereferencing itself in the addDescendantStatesToEnter procedure.
>>>> If you do this upfront in you getTargetStates implementation, the
>>>> addDescendantStatesToEnter never will get to 'see' a history state as
>>>> it is supposed to, and neither can it then handle the default history
>>>> content as just was added.
>>>>
>>>> I just completed the implementation of the current algorithm for
>>>> Apache Commons SCXML (although not yet committed), and I now can
>>>> successfully run test579, so it seems to me the proposed change 
>>>> simply works as expected.
>>>>
>>>> But maybe I'm overlooking some other specification requirements for
>>>> getTargetStates?
>>>> I'd appreciate some pointers in that case :)
>>>>
>>>> Regards, Ate
>>>>
>>>>
>>>>> In the case of test579, the <initial> transition, targets <history
>>>>> id="sh1">, but is effectively redirected to <state id="s01" >,
>>>>> skipping any executable content of <history> along the way.
>>>>>
>>>>> Please take a look at my code for getTargetStates and 
>>>>> getTargetState below [1].
>>>>>
>>>>> It is only with the following two lines present in getTargetState:
>>>>>                       if( defaultHistoryContent != null &&
>>>>> h.parent.exists("id") )
>>>>> defaultHistoryContent.set(h.parent.get("id"),
>>>>> h.transition().next());
>>>>> ..that 579 passes.
>>>>>
>>>>> As mentioned, I did not look at it in depth and will not be able to
>>>>> spend more time on it over the next few days unfortunately.
>>>>>
>>>>> So in summary, after adding the published additions to the
>>>>> algorithm, test579 did not pass.
>>>>> It was only after the changes to getTargetStates that it did.
>>>>> Hope this helps for now.
>>>>>
>>>>> Thanks,
>>>>> Zjnue
>>>>>
>>>>> [1]
>>>>>
>>>>>       function getTargetStates( node : Node, ?defaultHistoryContent :
>>>>> Hash<Iterable<Node>> ) : List<Node> {
>>>>>           var l = new List<Node>();
>>>>>           if( !node.exists("target") )
>>>>>               return l;
>>>>>           var ids = node.get("target").split(" ");
>>>>>           var top = node;
>>>>>           while( top.parent != null && !top.isTScxml() )
>>>>>               top = top.parent;
>>>>>           for( id in ids ) {
>>>>>               var ts = getTargetState(top, id, 
>>>>> defaultHistoryContent);
>>>>>               for( tss in ts )
>>>>>                   l.add( tss );
>>>>>           }
>>>>>           return l;
>>>>>       }
>>>>>
>>>>>       function getTargetState( s : Node, id : String, 
>>>>> ?defaultHistoryContent :
>>>>> Hash<Iterable<Node>> ) : List<Node> {
>>>>>           if( s.get("id") == id )
>>>>>               return [s].toList();
>>>>>           else {
>>>>>               for( child in s.getChildStates() ) {
>>>>>                   var ss = getTargetState(child, id, 
>>>>> defaultHistoryContent);
>>>>>                   if( ss != null )
>>>>>                       return ss;
>>>>>               }
>>>>>               for( h in s.history() ) {
>>>>>                   var hh = getTargetState(h, id);
>>>>>                   if( hh != null ) {
>>>>>                       if( defaultHistoryContent != null &&
>>>>> h.parent.exists("id") )
>>>>> defaultHistoryContent.set(h.parent.get("id"),
>>>>> h.transition().next());
>>>>>                       return historyValue.exists( h.get("id") ) ?
>>>>>                           historyValue.get( h.get("id") ) :
>>>>>                           getTargetStates( h.transition().next(),
>>>>> defaultHistoryContent );
>>>>>                   }
>>>>>               }
>>>>>           }
>>>>>           return null;
>>>>>       }
>>>>>
>>>>>
>>>>>
>>>>>      On 3/28/2014 8:51 PM, Zjnue Brzavi wrote:
>>>>>>
>>>>>>          I've added test579 to the suite.  Let me know if there 
>>>>>> are problems
>>>>>>          with it or if you think there need to be more/different
>>>>>> tests.--
>>>>>>
>>>>>>
>>>>>>      Hi,
>>>>>>
>>>>>>      Not had a very good look yet, but it appears we'll need to pass
>>>>>>      defaultHistoryContent to getTargetStates as well, as it is 
>>>>>> another area
>>>>>>      where the algorithm is skipping past some history elements 
>>>>>> without
>>>>>>      considering their executable content. This diff passes 
>>>>>> test579 :
>>>>>> https://github.com/zjnue/hscxml/commit/4b9091470d3c7c1995e6b41900535e0437ec3eb4 
>>>>>>
>>>>>>      Another test or 2 on the subject is probably a good idea.
>>>>>>
>>>>>>      Best regards,
>>>>>>      Zjnue
>>>>>
>>>>>      --
>>>>>      Jim Barnett
>>>>>      Genesys
>>>>>
>>>>>
>>>>
>>>>
>>>
>>
>>
>>
>
>

-- 
Jim Barnett
Genesys

Received on Tuesday, 8 April 2014 19:37:34 UTC