• No se han encontrado resultados

Konstruktivni problemi s ortocentrom

lookup 7

Branch Branch Empty

Pair(3,"simon") Empty

Pair(4,"john") Branch Branch Empty

Pair(6,"sarah") Empty Pair(7,"claire") Empty … Some( ) lookup 7 Branch ⊞ Pair(4,⊞) Branch ⊞ Pair(7,"claire") ⊞ … Some( )

(a) Consumption of input tree to locate7 (b) Hide unused parts of input Figure 2.15 Slicing input tolookup

and bug-fixing. The point of interactive programming is to allow execution to be retroactively explored and updated so that these activities can be interwoven. Figure2.15returns to thelookupexample we introduced in §2.3. In (a), we see a run oflookupwhich finds the key7. The user backward-slices with respect to the entire output, so that the unused parts of the input tree are highlighted in red. What they see is consistent with their understanding of binary search: for a successful lookup, only the path to the located node will be consumed.

In (b), we introduce a LambdaCalc visualisation option which allows nodes which are unneeded in a particular execution to be collapsed down to a⊞. This can be performed automatically after every update, or applied manually. Collapsed nodes can be re-expanded; the ⊞ symbol is intended to be suggestive of this. Collapsing to a⊞is different in intention from hiding the execution of a function with an ellipsis. An ellipsis indicates that we are not interested in the “internals” of a function application, but only in the value it computes. A computation collapsed to a⊞is disregarded entirely, result included. For differencing purposes, collapsed nodes are treated as deleted. In Figure2.15(b), collapsing the unused parts of the tree to⊞reduces the amount of information that the user has to digest: it is more obvious now that only one string in the tree is consumed, namely"claire", and that only two keys are consumed, namely4and7.

In Figure 2.16 the user does some more testing and discovers a problem. In a real-world interactive programming system, one would create collections of “exemplar” function applications explicitly tagged as test cases or assertions. This would allow the user to “freeze” the extensional behaviour of functions at particular arguments and to automatically be notified of any test failures whenever a code change caused assertions to be violated. This is a significant improvement over unit testing, as there is no need to write separate test code. It is a relatively easy feature to layer on top of what we already provide, but we do not consider it further here. Similar ideas are discussed by Edwards [Edw04]. Instead the user tests interactively, obtaining Figure2.16(a) from Figure2.15(b) by editing the search key from7to3. The “automatic” backward- slicing setting mentioned above is enabled, so unused nodes are automatically collapsed after every update, before the delta is calculated. The overall computation returnsNone, meaning3was not found. However, suppose the user is certain that the data is in the tree and that the tree is sorted properly. What is more, they can see that anEmptyis being consumed in the new state, meaning that the search reached the end of a terminal path in the tree. ThisEmptyis shown in green because it was unneeded and therefore collapsed in the previous state. For differencing purposes, collapsed is the same as absent. Now that it is needed it is no longer collapsed and therefore appears new.

Unsure whether the problem is with the code or the input tree itself, the user decides to start debugging the test case by expanding the ellipsis. This partially reveals a backward slice of the execution: they can see the tree being taken apart, along the lines of what we showed earlier in §2.3, but here the unused parts of the tree remain collapsed, providing a more focused view of what is happening. Thet1:⊞ binding in the pattern-match oftindicates that the first subtree was discarded, and the fact the newEmptynode is inside the subtree bound tot2tells them visually that the search proceeded intot2.

The greenness of theLT ։branch indicates that the case expression took a different branch this time

compared to when we looked up7. Although this by no means pinpoints the problem it at least identifies some code that was run in the incorrect execution but not in the previous (correct) execution. The fact that

lookup 3 Branch ⊞ Pair(4,⊞) Branch ⊞ Pair(7,⊞)   None

(a) Key3is not found

lookup k:3 t:Branch ⊞ ↠ case t of Branch t1:kv:Pair(4,⊞) t2:Branch ⊞ Pair(7,⊞) Empty case LT of LT ↠ lookup k t2 … None

(b) Debug into test case

lookup k:3

t:Branch

↠ case t of

Branch t1:Branch Empty

Pair(3 simon")

Empty

kv:Pair(4,"john")

t2:Branch Branch Empty

Pair(6,"sarah") Empty Pair(7,"claire") Empty case LT of LT ↠ lookup k t2 … None

(c) Accept delta, then re-expand unused nodes

Figure 2.16 Finding and analysing bug inlookup

the branch appears green is a simple consequence of our decision to suppress dead conditional branches. When a conditional flow changes, the newly-live branch will appear out of nowhere (and thus seem “new”) and the previously live branch will simply disappear. However the user would still be directed towards this bit of code if we were visualising dead branches as well: the arrow glyphs ։ and→ would change and the recursive application oflookupwould transition from dead to live. We saw an example of this in Figure2.6(a), with factorial.5

On the other hand, although the applicationlookup k t2is green, the ellipsis underneath is not. This means thatlookupwas applied to the subtree to whicht2is bound in the previous state, when we looked up7. This should ring alarm bells, but again is not in itself conclusive. So the user decides to inspect the data more carefully. They can do so while retaining the particular view of the execution they currently have. In Figure2.16(c) they expand the collapsed⊞nodes. This has the effect of dismissing the delta visible in (b) and then showing the unused nodes again in red. The node being searched for can be seen withint1. The recursive call in theLTbranch now looks culpable, as it recurses intot2. The user then changest2intot1

to obtain Figure2.17(a), which causes the overall result to switch fromNonetoSome("simon"). The ellipsis under the recursive call turns green, indicating that a new function body has been executed. This is because 5A better design for compact visualisation of conditionals would be to retain dead branches in the execution trace, but only allow the user to view one branch at a time. The live branch would be selected by default, but the user would be able to “flip” through all the branches (perhaps using the z-axis to animate). This would provide the desired compactness of presentation, but without conditional-flow changes causing nodes to appear and disappear.

lookup k:3 t:Branch ⊞ ↠ case t of Branch t1:Branch ⊞ Pair(3,#simon") ⊞ kv:Pair(4,⊞) t2: case LT of LT ↠ lookup k t1 … Some( ) lookup 3 Branch Branch ⊞ Pair(3$%simon") ⊞ Pair(4,⊞) ⊞ … Some( )

(a) Change argument to recursive call (b) Collapse body oflookup

Figure 2.17 Fixing the error and returning to testing

lookup is now being called with an argument distinct from any argument with which it was called in the previous state, corresponding to a new region of the tree being searched. Finally, the “demand profile” of the computation changes. First,t1which was not needed at all before, is now partly needed: the path to the node with key3is needed, to be precise. This prefix of the subtree is shown in green, because it was collapsed in the previous state. Second,t2is no longer needed and so is collapsed. (There is no highlighting to indicate this because we are looking at a creation delta. If the user wanted to see this aspect of the change in neededness, they could undo to the previous state, where t2 would then be highlighted in red.) Most important, this update to the execution preserves how the user was viewing this part of the computation. They are still debugging the program, and indeed are at the same “point” in the execution that they were before, only now with a corrected version of the program. In (b) they have accepted the current delta, “put away” the body oflookup, and returned to testing.

Admittedly, this is a very simple example and showed a bug that any experience programmer could have fixed without any kind of interactive debugging feature. Our goal here is not to convince the reader that interactive programming is indispensible for simple problems like this. Instead we would like the reader to consider the wider ramifications of being able to transition smoothly from testing to debugging to bug-fixing and back to testing, with no artificial “mode” changes and minimal loss of working context. We make our point with small examples so that we can defer tackling some of the scalability issues discussed in Future Work, §7.2.

Documento similar