A talk I given at SwiftCrunch http://swiftcrunch.com, the first ever hack day on Swift.
https://github.com/kylef/QueryKit https://twitter.com/kylefuller
DESIGNING | BUILDINGDSLS IN SWIFT@KYLEFULLER
View Slide
BASE
SHAPE
WHAT IS A DSL?
DOMAIN SPECIFICLANGUAGE
PROGRAMMINGLANGUAGE
DECLARATIVE
SPECIFIC
NOT GENERIC
FREEDOM
BUGS
NSPredicate
NAME EQUALS Kyle
USINGNSPredicate
NSExpression *left = [NSExpression expressionForKeyPath:@"name"];NSExpression *right = [NSExpression expressionForConstantValue:@"Kyle"];[NSComparisonPredicatepredicateWithLeftExpression:leftrightExpression:rightmodifier:NSDirectPredicateModifiertype:NSEqualToPredicateOperatorTypeoptions:0];
IMPERATIVE
FRUSTRATING
[NSPredicate predicateWithFormat:@"name == Kyle"];
EASY TO WRITEBUGS
[NSPredicate predicateWithFormat:@"nme == Kyle"];
NSString *key = NSStringFromSelector(@selector(name));[NSPredicate predicateWithFormat:@"%K == Kyle", key];
INELEGANT
DSLS
[[Person name] equals:@"Kyle"]
SWIFT
CUSTOMOPERATORS
(Something) == (Something else)
/ = - + * % <> ! & | ^ . ~
Person.name == "Kyle"
IDEA
CORE DATADSL
MAKING A QUERY INCORE DATA
let fetchRequest = NSFetchRequest(entityName: "Person")fetchRequest.predicate = NSPredicate(format:"name == %@", "Kyle")fetchRequest.sortDescriptors = [NSSortDescriptor(key: "name", ascending: true)]var error:NSErrorPointer?var objects = managedObjectContext.executeFetchRequest(fetchRequest, error:error!)if let objects = objects {;}
6 LINES
Person.queryset(context).orderBy(Person.name.ascending).filter(Person.name == "Kyle")
1 LINE
REFACTORING
class Person : NSManagedObject {var name:String?- var age:NSNumber?}
class Person : NSManagedObject {var name:String?- var age:NSNumber?+ var birthday:NSDate?}
BUILDSUCCESSFUL
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'keypath age not found in entity '*** First throw call stack:(0 CoreFoundation 0x00000001039e5495 __exceptionPreprocess + 1651 libobjc.A.dylib 0x0000000102fe199e objc_exception_throw + 432 CoreData 0x0000000100b04438 -[NSSQLGenerator newSQLStatementForFetchRequest:ignoreInheritance:countOnly:nestingLevel:] + 9043 CoreData 0x0000000100b03f6d -[NSSQLAdapter _newSelectStatementWithFetchRequest:ignoreInheritance:] + 3654 CoreData 0x0000000100b03bd3 -[NSSQLCore newRowsForFetchPlan:] + 1155 CoreData 0x0000000100b034a4 -[NSSQLCore objectsForFetchRequest:inContext:] + 5166 CoreData 0x0000000100b03025 -[NSSQLCore executeRequest:withContext:error:] + 2457 CoreData 0x0000000100b02b60 -[NSPersistentStoreCoordinator executeRequest:withContext:error:] + 35048 CoreData 0x0000000100b00a21 -[NSManagedObjectContext executeFetchRequest:error:] + 4979 Fitness First 0x000000010021d89a -[KFObjectManager array:] + 13810 Fitness First 0x00000001000c6396 -[FFGDashboardViewController viewWillAppear:] + 119011 UIKit 0x0000000101ae8db5 -[UIViewController _setViewAppearState:isAnimating:] + 42212 UIKit 0x0000000101d42410 -[UIWindowController transition:fromViewController:toViewController:target:didEndSelector:animation:] + 562913 UIKit 0x0000000101aeffce -[UIViewController presentViewController:withTransition:completion:] + 4854)libc++abi.dylib: terminating with uncaught exception of type NSException
Keypath age not found in entity
NSSortDescriptor(key: "age", ascending: true)
COULD WE DO THISBETTERWITH A DSL?
SAY NO TO HARDCODED STRINGS
Person.age.ascending
READABILITY
SAFE
DSLS ARE!
!
DON'T ABUSE THE POWERSwift HAS GIVEN US.
GOING TOO FAR
BAD
Person.name %= "Kyle"
CONSIDERATION
INTUITIVE
>>> Person.age > 27Age is more than 27
>>> Person.name %= "Kyle"Name is ? Kyle
>>> Person.name ~= "Kyle"Name is LIKE Kyle
WHERE TOSTART?
README
PLAYGROUNDS
QuerySet().filter(Person.name == "Kyle").orderBy(Person.name.ascending)
class Attribute {var ascending:NSSortDescriptor {return NSSortDescriptor(key: "x", ascending: true)}var descending:NSSortDescriptor {return NSSortDescriptor(key: "x", ascending: true)}}@infix func == (left: Attribute, right: AnyObject) -> NSPredicate {return NSPredicate(format: "x", nil)}
class QuerySet {func orderBy(sortDescriptor:NSSortDescriptor) -> QuerySet {return self}func filter(predicate:NSPredicate) -> QuerySet {return self}subscript(range: Range) -> QuerySet {return self}}
class Person {class var name:Attribute {return Attribute()}}QuerySet().filter(Person.name == "Kyle").orderBy(Person.name.ascending)
WHAT'S NEXT?
WRITING THEREALIMPLEMENTATION
04:00 AM
github.com/kylef/QueryKit
twitter.com/kylefuller
github.com/kylef/QueryKittwitter.com/kylefuller