Re: Recursive blocks
Re: Recursive blocks
- Subject: Re: Recursive blocks
- From: Glen Low <email@hidden>
- Date: Fri, 28 Jan 2011 16:03:20 +0800
Chris, Bill, All
My mummy tells me not to feed the flames, but...
On 28/01/2011, at 7:38 AM, Chris Suter wrote:
Hi Bill,
On Fri, Jan 28, 2011 at 4:51 AM, Bill Bumgarner <email@hidden> wrote:
You have measured a situation where the pattern's marginal slowness
actually
matters?
No, of course not; I don't have a real world use case for it and, I
suspect, neither does anyone else.
... them fighting words.
I wrote an Objective-C wrapper around Graphviz I call "GraphKit". One
of the graph methods is to find the smallest cluster that encloses a
set of nodes. Each cluster may have subclusters. Each cluster has
nodes, but also contains the nodes of its subclusters. This design is
based 1-1 to the Graphviz C API actually.
So the method I wrote is:
- (GKGraph*)clusterForNodes:(NSSet*)nodes
{
__block GKGraph* smallestCluster = nil;
__block BOOL (^findSmallestCluster)(GKGraph*);
findSmallestCluster = ^(GKGraph* cluster)
{
NSMutableSet* directNodes = [NSMutableSet setWithSet:cluster.nodes];
for (GKGraph* subcluster in cluster.subgraphs)
if (subcluster.isCluster)
{
if (findSmallestCluster(subcluster))
return YES;
[directNodes minusSet:subcluster.nodes];
}
if ([nodes intersectsSet:directNodes])
{
if ([nodes isSubsetOfSet:directNodes])
smallestCluster = cluster;
return YES;
}
else
return NO;
};
findSmallestCluster(self);
return smallestCluster;
}
The findSmallestCluster block captures the smallestCluster local
variable as well as the nodes parameter. The BOOL result is just to
short-circuit further testing -- if any leaf call hits a set
intersection, we don't have to look further.
The equivalent without blocks might look like:
- (BOOL)findSmallestClusterForGraph:(GKGraph*)graph smallestCluster:
(GKGraph**)smallestCluster nodes:(NSSet*)nodes
{
NSMutableSet* directNodes = [NSMutableSet setWithSet:cluster.nodes];
for (GKGraph* subcluster in cluster.subgraphs)
if (subcluster.isCluster)
{
if ([self findSmallestClusterForGraph:subcluster
smallestCluster:smallestCluster nodes:nodes])
return YES;
[directNodes minusSet:subcluster.nodes];
}
if ([nodes intersectsSet:directNodes])
{
if ([nodes isSubsetOfSet:directNodes])
*smallestCluster = cluster;
return YES;
}
else
return NO;
}
- (GKGraph*)clusterForNodes:(NSSet*)nodes
{
GKGraph* smallestCluster = nil;
[self findSmallestCluster:self smallestCluster:&smallestCluster
nodes:nodes];
return smallestCluster;
}
The recursive block allows me to hide the recursion within a method,
also to avoid having to return more than 1 type (BOOL + GKGraph*) from
the recursion i.e. avoiding the ugly GKGraph** smallestCluster
parameter.
On the other hand, the block formulation is not quite in sequence with
how the code actually flows either -- which is one of the advantages
of block programming IMHO. At least the block code is not too far
visually from the invoking code.
The thing about recursion is it forces you to name the recursive part,
in order for recursion to work. At least with blocks I can avoid
polluting the method namespace with the recursion name.
This is shipping or soon-to-be shipping code (Instaviz 2.0).
A Block_copy() is going to often be at least an order of magnitude
(if not
more) slower because it ends up causing a malloc(). Given that
there will
only be one allocation (or one assignment), it is likely that the
overhead
of either will be in the noise.
I was actually hinting at the slowness caused by using a block
variable. I forget exactly how many levels of indirection the block
variable incurs, three perhaps vs. a single call instruction, and
there's no way for the compiler to optimise it.
As you say, it's unlikely to make a difference but if you were to use
tail recursion and have a large number of iterations, then it might.
I thought __block variables are not on the heap until the block itself
is copied?
A smart compiler could see that the block only exists or is used
within a scope, so the __block variable will only ever be on the
stack, so there should only be the single indirection at most? I know
gcc used to solve the aliasing problem within certain scopes so things
like dereferencing a C++ reference would not end up loading from
memory all the time.
I found the Blocks example to be more readable, but to each his own.
When I say readable, I also mean less prone to error. If you forget
the __block type qualifier, you don't get a compiler warning. Also if
you were stupid enough to reuse the block variable later that would
change the behaviour of the earlier block.
You do get an analyzer warning if you forget the __block modifier. It
complains that you are capturing garbage since it is uninitialized.
I wonder if you can do a const __block or some other, um, abomination.
It also smells. Using a block variable to call yourself recursively
is a hack.
I have to agree with you partially. It looks fishy but I don't have
enough experience with blocks so far to outright call it a hack.
I like my original (bad) formulation
BOOL (^findSmallestCluster)(GKGraph*) = ^(GKGraph* cluster) { ... }
better since it is more succinct.
What bugs me is the block being told its own name so to speak. If
there was some way to call the block from within itself e.g.
^(GKGraph* cluster) { ... __self__(subcluster); /* recursive call
*/ ... }
Cheers, Glen Low
---
pixelglow software | simply brilliant stuff
www.pixelglow.com
aim: pixglen
twitter: pixelglow
_______________________________________________
Cocoa-dev mailing list (email@hidden)
Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com
Help/Unsubscribe/Update your Subscription:
This email sent to email@hidden