DSLs in Swift

DSLs in Swift

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

D200a17dd269fd4001bacb11662dab4b?s=128

Kyle Fuller

July 06, 2014
Tweet

Transcript

  1. DESIGNING | BUILDING DSLS IN SWIFT @KYLEFULLER

  2. None
  3. None
  4. BASE

  5. None
  6. SHAPE

  7. WHAT IS A DSL?

  8. DOMAIN SPECIFIC LANGUAGE

  9. PROGRAMMING LANGUAGE

  10. DECLARATIVE

  11. None
  12. SPECIFIC

  13. NOT GENERIC

  14. FREEDOM

  15. BUGS

  16. NSPredicate

  17. NAME EQUALS Kyle

  18. USING NSPredicate

  19. NSExpression *left = [NSExpression expressionForKeyPath:@"name"]; NSExpression *right = [NSExpression expressionForConstantValue:@"Kyle"];

    [NSComparisonPredicate predicateWithLeftExpression:left rightExpression:right modifier:NSDirectPredicateModifier type:NSEqualToPredicateOperatorType options:0];
  20. IMPERATIVE

  21. FRUSTRATING

  22. [NSPredicate predicateWithFormat:@"name == Kyle"];

  23. EASY TO WRITE BUGS

  24. [NSPredicate predicateWithFormat:@"nme == Kyle"];

  25. NSString *key = NSStringFromSelector(@selector(name)); [NSPredicate predicateWithFormat:@"%K == Kyle", key];

  26. INELEGANT

  27. DSLS

  28. [[Person name] equals:@"Kyle"]

  29. SWIFT

  30. CUSTOM OPERATORS

  31. (Something) == (Something else)

  32. / = - + * % < > ! &

    | ^ . ~
  33. Person.name == "Kyle"

  34. IDEA

  35. CORE DATA DSL

  36. MAKING A QUERY IN CORE DATA

  37. 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 { ; }
  38. 6 LINES

  39. Person.queryset(context) .orderBy(Person.name.ascending) .filter(Person.name == "Kyle")

  40. 1 LINE

  41. REFACTORING

  42. class Person : NSManagedObject { var name:String? - var age:NSNumber?

    }
  43. class Person : NSManagedObject { var name:String? - var age:NSNumber?

    + var birthday:NSDate? }
  44. BUILD SUCCESSFUL

  45. None
  46. *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'keypath

    age not found in entity <NSSQLEntity Person id=6>' *** First throw call stack: ( 0 CoreFoundation 0x00000001039e5495 __exceptionPreprocess + 165 1 libobjc.A.dylib 0x0000000102fe199e objc_exception_throw + 43 2 CoreData 0x0000000100b04438 -[NSSQLGenerator newSQLStatementForFetchRequest:ignoreInheritance:countOnly:nestingLevel:] + 904 3 CoreData 0x0000000100b03f6d -[NSSQLAdapter _newSelectStatementWithFetchRequest:ignoreInheritance:] + 365 4 CoreData 0x0000000100b03bd3 -[NSSQLCore newRowsForFetchPlan:] + 115 5 CoreData 0x0000000100b034a4 -[NSSQLCore objectsForFetchRequest:inContext:] + 516 6 CoreData 0x0000000100b03025 -[NSSQLCore executeRequest:withContext:error:] + 245 7 CoreData 0x0000000100b02b60 -[NSPersistentStoreCoordinator executeRequest:withContext:error:] + 3504 8 CoreData 0x0000000100b00a21 -[NSManagedObjectContext executeFetchRequest:error:] + 497 9 Fitness First 0x000000010021d89a -[KFObjectManager array:] + 138 10 Fitness First 0x00000001000c6396 -[FFGDashboardViewController viewWillAppear:] + 1190 11 UIKit 0x0000000101ae8db5 -[UIViewController _setViewAppearState:isAnimating:] + 422 12 UIKit 0x0000000101d42410 -[UIWindowController transition:fromViewController:toViewController:target:didEndSelector:animation:] + 5629 13 UIKit 0x0000000101aeffce -[UIViewController presentViewController:withTransition:completion:] + 4854 ) libc++abi.dylib: terminating with uncaught exception of type NSException
  47. Keypath age not found in entity <NSSQLEntity Person id=6>

  48. class Person : NSManagedObject { var name:String? - var age:NSNumber?

    + var birthday:NSDate? }
  49. NSSortDescriptor(key: "age", ascending: true)

  50. COULD WE DO THIS BETTER WITH A DSL?

  51. SAY NO TO HARD CODED STRINGS

  52. Person.age.ascending

  53. READABILITY

  54. SAFE

  55. None
  56. DSLS ARE !

  57. !

  58. DON'T ABUSE THE POWER Swift HAS GIVEN US.

  59. GOING TOO FAR

  60. BAD

  61. Person.name %= "Kyle"

  62. CONSIDERATION

  63. INTUITIVE

  64. >>> Person.age > 27 Age is more than 27

  65. >>> Person.name %= "Kyle" Name is ? Kyle

  66. >>> Person.name ~= "Kyle" Name is LIKE Kyle

  67. WHERE TO START?

  68. README

  69. None
  70. README

  71. PLAYGROUNDS

  72. QuerySet().filter(Person.name == "Kyle") .orderBy(Person.name.ascending)

  73. None
  74. 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) }
  75. class QuerySet { func orderBy(sortDescriptor:NSSortDescriptor) -> QuerySet { return self

    } func filter(predicate:NSPredicate) -> QuerySet { return self } subscript(range: Range<Int>) -> QuerySet { return self } }
  76. class Person { class var name:Attribute { return Attribute() }

    } QuerySet().filter(Person.name == "Kyle") .orderBy(Person.name.ascending)
  77. !

  78. WHAT'S NEXT?

  79. WRITING THE REAL IMPLEMENTATION

  80. None
  81. None
  82. None
  83. None
  84. None
  85. None
  86. 04:00 AM

  87. None
  88. None
  89. None
  90. None
  91. github.com/kylef/QueryKit

  92. twitter.com/kylefuller

  93. None
  94. github.com/kylef/QueryKit twitter.com/kylefuller