Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Swift Enums, Pattern Matching, and Generics

Swift Enums, Pattern Matching, and Generics

Slides for a talk on the Swift programming language's support for enumerated types, pattern matching (via the 'switch' statement), and generic programming.

Austin Zheng

August 21, 2014
Tweet

Other Decks in Technology

Transcript

  1. Basic Enum enum Direction { case North case South case

    East case West } ! let myDirection = Direction.North
  2. Raw Value Enum enum Title : String { case CEO

    = "Chief Executive Officer" case CTO = "Chief Technical Officer" case CFO = "Chief Financial Officer" } ! let myTitle = Title.CEO let myString : String = Title.CEO.toRaw() let anotherTitle : Title = Title.fromRaw("Chief Executive Officer")!
  3. Raw Value Enum (2) enum Planet : Int { case

    Mercury = 1 case Venus, Earth, Mars // 2, 3, 4 case Jupiter = 100 case Saturn, Uranus, Neptune // 101, 102, 103 } Int-valued enums count up implicitly
  4. Associated Value Enum enum Barcode { case UPCA(sys: Int, data:

    Int, check: Int) case QRCode(String) } ! let myUPC = Barcode.UPCA(sys: 0, data: 27917_01919, check: 2) let myQRCode = Barcode.QRCode("http://example.com")
  5. Associated Value Enum (2) enum JSONNode { case NullNode case

    StringNode(String) case NumberNode(Float) case BoolNode(Bool) case ArrayNode([JSONNode]) case ObjectNode([String:JSONNode]) } ! let x : JSONNode = .ArrayNode( [.NumberNode(10.0), .StringNode("hello"), .BoolNode(false)])
  6. Basic Switch Statement let value = 10 switch value {

    case 10: println("ten") case 20: println("twenty") case 30: println("thirty") default: println("another number") }
  7. Strings let e: String = "France" switch e { case

    "France": println("!") case "Germany": println(""") case "Italy": println("#") case "Russia": println("$") case "Spain": println("%") case "United Kingdom": println("&") default: println("Other") } (actually, any Equatable type)
  8. Ranges let v: UInt = 10 switch v { case

    0...9: println("Single digit") case 10...99: println("Double digits") case 100...999: println("Triple digits") default: println("4 or more digits") }
  9. Tuples let person = ("Helen", 25) switch person { case

    ("Helen", let age): println("Your name is Helen, and you are \(age)" + " years old") case (_, 13...19): println("You are a teenager") case ("Bob", _): println("You are not a teenager, but your name" + " is Bob.") case (_, _): println("no comment") }
  10. Binding in cases let myTuple = ("abcd", 1234) switch myTuple

    { case let (x, y): "The string in 'x' is \(x); " + "the integer in 'y' is \(y)" case (let x, let y): "Another way to do the exact same thing" case (_, let y): "We don't care about the string in 'x', " + "but the integer in 'y' is \(y)" }
  11. Enums enum ParseResult { case NumericValue(Int) case Error(String) } !

    let a = ParseResult.NumericValue(1) switch a { case let .NumericValue(v): "Success; numeric value is \(v)" case .Error(let err): "Failed; error message is \(err)" }
  12. Switching on Types let myView : UIView = getView() switch

    myView { case is UIImageView: println("It's an image view") case let lbl as UILabel: println("It's a label, with text \(lbl.text)") case let tv as UITableView: println("It's a table view, with" + " \(tv.numberOfSections()) sections") default: println("It's some other type of view") }
  13. where clause let myView : UIView = getView() switch myView

    { case _ where myView.frame.size.height < 50: println("Your view is shorter than 50 units") case _ where myView.frame.size.width > 20: println("Your view is at least 50 units tall," + " and is more than 20 units wide") case _ where myView.backgroundColor == UIColor.greenColor(): println("Your view is at least 50 units tall," + " at most 20 units wide, and is green.") default: println("I can't describe your view.") }
  14. Expression Operator switch someFoo { case pattern1: doSomething() case pattern2:

    doSomethingElse() // ... default: doDefault() } let r = someFoo if pattern1 ~= r { doSomething() } else if pattern2 ~= r { doSomethingElse() } // ... else { doDefault() } (sort of)
  15. What if…? let myArray = [1, 2, 4, 3] switch

    myArray { case [..., 0, 0, 0]: doSomething() case [4, ...]: doSomething() case [_, 2, _, 4]: doSomething() case [_, _, 3, _]: doSomething() case [_, _, _, 3]: doSomething() default: doDefault() }
  16. Expression Pattern func ~=(pattern: [WCEnum], value: [Int]) -> Bool {

    var ctr = 0 for currentPattern in pattern { if ctr >= value.count || ctr < 0 { return false } let currentValue = value[ctr] switch currentPattern { case .Wildcard: ctr++ case .FromBeginning where ctr == 0: ctr = (value.count - pattern.count + 1) case .FromBeginning: return false case .ToEnd: return true case .Literal(let v): if v != currentValue { return false } else { ctr++ } } } return true }
  17. Expression Pattern (2) let myArray = [1, 2, 4, 3]

    switch myArray { case [.FromBeginning, .Literal(0), .Literal(0), .Literal(0)]: "last three elements are 0" case [.Literal(4), .ToEnd]: "first element is 4, everything else doesn't matter" case [.Wildcard, .Literal(2), .Wildcard, .Literal(4)]: "4 elements; second element is 2, fourth element is 4" case [.Wildcard, .Wildcard, .Literal(3), .Wildcard]: "third element (out of 4) is 3" case [.Wildcard, .Wildcard, .Wildcard, .Literal(3)]: "fourth element (out of 4) is 3" default: "catchall" }
  18. Expression Patterns (3) let anotherArray = [2, 10 ,0, 0,

    0] switch anotherArray { case [<~nil, *0, *0, *0]: "last three elements are 0" case [*4, nil~>]: "first element is 4" case [nil, *2, nil, *2]: "4 elements; 2nd is 2, 4th is 4" case [nil, nil, *3, nil]: "third element (out of 4) is 3" case [nil, nil, nil, *3]: "fourth element (out of 4) is 3" default: "catchall" }
  19. Protocols? protocol MyProtocol { var someProperty : Int { get

    set } func barFunc (x: Int, y: Int) -> String } Property (get or get/set) Method
  20. Conformance class MyClass : MyProtocol { var myProperty : Int

    = 0 // Property conformance func barFunc (x: Int, y: Int) -> String { return "\(x) + \(y) = \(x + y)" } var someProperty : Int { get { return myProperty } set { myProperty = newValue } } }
  21. Uses? • Parent-child relationships 
 (e.g. UITableViewDelegate) • Aggregating functionality

    by composition
 (e.g. Equatable, LogicValue, AbsoluteValuable) • Allow disparate types to be described by the same generic type parameter (careful)
  22. Playing With Fire… protocol JSONType { } extension String :

    JSONType { } extension Float : JSONType { } extension Bool : JSONType { } extension Array : JSONType { } extension Dictionary : JSONType { } ! let b : Array<JSONType> = [10.1, 10.2, "foo"] let a : Array<JSONType> = [10.0, "bar", false, b] Note we can’t extend e.g. Array<JSONType>
  23. A Few More Notes… • Types can conform to zero,

    one, or multiple protocols • Protocols can ‘inherit’ from other protocols - they implicitly get their parent’s methods/properties • Protocols are not traits - can’t specify implementation
  24. Simple Example func swapInts(inout this: Int, inout that: Int) {

    let tempThis = this this = that that = tempThis }
  25. func swapItems<T>(inout this: T, inout that: T) { let tempThis

    = this this = that that = tempThis } Type Parameter
  26. Another Example func firstAndLastAreEqual<T> (someArray: Array<T>) -> Bool { let

    first = someArray[0] let last = someArray[someArray.count - 1] return first == last } How do we know the type ’T’ is equatable?
  27. func firstAndLastAreEqual<T : Equatable> (someArray: Array<T>) -> Bool { let

    first = someArray[0] let last = someArray[someArray.count - 1] return first == last } T is any type… …that conforms to Equatable
  28. Generics • Functions or types (e.g. classes) can be generic

    • Type information is available at runtime • Compiler can optimize by creating specific versions for common cases (C++ templates), and fall back to the generic implementation
  29. protocol InitProtocol { init() } ! struct SomeStruct<T : InitProtocol>

    { var x: T? = nil func instantiate() -> T { return T() } } ! struct AnotherStruct: InitProtocol { func favoriteString() -> String { return "reify me please" } } ! let c = SomeStruct<AnotherStruct>() let d = c.instantiate() d.favoriteString() ‘?’ just means this is allowed to be nil
  30. Our Goal • We want a function that can take

    a collection, take an array, and append the items in that collection to the array • We want this function to be typesafe • We want to write one function that handles every case • Array, Dictionary, SquareMatrix, TreeNode
  31. My Square Matrix struct SquareMatrix<T> { let d: Int; var

    backingArray: [T] init(dimension d: Int, initial: T) { self.d = d backingArray = [T](count: d*d, repeatedValue: initial) } subscript(row: Int, col: Int) -> T { get { return backingArray[row*d + col] } set { backingArray[row*d + col] = newValue } } }
  32. My Tree class TreeNode<T> { let value : T let

    leftNode : TreeNode? let rightNode : TreeNode? init(value: T, left: TreeNode?, right: TreeNode?) { self.value = value self.leftNode = left self.rightNode = right } } An aside: would have preferred to use enums.
  33. Let’s define a protocol… protocol AllElements { typealias ElementType //

    Return an array containing all the objects // in the collection func allElements() -> Array<ElementType> }
  34. Associated Types protocol AllElements { typealias ElementType func allElements() ->

    Array<ElementType> } Determined by what the conforming type returns for its implementation of allElements()
  35. Associated Types protocol FooProtocol { typealias SomeType func fooFunc() ->

    SomeType } ! struct Bar : FooProtocol { func fooFunc() -> String { return "bleh" } } Bar.SomeType == String
  36. Extend Array extension Array : AllElements { func allElements() ->

    Array<T> { return self } } Extension: add methods, computed properties, protocol conformance to existing type
  37. Extend Dictionary extension Dictionary : AllElements { func allElements() ->

    Array<ValueType> { var buffer = Array<ValueType>() for (_, value) in self { buffer.append(value) } return buffer } }
  38. Extend Tree extension TreeNode : AllElements { func allElements() ->

    Array<T> { var buffer = Array<T>() if leftNode { buffer += leftNode!.allElements() } buffer.append(self.value) if rightNode { buffer += rightNode!.allElements() } return buffer } }
  39. Put it all together… func appendToArray <T: AllElements, U where

    U == T.ElementType> (source: T, inout dest: Array<U>) { ! let a = source.allElements() for element in a { dest.append(element) } } ! var buffer = ["a", "b", "c"] appendToArray([1: "foo", 2: "bar"], &buffer)
  40. An Aside • In real life: use Sequence, for-in loop

    • See: Generator, EnumerateGenerator, etc • http://www.scottlogic.com/blog/2014/06/26/swift-sequences.html • Still have to implement Sequence for custom types…but you get all Sequence functionality
  41. Before ‘where’ • Declare type arguments (e.g. T, U) •

    In this declaration, a type argument can conform to zero or one protocols • If you want T to conform to 2+ protocols…add a ‘where’ clause to constrain
  42. What Can You Constrain? • Any free type arguments you

    declared earlier
 (e.g. T, U) • Any associated types associated with the type arguments via protocols (e.g. T.SomeType)
  43. How Can You Constrain? • Specify that a type implements

    a protocol 
 (e.g. T.SomeType : MyProtocol) • Specify that a class inherits from another class
 (e.g. T : SomeParentClass) • Specify equality of types
 (e.g. T.ContainerType == U.ContainerType)
  44. Revisiting ~= enum WCEnum<T : Equatable> { case Wildcard case

    FromBeginning case ToEnd case Literal(T) } ! func ~=(pattern: [WCEnum<T>], value: [T]) -> Bool { // ... }