It is better to have 100 functions operate on one data structure than 10 functions on 10 data structures. -- Alan Perlis
Coming to Objective C after using a high-level functional language is frustrating. The foundation classes all have their own way of doing things. Method names for similar operations differ between classes. Methods do not compose.
What I realized is that I miss Clojure's data structures and the functions that operate on them.
Is functional programming possible in Objective C? If it were, would a functional programming approach be useful in this environment? FObjectiveC is attempting to find out.
Your feedback is greatly appreciated.
FOC is heavily inspired by Clojure's sequence abstractions. Function names are taken from Clojure's names. I would have Clojure's baby, but it seems as if Objective C might have beat me to it.
Sum the first 100 even numbers.
NSNumber *sum = FReduce(^(NSNumber *sum, NSNumber *val) {
return [NSNumber numberWithInt:([sum intValue] + [val intValue])];
}, 0, FRange(2, 200, 2));
// sum = 9900
Return substrings of a given string.
FFn substrings = ^(NSString *str){
return FMap(^(NSNumber *n){
return FReduce(FFn2FromSelector(@selector(stringByAppendingString:)), nil, FTake([n intValue], str));
}, FRange(1, [str length], 1));
};
substrings(@"FObjectiveC");
// @[ @"F",
// @"FO",
// @"FOb",
// @"FObj",
// @"FObje",
// @"FObjec",
// @"FObject",
// @"FObjecti",
// @"FObjectiv",
// @"FObjective"
// ];
There are currently 2 protocols that enable the foundation types to participate in FOC. FSeq provides basic list functions: first, next and cons. FSeqable means that an object can be turned into a Seq (object that implements FSeq).
The following types along with their mutable counterparts are extended with the FSeqable protocol:
- NSArray
- NSDictionary
- NSSet
- NSOrderedSet
- NSString
Calling seq on an object of any of these types will return an immutable object that implements the FSeq protocol.
Check out FObjectiveC.h. Looks nasty right? Who wants to see this crap?
NSArray* (^FMap)(FFn fn, id<FSeqable>seq);
NSArray* (^FFilter)(FPredicate pred, id<FSeqable>seq);
NSArray* (^FRemove)(FPredicate pred, id<FSeqable>seq);
NSArray* (^FConcat)(id<FSeqable>x, id<FSeqable>y);
It's nasty because those are block declarations. All of the FOC functions are defined as blocks for a few reasons.
- better composability - pass FOC functions to other FOC functions
- define them inline - function pointers suck and don't create closures
- closer to a 1st class entity than function pointers
Because we're coding in C and because C is statically typed, we need to use generic types
for our functions. id
types are used for all functions that expect or return values.
Thus it can be somewhat tedious to convert int
or BOOL
back and forth to NSNumber
.
I've provided a FBool()
macro for that. It helps a little. Use it for those functions
that expect a FPredicate
. Oh wait, I haven't mentioned that yet.
FFn
defines a block type that accepts an id
parameter and returns an id
.
FFn2
defines a block type that accepts 2 arguments of type id
and returns an id
.
FPredicate
is just an alias for FFn that indicates it expects a BOOL
return value.
FObjectiveC should be considered experimental. It is currently a thought experiment and hasn't been used in any production code. I'm not an experienced OC hacker so I expect I've done things COMPLETELY WRONG. As such the API is subject to complete overhaul. You have been warned.
If this ends up being useful at all, these are some things we might look into.
- lazy sequences
- more functions that operate on / construct dictionaries
- documentation that doesn't suck so much