Skip to content

Instantly share code, notes, and snippets.

@samwgoldman
Created March 12, 2014 22:42
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save samwgoldman/9518086 to your computer and use it in GitHub Desktop.
Save samwgoldman/9518086 to your computer and use it in GitHub Desktop.
Maybe monad-alike in Objective-C using parts of ReactiveCocoa and libextobjc concrete protocols
#import <Foundation/Foundation.h>
#import <libextobjc/EXTConcreteProtocol.h>
@protocol Maybe <NSObject>
- (id<Maybe>)map:(id (^)(id value))block;
- (id<Maybe>)flattenMap:(id<Maybe> (^)(id value))block;
- (id<Maybe>)orElse:(id)defaultValue;
- (id)getOrElse:(id)defaultValue;
@concrete
- (id<Maybe>)reduce:(id (^)())reduceBlock;
@end
@interface Maybe : NSObject
+ (id<Maybe>)some:(id)value;
+ (id<Maybe>)none;
+ (id<Maybe>)all:(NSArray *)values some:(id<Maybe> (^)(id value))block;
+ (id<Maybe>)combine:(NSArray *)maybes;
+ (id<Maybe>)combine:(NSArray *)maybes reduce:(id (^)())reduceBlock;
+ (id<Maybe>)notNil:(id)value;
+ (id<Maybe>)value:(id)value kindOfClass:(Class)klass;
+ (id<Maybe>)number:(id)value;
+ (id<Maybe>)string:(id)value;
+ (id<Maybe>)strings:(id)value;
+ (id<Maybe>)URL:(id)value;
+ (id<Maybe>)array:(id)value;
+ (id<Maybe>)dictionary:(id)value;
+ (id<Maybe>)date:(id)value formatter:(NSDateFormatter *)formatter;
@end
#import "Maybe.h"
#import <ReactiveCocoa/RACTuple.h>
#import <ReactiveCocoa/RACBlockTrampoline.h>
@concreteprotocol(Maybe)
- (id<Maybe>)flattenMap:(id<Maybe> (^)(id))block { return nil; };
- (id<Maybe>)map:(id (^)(id))block { return nil; };
- (id<Maybe>)orElse:(id)defaultValue { return nil; }
- (id)getOrElse:(id)defaultValue { return nil; }
- (id<Maybe>)reduce:(id (^)())reduceBlock
{
return [self map:^id(RACTuple *tuple) {
return [RACBlockTrampoline invokeBlock:reduceBlock withArguments:tuple];
}];
}
@end
@interface None : NSObject<Maybe>
@end
@interface Some : NSObject<Maybe>
@property (nonatomic, strong) id value;
- (id)initWithValue:(id)value;
@end
@implementation Maybe
+ (id<Maybe>)some:(id)value
{
return [[Some alloc] initWithValue:value];
}
+ (id<Maybe>)none
{
static dispatch_once_t onceToken;
static None *none = nil;
dispatch_once(&onceToken, ^{
none = [[None alloc] init];
});
return none;
}
+ (id<Maybe>)all:(NSArray *)values some:(id<Maybe> (^)(id))block
{
id<Maybe> result = [Maybe some:@[]];
for (id value in values) {
result = [block(value) flattenMap:^id<Maybe>(id value) {
return [result map:^id(NSArray *values) {
return [values arrayByAddingObject:value];
}];
}];
}
return result;
}
+ (id<Maybe>)combine:(NSArray *)maybes
{
return [[self all:maybes some:^id<Maybe>(id<Maybe> value) {
return value;
}] map:^id(NSArray *values) {
return [RACTuple tupleWithObjectsFromArray:values];
}];
}
+ (id<Maybe>)combine:(NSArray *)maybes reduce:(id (^)())reduceBlock
{
return [[self combine:maybes] reduce:reduceBlock];
}
+ (id<Maybe>)notNil:(id)value
{
if (value) {
return [self some:value];
} else {
return [self none];
}
}
+ (id<Maybe>)value:(id)value kindOfClass:(Class)klass
{
if (![value isKindOfClass:klass]) {
return [self none];
} else {
return [self some:value];
}
}
+ (id<Maybe>)number:(id)value
{
return [self value:value kindOfClass:[NSNumber class]];
}
+ (id<Maybe>)string:(id)value
{
return [self value:value kindOfClass:[NSString class]];
}
+ (id<Maybe>)strings:(id)value
{
return [[self array:value] flattenMap:^id<Maybe>(NSArray *array) {
return [self all:array some:^id<Maybe>(id value) {
return [Maybe string:value];
}];
}];
}
+ (id<Maybe>)URL:(id)value
{
return [[self string:value] flattenMap:^id(NSString *URLString) {
return [self notNil:[NSURL URLWithString:URLString]];
}];
}
+ (id<Maybe>)array:(id)value
{
return [self value:value kindOfClass:[NSArray class]];
}
+ (id<Maybe>)dictionary:(id)value
{
return [self value:value kindOfClass:[NSDictionary class]];
}
+ (id<Maybe>)date:(id)value formatter:(NSDateFormatter *)formatter
{
return [[self string:value] flattenMap:^id(NSString *dateString) {
return [self notNil:[formatter dateFromString:dateString]];
}];
}
@end
@implementation None
- (id<Maybe>)map:(id (^)(id))block
{
return self;
}
- (id<Maybe>)flattenMap:(id<Maybe> (^)(id))block
{
return self;
}
- (id<Maybe>)orElse:(id)defaultValue
{
return [Maybe some:defaultValue];
}
- (id)getOrElse:(id)defaultValue
{
return defaultValue;
}
@end
@implementation Some
- (id)initWithValue:(id)value
{
self = [super init];
if (self) {
self.value = value;
}
return self;
}
- (id<Maybe>)map:(id (^)(id))block
{
return [[Some alloc] initWithValue:block(self.value)];
}
- (id<Maybe>)flattenMap:(id<Maybe> (^)(id))block
{
return block(self.value);
}
- (id<Maybe>)orElse:(id)defaultValue
{
return self;
}
- (id)getOrElse:(id)defaultValue
{
return self.value;
}
@end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment