Visual Haskell Debugger (part 2)
For another proof of the idea, I will draw some diagrams by hand for the following suggested pieces of code. As you will see in this article, the drawing will evolve as I will tackle with more complex examples (and there are still pieces of code that I cannot render right now).
We will begin with various implementations for the
map function (recursion, fold, cps, list comprehension and list monad). The following code snippet lists all of them (snippet taken from the Haskell-Cafe mailing list):
map1, map2, map3, map4, map5 :: (a-> b) -> [a] -> [b]
map1 f  = 
map1 f (x:xs) = (f x) : map f xs
map2 f = foldr ((:).f) 
map3 f xs = map' (\x y -> f x : y) xs
map' k  = 
map' k (y:ys) = k y (map' k ys)
map4 f xs = [f x | x <- xs]
map5 f xs = xs >>= (return .f)
This article will render only the first two implementations, leaving the others for the third part.
Before we begin, let’s see some basic building blocks. For example, the debugger must know how to represent values given to a function and how to represent the type of the function (this is where integrating with Hoogle/Hayoo may prove useful). For this, we can either draw different diagrams for such cases like the following image which represents the values given as input and obtained as output for our function:
or like in this diagram depicting the same function but at a type level (ignore the fact that the edges are labelled in the same way, what’s important is that in this case those edges are curled, not straight)
Or, we can represent both views on the same diagrams (no picture right now). The first problem arises when we would have to deal with functions with more than one input variable.
We could represent those functions as though they would need all those parameters (but only if they are provided explicitly)
Or we could represent the function taking into account that any Haskell function is in curry form. In this case, we have two possible diagrams. I will list them below and will use them interchangeable in this article.
Remember that an oval form means that the debugger will not enter into the details of the represented function, while a square form implies that a certain action on it (click/double click) will change the diagram to involve that of the represented function.
Depending on the feedback, the debugger will settle on only one rendering or will provide both of them in a configurable way.
Going back to the above two diagrams, we see two new symbols. The lambda letter inside the block (not represented on all blocks) can be a placeholder for anonymous functions. The ? symbol is not really a used symbol, it is used only to show that we need a way to represent functions as arguments. Again, two ideas have popped out: we can represent the functions as a black box (maybe with the type annotation bellow).
Or we could represent them like the following diagram:
In both cases, we need to differentiate between giving one of our function as argument and giving one of other programmer’s function (thus, not debugging it). If I think more of this situation, I start to think that instead of drawing different shapes, it would be better to underline or italicize the name of one or the other type of function.
With this in mind, we can begin implementing the map versions. For now, the input would be a short list, like [0,1,2].
The above statement raised a new question: what will we do when the output is showed as a too big string for our rendering? The simplest idea will be to represent the output in some kind of box (not decided on its shape) and the user woul have to click on it to obtain the full output, if desired.
Having solved this issue, we present the first map version in a few diagrams:
Firstly, we are presented with the above diagram. As you see, there is a pattern match captured in it as well as a value reuse. Right now, both were represented in the same way, though they could be drawn distinctively. After a few more clicks on the right functions, we are left with this case:
For the second solution, the following diagram will suffice. While in the above picture, inputs that were not used were represented as if linked to the ground in a electric circuit, in this case, the constants enclosed in the function body are represented as the corresponding element
Everythin seems to work for now. Bu what about a more complicated function, like the following one?
renderBasicHtml = prettyHtml $ (header << (thetile << "Test")) +++
(body << (h1 << ("For Demonstrative" ++ "Purposes Only")))
Though this is a code that one would rarely see, a code that can be broken into smaller and easier to understand (and debug) pieces, we want to stress test our debugger with it.
Our first solution would involve drawing all of the internals of our function.
The deltas on the edges are not significant right now. As you see, the nodes are too many sometimes. We need to find a different representation. I guess that we can take into account the fact that each resulting value from a function is either a constant or the result of another function. Thus, instead of displaying all of the internals of a function, why not render only one layer, feeling like peeling an onion?
But, before trying to sketch those diagrams, I need to find a way to represent the instances of the Show typeclass (even in the case when that string is too long) and the values whose type is not an instance of Show (functions and others)
I guess that this concludes this article. I’m still looking for feedback and I promise that I will manually render all of the above map examples and any other piece of code you’ll give to me until 29th of this month.