Skip to content

Instantly share code, notes, and snippets.

@pwightman
Last active August 29, 2015 13:57
Show Gist options
  • Save pwightman/9679932 to your computer and use it in GitHub Desktop.
Save pwightman/9679932 to your computer and use it in GitHub Desktop.
Pattern matching, taken from functional programming languages like Clojure/Racket, applied to Objective-C.
// Example Goal: find URLs embedded in arbitrary JSON
NSDictionary *JSON = @{
@"some": @[
@"arbitrary": @[
@"json",
@"http://google.com",
@{ @"url": @"http://yahoo.com" }
]
]
};
// ZSPatternMatcher would (initially) match on JSON-compatible classes (`NSString`, `NSArray`, `NSDictionary`, etc...)
// should maybe indicate that in the class/method names
ZSPatternMatcher *matcher = [ZSPatternMatcher matcherWithSource:JSON];
// Enumerates all strings in arrays, or strings as values in objects
[[[matcher valuesMatchingClass:NSString.class]
filter:^BOOL(id obj) {
return [urlRegex matches:obj]; // I'm making this up, roll with me
}]
each:^(id obj) {
// do stuff
}];
// This
[matcher valuesMatchingClass:NSString.class]
// is really just a special case of something more like:
[matcher valuesMatching:^BOOL(id obj) { return obj.class == NSString.class }];
// which is really just a special case of this, where allValues recursively returns
// all values (array elements or values in ditionaries)
[[matcher allValues] filter:^BOOL(id obj) { return obj.class == NSString.class }];
// You could also do allKeys to recursively get all keys in all dictionaries
[matcher allKeys]
/*
While this imperative matching is nice in its own right, declarative matching based
on *structure* would be awesome/better.
*/
// It could be used to find occurences of exact, static elements in JSON
[matcher JSONObjectsMatching:@{ @"url": @"http://google.com" }].count;
// It could also match on more vague, dynamic definitions, aptly called "patterns"
[matcher JSONObjectsMatching:@{ @"url": ZSPatternAnyString }];
// which is just a special case of something like
[matcher JSONObjectsMatching:@{
@"url": [ZSPattern patternWithTest:^BOOL(id obj) { return obj.class == NSString.class; }]
}];
// You could have several macros defined for common things:
ZSPatternAnyObject
ZSPatternAnyString
ZSPatternAnyNumber
ZSPatternAnyDate
ZSPatternAnyArray
ZSPatternAnyDictionary
// Maybe class methods would be better:
[ZSPattern anyString];
// Eh, it's just syntactic. Let's move on.
// You could also mix static data with patterns
[matcher JSONObjectsMatching:@{ @"nums": @[@1, @2, ZSPatternAnyNumber] }]
// ZSPattern's API would be something simple like:
[ZSPatternAnyString objectPasses:@1]; // => NO
[ZSPatternAnyString objectPasses:@"hi"]; // => YES
// Honestly, you could just use straight-up blocks and forget ZSPattern as a class, without losing
// the nice macros.
[matcher JSONObjectsMatching:@{ @"url": ^BOOL(id obj) { return YES; } }];
// My only concern with this is that blocks are (I think?) rather opaque at runtime... not sure how
// easy it is to "detect" blocks, ensure they're the right return type/parameters, etc.. I've had
// very mysterious runtime bugs crop up in the past based on slight variations in block definitions
// when unpacking blocks from NSArray/NSDictionary objects.
// UPDATE: Opaque, indeed: http://stackoverflow.com/questions/9048305/checking-objective-c-block-type
// You could also make patterns composable. I'm building an interpreter where it would be useful to
// know when you've run across the "atoms" (irreducible parts of a language's grammar) of JSON
ZSPattern *atoms = [ZSPattern orPatternForPatterns:@[
ZSPatternAnyString,
ZSPatternAnyBool,
ZSPatternAnyNumber
]];
// Alternative syntax:
ZSPattern *atoms = [[ZSPatternAnyString or:ZSPatternAnyBool] or:ZSPatternAnyNumber];
// Finally, you could also do something that's syntactically more along the lines of Clojure/Racket's
// `match`, (link: https://github.com/clojure/core.match#example-usage)
// Here's a FizzBuzz example:
for (NSInteger num = 0; num < 100; num++) {
[matcher match:@[@(num % 3), @(num % 5)] clauses:@[
@[@0, @0]: ^(id obj) {
NSLog(@"FizzBuzz");
},
@[@0, ZSPatternAnyNumber]: ^(id obj) {
NSLog(@"Fizz");
},
@[ZPatternAnyNumber, @0]: ^(id obj) {
NSLog(@"Buzz");
}
];
}
// For doing transforms on matches, there could also be a replaceMatch:clauses
NSArray *transformed = [matcher replaceMatch:@[ @1, @2, @3 ] clauses:@{
ZSPatternAnyNumber: ^id(id obj) {
return @([obj integerValue] + 1);
}
}]; // => @[ @2, @3, @4 ]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment