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
nil
checks 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. Returnsself
if not nothing.