Filtering the stack (Episode 1: A new filter)

Hi there! Before telling you anything of what I am doing right now, I want to tell you some great news: the new debugger has been integrated to Pharo3.0 build 🙂 So now we have a prettier and more extensible debugger, and every change and improvement we make it’s going to be integrated faster.

So now, back to business. Now most of the actions available in the old debugger are present in the new one, except for filters.  So the new challenge is to implement that functionality.

In the old debugger, all the logic for filtering the contexts of the stack was all together in one method…  In the new debugger, on the other hand, all the debugging actions are done on the full stack. So my first approach was to create a new filter class, and parametrize the filtering criteria:

#StackFilter
   >>filterWith: aCriteria
           stack select: aCriteria

Here stack is a reference to the stack, and aCriteria is one (or more than one, that’s the intention) of the filters we want to apply on the stack. The first decision I made was to model the criteria as blocks. So, for example, you could use it to filter common messages like this:

context := [Set new] asContext.

process:= Process
forContext: context
priority: Processor userInterruptPriority.
session := DebugSession process: process context: context
filter := StackFilter new stack: session stack.
filter filterWith: filter commonSelectorsFilter.

This code snippet produces a new collection with those contexts which aren’t common selectors.

Now, what about filtering by more than one criteria? Well, my first approach, in a workspace, was something like this:

filter := StackFilter new stack: session stack.
filtered := #(#doItSelectorFilter #commonSelectorsFilter #kernelClassesFilter) inject: Set new into: [:acc :symbol | acc union: (filter filterWith: (filter perform: symbol))].

But after discussing it with my mentors, we arrived to certain conclusions:

  • We’d rather pass the stack as an argument instead of holding it as internal state (this is, filters should be stateless).
  • Flattening the resulting collection isn’t good since it’s more flexible to work with different stacks for each filter, instead of folding filter applications.
  • Most filtering operations are done by intersection rather than union of filtered stacks. Also #union:  wouldn’t preserve the order of the collection.

Considering this, I did a refactor:

context := [ (Set with: 1 with: 2) collect:[:e | e*2]. self halt. ] asContext.
process := Process
           forContext: context
           priority: Processor userInterruptPriority.
session := DebugSession process: process context: context.

filter := StackFilter new.
filtered := #(#doItSelectorFilter #commonSelectorsFilter #kernelClassesFilter) collect: [ :symbol |  filter filter: session stack with: (filter perform: symbol)].

Which returns

an Array(an OrderedCollection() an OrderedCollection([ :e | e * 2 ] in UndefinedObject>>DoIt) an OrderedCollection())

This is, instead of folding, I just mapped filter application, and return an array of filtered stacks 🙂 Then we can decide if I want to intersect each one of those, or leave them separated. This would depend on how we want to plug in/out the filter from the debugger window (I mean,  the decision is closely related to how are the users going to activate/deactivate the filters, if they’re to be set as global options or in context menu, or both; etc).

Still, I am not sure if this is the way I want to combine the filters. I felt that I could still refine the idea, and Nico Passerini suggested me to change the filters so that instead of receiving the stack and returning a new, filtered one, they receive a message, and return a boolean… Something like


blocks := #(#doItSelectorFilter #commonSelectorsFilter #kernelClassesFilter) collect: [:symbol | filter perform: symbol]
filter filter: session stack with: [:message | filters allSatisfy: [:block | block value: message]

This way it’s simpler to combine filters using and/or, and then generate one resulting stack. Using and/or as combinators is a more powerful approach, but perhaps it’s not that common… So I chose to have a collection: enabling/disabling a filter is just a matter of adding/removing an element from this collection, and if you want combined filters, you could add a filter created by using and/or combinators.

And I am most content with the plot twist 🙂 Now I just have to refactor it again, and see what comes up of it.

I mentioned before that we have yet to decide how we want to expose the filters settings to the users. That is an important part of the discussion, and I think it deserves that I write another post to explain it 🙂

So, stay tuned!!

Cheers!

Advertisements
By clariallende