Article:
  Higher-Order Messages in Cocoa
Subject:   Let HOM be your mantra
Date:   2004-09-17 07:53:31
From:   MikeAmy
Hands down? Cheers.


The inspiration is really owed to Haskell's map and fold functions. I just translated the idea into obj-c, using existing HOM implementations as a guide.


It is true that HOM reduces LOC, increases readability and reduces errors. But, I think the most important benefit is in what happens to the design.


Consider a simple while loop iterating over an array such as:


[1]


NSEnumerator *superHeroEnumerator = [superHeros objectEnumerator];
SuperHero *superHero;
while (superHero = [superHeroEnumerator nextObject])
{
[superHero defeatTheVillain];
[superHero saveTheGirl];
}


Note the while block has several statements. Several statements don't naturally go into one HOM statement. Instead we are forced to add the block onto the target. (We can always do this in Objective-C thanks to categories. Respect 'em.)


So we now have:


[2]


@implementation SuperHero
- (void) saveTheDay
{
[self defeatTheVillain];
[self saveTheGirl];
}
@end


with our main loop being:
{
[[superHeros all] saveTheDay]
}


What happened? The block became a method on the target! Exactly where it should be. SuperHeros should know how to save the day. They don't need someone to tell them how (which is the case in [1].


Of course, we could have been a bit more insightful at the outset and used:


[1a]


while (superHero = [superHeroEnumerator nextObject])
[superHero saveTheDay];


with -saveTheDay already defined as in [2]. We could then have applied the loop refactoring as before.


What this shows us is that two refactorings have occurred:
1. The block moved to the target as a method ("Extract method").
2. The loop was refactored using - all.
In this case the block refactoring was done first. The point is that doing the loop refactoring first forces the block refactoring.


HOM encourages the Law of demeter by forcing blocks onto the things that perform them. Each axis of change has been extracted in [2]. The code is much more flexible in the face of changes, which is a good thing.


NB The other alternative was to have:


[1b]
{
[[superHeros all] defeatTheVillain];
[[superHeros all] saveTheGirl];
}
as our main loop.


I'll leave it as an exercise for the reader to figure out in what situations [1b] is better or worse.


Enjoy - Mike

Full Threads Oldest First

Showing messages 1 through 3 of 3.

  • Let HOM be your mantra
    2005-06-23 01:14:50  DanD. [View]

    Just wondering, if superHeros is an array, why not just use

    [superHeros makeObjectsPerformSelector:@(saveTheDay)];

    or, alternately

    {
    [superHeros makeObjectsPerformSelector:@(defeatTheVillain)];
    [superHeros makeObjectsPerformSelector:@(saveTheGirl)];
    }

    -Dan
    • Let HOM be your mantra
      2005-06-23 01:17:49  DanD. [View]

      dang it, I meant

      [superHeros makeObjectsPerformSelector:@selector(saveTheDay)];

      etc. Sorry for not proofreading.

      -Dan
      • Let HOM be your mantra
        2005-06-23 16:40:08  georgerix [View]

        (I'm posting from my Dad's account for the moment--apologies for any confusion this causes.)

        There really is no problem with makeObjectsPerformSelector:; it's a handy mechanism to have when you need to perform an operation over an array. However, it doesn't yield the full flexibility of a mechanism such as blocks or trampolines, whereby you can define methods (like collect, select, reject, and detect) which return (and in the case of detect, operate) differently.

        makeObjectsPerformSelector: is more or less equivalent, when combined with its siblings which take further arguments to use, to the HOM method do, but more complex uses require invocations... trampolines and HOM are basically a system to make invocations automatically-- that is, to make them conveniently.

        As much as anything else, it's a matter of style.

        Thanks for the comments!