A simple Maybe monad implementation in Objective-C.
If you already know about Maybe from languages like Haskell, and you're familiar with Objective-C, you may be asking: "Why bother?"
In Objective-C, sending a message to nil returns nil, so with a category on NSObject that adds a binding method like ifNotNil or Haskell's >>= operator, we already have Maybe monad functionality. Nothing more is necessary. But if we want more sophisticated binding functionality like that described below, this won't suffice. In that case, we need a "smarter" nil, which is what SVMaybe provides.
SVMaybe provides the following:
- An elegant solution to the common problem of nested
nilchecks in Objective-C code. - Custom definitions of what is meant by "nothing" on a per-class basis ("semantic nil").
If you've ever found yourself writing something tedious like this:
NSDictionary *person = @{@"name":@"Homer Simpson", @"address":@{@"street":@"123 Fake St", @"city":@"Springfield"}};
NSString *cityString;
if (person)
{
NSDictionary *address = (NSDictionary *)[person objectForKey:@"address"];
if (address)
{
NSString *city = (NSString *)[address objectForKey:@"city"];
if (city)
{
cityString = city;
}
else
{
cityString = @"No city."
}
}
else
{
cityString = @"No address.";
}
}
else
{
cityString = @"No person.";
}SVMaybe allows you to more concisely and declaratively provide the same solution:
NSDictionary *person = @{@"name":@"Homer Simpson", @"address":@{@"street":@"123 Fake St", @"city":@"Springfield"}};
NSString *cityString = [[[Maybe(person) whenNothing:Maybe(@"No person.") else:MapMaybe(person, [person objectForKey:@"address"])]
whenNothing:Maybe(@"No address.")] else:MapMaybe(address, [address objectForKey:@"city"])]
whenNothing:Maybe(@"No city.")] justValue];It also allows you to move beyond simple nil checks by offering run-time redefinition of what is meant by "nothing" on a per-class basis. For instance, in the above example suppose that empty strings should also be considered "nothing." Here's the re-definition:
[NSString defineNothing:[NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings) {
return [(NSString *)evaluatedObject length] == 0;
}]];Given this re-definition, if any of the strings in the above example were empty (or nil), the monad binding would short to nothing and return the appropriate default string wrapped in a SVMaybe.
Use the provided macros:
Maybe(@"foo");
Maybe(nil); // Equals Nothing
Nothing;Or a static method:
[SVMaybe maybe:@"foo"];
[SVMaybe maybe:nil]; // Equals [SVMaybe nothing]
[SVMaybe nothing];To get the value of a maybe:
[Maybe(@"foo") justValue] // "foo"
[Nothing justValue] // Throws an exception!SVMaybe offers a few other chaining options in addition to whenNothing:else described above:
-
andMaybe:Binds multiple maybe values together, returning the last bound maybe. If any maybe is "nothing," the binding shorts and returns "nothing." (Equivalent to>>in Haskell.) -
whenSomething:Binds and maps multiple maybes together using the provided block. If any maybe is "nothing," the binding shorts and returns "nothing." (Equivalent to>>=in Haskell.) -
whenNothing:Same aswhenNothing:else:but without the else block. Returnsselfif not nothing.