António Pinto
[email protected]


http://realm.io/news/swift-enums-pattern-matching-generics/

Sign up to be notified of future videos

We won't email you for any other reason, ever.

Enums (0:40)

Enums are a kind of abstraction that allow you to give a variable one value 
among several related values. For example, when building a state machine you 
could have an enum that describes the state of an object from a set number of 
states. There are three types of enums in Swift.

Basic Enum (1:12)

The most basic Swift enum simply has a bunch of cases and is declared using the 
enum key word. In this example, the enum is called Direction and can only be 
one of the four provided cases. To declare an enum, you use the enum name, 
followed by a dot and then one of the possible values. Unlike in Objective-C or 
C, enums are not typedefs of aliases for integers.

enum Direction { 
  case North 
  case South 
  case East 
  case West 
}

let myDirection = Direction.North
Raw Value Enum (2:00)

The second type of Swift enum is a "raw value" enum, which is the same as the 
basic enum but has additional raw values associated with each case. In this 
enum Title, each case has an associated string that is the name for the title. 
Important to note is that the raw value and the enum itself are not 
interchangeable. The toRaw and fromRaw methods help to go between the value and 
the 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")!
For integer type raw enums, integer numbering remains implicit if you do not 
specify values. Swift automatically counts up from the last provided explicit 
value. This can be seen in the following example:

enum Planet : Int { 
  case Mercury = 1
  case Venus, Earth, Mars // 2, 3, 4 
  case Jupiter = 100
  case Saturn, Uranus, Neptune // 101, 102, 103 
}
Associated Value Enum (3:52)

The third type of enum is one with associated values. Each case in the enum can 
carry associated data. In this example from the Swift book, the bar code enum 
allows you to associate different QR codes with different strings.

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";)
Associated values are especially useful in handling JSON, because arrays and 
dictionaries are actually types. Thus, you can create an enum to represent a 
JSON structure as a tree, with JSON nodes wrapping different types.

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)])
Switch Statements (Pattern Matching) (6:17)

Switch statements is where most of the pattern matching functionality in Swift 
comes from. A basic switch statement looks very similar to an Objective-C 
switch statement, where there is a clause followed by cases. Two things to 
note: Swift switches don't have breaks, so for old style fallthrough behaviour, 
your code must have an explicit use of the keyword fallthrough. Switch 
statements must also be comprehensive, so default cases are required, which may 
help prevent errors.

let value = 10
switch value { 
case 10: println("ten") 
case 20: println("twenty") 
case 30: println("thirty") 
default: println("another number") 
}
Ranges (8:35)

In Swift, you can also pattern match on anything that's comparable. You can 
even match strings now to a case (and printout emojis for a fun parlor trick!). 
Anything that can be compared with the double equals operator can be matched. 
Swift also allows matching on ranges, where ranges can be the pattern inside a 
switch state.

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") 
}
Tuples (9:00)

Tuples are composite data types - they contain multiple elements that can all 
be of different types. A tuple is then represented by the types of its 
elements. In this tuple-based switch statement, a tuple is the input. Each 
element in the pattern tuple is actually a sub-pattern, so you can match 
against patterns in each element. The underscore represents a "I don't care" 
value. In this example, the last case functions as a default because the (_, _) 
says the same thing as a default - none of the other cases match, and we don't 
care about other possible values.

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") 
}
Binding in Cases (10:56)

If you have case statements, there may be associated data that needs to be used 
in the case statement body. The let binding is how you can do that. The first 
two cases in this example take the tuple elements and create the bindings X and 
Y to represent those elements.

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)"
}
Enums (12:22)

You can also switch on enums, but for enums with values wrapped inside, switch 
statements are the only way you can access those values. The following switch 
statement uses let to bind each case and use the value.

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)"
}
Types (13:20)

The main type of switch is the type/sub-class pattern. If your switch 
conditional in this clause is a class, then class will have sub- or 
superclasses. In this example, the UIView is matched against the different 
possible types of views: UIImageView, UILabel, and UITableView. The "as" 
keyword differs from the "is" keyword in that "as" allows you to use the let 
binding and do something with myView, while "is" is used simply to check the 
type.

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") 
}
where clause (14:33)

Another important thing about switch statements is the where cause. This clause 
can be added to any case and acts as a boolean expression that returns true or 
false. The case is then taken only if this where clause returns true. This 
switch example relies not on pattern matching but instead on these where 
clauses, following through with the case if myView is of a certain size or 
other characteristic.

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.") 
}
Expression Operator (15:28)

A final important feature in Swift is the expression operator.

func ~=(pattern: Type1, value: Type2) -> Bool
This operator takes the pattern and the value, and then uses pattern matching 
to return true or false. It can be overloaded as well to implement custom 
matching behaviour. The following piece of code is Swift pseudo-code to 
demonstrate what you could build. In an array four elements long, you could 
create cases that matched any combination of the elements.

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() 
}
Expression Patterns (17:42)

The following extended example is a custom implementation of the pattern match 
operator. This custom operator turns the elements of an array into the enum 
values.

enum WCEnum { 
  case Wildcard 
  case FromBeginning 
  case ToEnd 
  case Literal(Int) 
}

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
}
Protocols (21:06)

In order to understand generics, you have to understand protocols, which are 
something that existed in Objective-C as well. Protocols are basically a 
contract that can define nothing at all or any number of methods and 
properties. However, protocols can't have any implementation details - they can 
only have method signatures and property names. MyProtocol in this code defines 
just one property as requiring a getter and setter, as well as one method.

protocol MyProtocol { 
  var someProperty : Int { get set } 
  func barFunc (x: Int, y: Int) -> String
}
Types can conform to none, one, or multiple protocols. Protocols can also 
inherit from other protocols, thereby getting all the parent protocol's methods 
and properties.

Conformance (23:18)

With protocols, you can make classes, structs, and enums conform to them. By 
making a type conform to a protocol, you are telling the compiler that your 
type will match the definitions set in the protocol. MyClass conforms to 
MyProtocol in providing implementations that match.

class MyClass { 
  // Nothing here... 
  // This won't compile!
}

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 
  } 
  } 
}
Uses (24:06)

There are a few reasons you may want to use protocols. The first reason is 
parent-child relationships, where you don't want all the children to have a 
specific class of a parent. For example, UITableView comes with Cocoa and is a 
list of objects that specifies the number and content of cells. For the 
delegate to provide that information, it just has to implement the protocol 
which has methods for the cells. This way, you can avoid specifying the 
delegate as a sub-class of a class.

A second major use for protocols can be seen in the Swift Standard Library, 
where they add functionality to a type, piece by piece. Equatable, LogicValue, 
and AbsoluteValuable are all protocols that are implemented by different types 
in the library. The LogicValue protocol has a method that takes an object and 
turns it into true or false, so if you create a custom type and implement this 
protocol, you can use that type in an if statement's clause.

One final use for protocols is to allow different types to be described by the 
same generic type parameter. For example, if you wanted to put different types 
into an array, you could give the objects a protocol and then declare the array 
with the protocol. You then have an array of equatables and anything that 
conforms to the equatable in that array can be added.

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]
Generics (28:36)

Generics did not exist in Objective-C. THey are basically the statically typed 
languages' answer to the flexibility of dynamically typed languages. Generics 
are used to allow you to use the same code for different types. The type 
parameter allows you to do this generically.

func swapItems<T>(inout this: T, inout that: T) { 
 let tempThis = this 
 this = that 
 that = tempThis 
}
With generics, you can also enforce more constraints, as in the following 
example. The ": Equatable" allows you to constrain T to any type that conforms 
to Equatable, or is valid with the double equals operator.

func firstAndLastAreEqual<T : Equatable> 
 (someArray: Array<T>) -> Bool { 
 let first = someArray[0] 
 let last = someArray[someArray.count - 1] 
 return first == last 
}
You can also use generics for either functions or type declarations. Type 
information is available at runtime, which is helpful. The compiler can 
optimize by creating specific versions for each type that's being used (like 
C++ templates), but it can also fall back to the generic implementation.

Extended Example (33:56)

In the extended example, the goal was to create a function that was typesafe 
and could handle every case that involved the following types: Array, 
Dictionary, SquareMatrix, TreeNode. The function was to take a collection of 
one of the above types, take an array, and append the items in that collection 
to the array.

A protocol was defined to specify that a type can give back all of the elements 
inside of it. You have containers that can implement "AllElements". On a 
generic level, you might need to know the type of the elements inside the 
collection, and so a wildcard is called "ElementType". And so, you specify the 
element type when you implement the function itself.

protocol AllElements {
  typealias ElementType
  // Return an array containing all the objects 
  // in the collection
  func allElements() -> Array<ElementType> 
}
Another thing you can use is the NilLiteral protocol, the point of which is to 
take nil and turn it into something equivalent to nil in that type. For an int, 
it would return 0, or maybe for a string, it would return "". In the following 
example, you would want it to return something of type self.

protocol NilLiteralConvertible { 
  class func convertFromNilLiteral() -> Self
}
Extensions then add methods, computed properties, and protocol conformance to 
existing types. Array and SquareMatrix are both fairly straightforward - you 
just return the arrays themselves. For the Dictionary, you iterate through all 
the elements, create a buffer, and return it with the elements inside. Finally, 
for the tree, you go through the tree in a recursive traversal and again add 
the elements to a buffer.

Put together, the final function appendToArray calls AllElements and gives "a", 
which is an array with all the elements on the source. Then each of those 
elements is just added to the array in "dest". This works because the generic 
argument "T" has to implement AllElements, and U has been constrained to the 
same type as T. The where clause is included in case you want to do more than 
have this type implement one protocol. Before the where, you declare type 
arguments, which can conform to either one or none protocols. If you want T to 
conform to 2+ protocols, add a "where" clause to constrain. You can then 
constrain any free type arguments you declared earlier and any associated types 
associated with the type arguments via protocols (e.g. T.SomeType).

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)
Now you've created five methods for this one process. In actuality, you would 
have your containers implement Sequence and use a for-in loop to go through all 
the elements. This requires knowledge of Generators, EnumerateGenerators, and 
other material.

Q&A (46:05)

Q: In the extension example, are we extending all arrays of anything?
Austin: Yes, which is why this is a bad idea in practice. You can't actually 
say that you only want arrays with JSON type objects to also implement JSON 
array, which is needed to ensure that you have valid JSON.

Q: Is the type T built-in? Where does it come from?
Austin: T is actually coming from the declaration of array. If you 
command-click an array in XCode, the declaration of the array shows that it 
declares a generic argument T that has some constraints.

Q: Is there any kind of trace for generics and the way it works? Is it 
traceable to an existing language or is it brand-new?
Austin: I don't know the answer, but I'm sure there are precedents. Generics in 
Swift kind of de-emphasize object oriented programming because they're built 
more around protocols than around class hierarchies. You might get a closer 
procedent if you looked at Haskell.

Q: What is the exclamation point used for?
Austin: The exclamation mark is for optionals. Everything in Swift is 
non-nilable by default, so if you want to set something to nil, it must be 
declared as an optional. The question mark after the type signifies this. Then, 
the exclamation mark, or bang, takes the value out of the optional so you can 
use it.

Q: Is there something like value template parameters in Swift? Or should we 
just type in, type template?
Austin: I don't think so, I don't think Swift generics are as powerful.

Q: Can you explain let? It seemed like it would sometimes check the case and 
othertimes it would set it to the constant.
Austin: A let pattern kind of matches everything, but also takes a value and 
puts it in the constant for later use. When we use "as", it both checks the 
type and also puts it in the constant.

Sign up to be notified of future videos

We won't email you for any other reason, ever.


-- 
Recebeu esta mensagem porque está inscrito no grupo "Mailing List da Comunidade 
Portuguesa de Rich Internet Applications - www.riapt.org" dos Grupos do Google.

Para anular a subscrição deste grupo e parar de receber emails do mesmo, envie 
um email para [email protected].
Para publicar uma mensagem neste grupo, envie um e-mail para 
[email protected].
Visite este grupo em http://groups.google.com/group/riapt.
Para mais opções, consulte https://groups.google.com/d/optout.

Responder a