• Tidak ada hasil yang ditemukan

Extending Swift Value(s) to the Server pdf pdf

N/A
N/A
Protected

Academic year: 2019

Membagikan "Extending Swift Value(s) to the Server pdf pdf"

Copied!
124
0
0

Teks penuh

(1)
(2)

Extending Swift Value(s) to the

Server

(3)

Extending Swift Value(s) to the Server by David Ungar and Robert Dickerson

Copyright © 2017 IBM Corporation. All rights reserved. Printed in the United States of America.

Published by O’Reilly Media, Inc., 1005 Gravenstein Highway North, Sebastopol, CA 95472.

O’Reilly books may be purchased for educational, business, or sales promotional use. Online editions are also available for most titles (http://safaribooksonline.com). For more information, contact our corporate/institutional sales department: 800-998-9938 or

corporate@oreilly.com.

Editors: Nan Barber and Susan Conant

Production Editor: Shiny Kalapurakkel

Copyeditor: Christina Edwards

Proofreader: Eliahu Sussman

Interior Designer: David Futato

Cover Designer: Karen Montgomery

Illustrator: Rebecca Panzer

(4)

Revision History for the First Edition 2017-01-25: First Release

The O’Reilly logo is a registered trademark of O’Reilly Media, Inc. Extending Swift Value(s) to the Server, the cover image, and related trade dress are trademarks of O’Reilly Media, Inc.

While the publisher and the authors have used good faith efforts to ensure that the information and instructions contained in this work are accurate, the publisher and the authors disclaim all responsibility for errors or omissions, including without limitation responsibility for damages resulting from the use of or reliance on this work. Use of the information and instructions contained in this work is at your own risk. If any code samples or other technology this work contains or describes is subject to open source licenses or the

intellectual property rights of others, it is your responsibility to ensure that your use thereof complies with such licenses and/or rights.

(5)

Preface: Swift for the Rest of

Your Application

Q: Why did the human put on his boxing gloves? A: He had to punch some cards.

Today’s applications do not run on a single platform. Rather, some parts run on resource-limited devices, and other parts run on a vast and mysterious cloud of servers. This separation has led to a schism in how we build these applications because different platforms have different requirements: the mobile portions must conserve battery power, while the server portions must handle a large number of requests simultaneously. Consequently,

programmers use different languages for different parts of applications — for instance, JavaScript for the browser, and Java for the server.

However, constructing an application out of multiple languages is fraught with drawbacks: different teams in the same organization speak different languages — literally — and must master different developer

ecosystems. Precious time must be spent translating concepts across language barriers and a few developers must know all of the languages in order to be effective. Test cases and data models must be replicated in different

languages, introducing bugs and incurring future maintenance efforts.

Because third-party libraries cannot be shared across groups, each team must learn different APIs to obtain merely the same functionality.

Swift was introduced by Apple in 2014 and replaced Objective-C as the recommended language for all new applications running on Apple devices. Later, when Swift became open source in 2015, it spread to new platforms. Currently, Swift is available on x86, ARM (including Raspberry Pi), and zOS architectures, as well as Linux, macOS, tvOS, watchOS, and iOS operating systems. So, it is now possible to write a whole end-to-end mobile

application — front-end, middle, back, and even toaster — all in Swift.

(6)

most likely writing in Java or JavaScript, to consider a switch to Swift. Why adopt Swift?

The Swift language may well be better than what you are currently using.

You can develop and debug in a consistent environment. Integrated development environments (IDEs) offer a tremendous amount of functionality such as text editing, static analysis, code completion, debugging, profiling, and even source-control integration. Switching back and forth between say, Eclipse and Xcode is a bit like switching between French horn and electric guitar: neither easy nor productive.

You can reuse code. When each bit of functionality is expressed exactly once, there is less work, more understanding, and fewer bugs.

You can leverage Swift’s features — such as optional types, value types, and functional programming facilities — to detect many bugs at compile time that would otherwise be hard to find.

Since Swift uses the LLVM compiler toolchain for producing native-code binaries, your applications have the potential for competitive performance in terms of speed, startup time, and memory usage.

However, examination of performance is outside the scope of this book.

You will find an active and approachable community of Swift developers who are creating web posts, books, and videos. In 2016, Swift was cited as the second “Most Loved” language in a

StackOverflow survey, and the third most upward trending technology.

(7)

CODING STYLE & IMPLEMENTATIONS

In the examples, the space constraints of this medium have led us to indent, break lines, and place brackets differently than we would in actual code. In addition, space has precluded the inclusion of full implementations and blocks of code in this edition contain inconsistencies in color and font. If the inconsistencies confuse you, please consult the repositories in Table P-1.

Table P-1. Where to find code examples

Repository name Referenced in

Book snippets Code snippets from the book

MiniPromiseKit Created in Chapter 3; used in Chapter 5 Pipes Used in Chapter 4

(8)

Acknowledgments

This book would not have been possible without the support, encouragement, and guidance of the IBM Cloud and Swift@IBM leadership team, including Pat Bohrer, Eli Cleary, Jason Gartner, Sandeep Gopisetty, Heiko Ludwig, Giovanni Pacifici, John Ponzo, and Karl Weinmeister. In addition, we want to extend our thanks to the many IBM Swift engineers and Swift community members working to bring Swift to the server — including Chris Bailey, Hubertus Franke, David Grove, David Jones, and Shmuel Kallner — for sharing their collective technical insights and creating the tools and libraries described herein. The Swift community’s embrace of Swift on the server reassured us that our contribution would be valued. The growing number of their instructive blog posts, videos, conference talks, and books have been of great help. We would like to thank our technical reviewers: Chris Devers, Shun Jiang, and Andrew Black. Nan Barber and the O’Reilly team had the daunting task of editing our lengthy technical drafts and producing this book. We owe a huge debt of gratitude to the Apple Core Swift Team for their courage, intelligence, talent, wisdom, and generosity for bringing a new language and ecosystem into existence and moving it to open source.

Language design involves many difficult and complex tradeoffs, and bringing a new language to the world requires a tremendous amount of work. The rapid acceptance of Swift by developers is powerful testimony to the quality of the language.

(9)

Chapter 1. A Swift Introduction

Swift supports several different programming paradigms. This chapter provides a brief overview of the parts of the Swift language that will be familiar to a Java or JavaScript programmer. Swift is not a small language, and this chapter omits many of its conveniences, including argument labels, shorthand syntax for closures, string interpolation, array and dictionary literals, ranges, and scoping attributes. Swift’s breadth lets you try Swift without changing your programming style while you master its basics. Later, when ready, you can exploit the additional paradigms it offers. A beginning Swift developer may initially be overwhelmed by the

cornucopia of features in the Swift language, since it gives you many ways to solve the same problem. But taking the time to choose the right approach can often catch bugs, shorten, and clarify your code. For instance, value

types help prevent unintended mutation of values. Paradigms borrowed from functional programming such as generics, closures, and protocols provide ways to factor out not only common code, but also variations on common themes. As a result, the underlying themes can be written once, used in

varying contexts, and still be statically checked. Your programs will be much easier to maintain and debug, especially as they grow larger.

(10)

Types and Type Inference

(11)

DECIPHERING TYPE ERRORS IN LONG STATEMENTS

(12)

Syntax

Swift’s syntax borrows enough from other languages to be easily readable. Here’s a trivial example:

let aHost = "someMachine.com"

aHost = "anotherMachine.com" // ILLEGAL: can't change a constant

aHost is inferred by Swift to be of type String. It is a constant, and Swift will not compile any code that changes a constant after it has been initialized. (Throughout this book, ILLEGAL means “will not compile.”) This constant is initialized at its declaration, but Swift requires only that a constant be

initialized before being used.

var aPath = "something" aPath = "myDatabase" // OK

aPath is also a String, but is a mutable variable. Swift functions use keywords to prevent mixing up arguments at a call site. For example, here is a function:

func combine(host: String, withPath path: String) -> String { return host + "/" + path

}

and here is a call to it:

// returns "someMachine.com/myDatabase"

combine(host: aHost, withPath: aPath)

(13)

Simple Enumerations

In Swift, as in other languages, an enumeration represents some fixed, closed set of alternatives that might be assigned to some variable. Unlike

enumerations in other languages, Swift’s come in three flavors, each suited for a particular use. The flavor of an enumeration depends on how much information its values are specified to include.

An enumeration may be specified by 1) only a set of cases, 2) a set of cases, each with a fixed value, or 3) a set of cases, each with a set of assignable values. (The last flavor is covered in Chapter 2.)

The simplest flavor merely associates a unique identifier with each case. For example:

enum Validity { case valid, invalid }

The second flavor of enumeration provides for each case to be associated with a value that is always the same for that case. Such a value must be expressed as a literal value, such as 17 or "abc". For example:

enum StatusCode: Int {

The value of this enumeration can be accessed via the rawValue attribute:

(14)

Tuples

As in some other languages, a Swift tuple simply groups multiple values together. For example, here’s a function that returns both a name and serial number:

func lookup(user: String) -> (String, Int) { // compute n and sn

return (n, sn) }

Tuple members can be accessed by index:

let userInfo = lookup(user: "Washington")

print( "name:", userInfo.0, "serialNumber:", userInfo.1 ) or can be unpacked simultaneously:

let (name, serialNumber) = lookup(user: "Adams")

print( "name:", name, "serialNumber:", serialNumber ) Members can be named in the tuple type declaration:

func lookup(user: String)

and then accessed by name:

let userInfo = lookup(user: "Washington")

print("name:", userInfo.name,

"serialNumber:", userInfo.serialNumber)

(15)

let second = lookup(user: "Adams")

second.name = "Gomez Adams" // ILLEGAL: u is a let

var anotherSecond = lookup(user: "Adams")

anotherSecond.name = "Gomez Adams" // Legal: x is a var

print(anotherSecond.name) // prints Gomez Adams

When you assign a tuple to a new variable, it gets a fresh copy:

var first = lookup(user: "Washington") var anotherFirst = first

first.name // returns "George Washington"

anotherFirst.name // returns "George Washington" as expected

first.name = "George Jefferson"

first.name // was changed, so returns "George Jefferson"

anotherFirst.name // returns "George Washington" because

// anotherFirst is an unchanged copy

first and anotherFirst are decoupled; changes to one do not affect the other. This isolation enables you to reason about your program one small chunk at a time. Swift has other constructs with this tidy property; they are all lumped into the category of value types. (See Chapter 2.) The opposite of a value type is a reference type. The only reference types are instances-of-classes and closures. Consequently, these are the only types that allow shared access to mutable state.

Tuples combine nicely with other language features: the standard built-in method for iterating through a dictionary uses key-value tuples. Also, Swift’s switch statements become very concise and descriptive by switching on a tuple:

enum PegShape { case roundPeg, squarePeg }

enum HoleShape { case roundHole, squareHole, triangularHole }

func howDoes( _ peg: PegShape, fitInto hole: HoleShape ) -> String

{

switch (peg, hole) { // switches on a tuple

case (.roundPeg, .roundHole): return "fits any orientation" case (.squarePeg, .squareHole): return "fits four ways" default:

(16)

} }

(17)

Custom Operators

As in some other statically-typed languages, Swift allows you to define your own operators based on static types.

One custom operator we’ll be using in our examples later is apply, which we’ll denote as |>. Like a Unix pipe, it feeds a result on the left into a function on the right:

9.0 |> sqrt // returns 3

This lets us read code from left to right, in the order of execution. For example:

send(compress(getImage()))

can be rewritten as:

getImage() |> compress |> send

To define this custom operator, you first tell Swift about the syntax:

precedencegroup LeftFunctionalApply { associativity: left

higherThan: AssignmentPrecedence lowerThan: TernaryPrecedence }

infix operator |> : LeftFunctionalApply

then provide a (generic) implementation:

func |> <In, Out> ( lhs: In, rhs: (In) throws -> Out ) rethrows -> Out {

return try rhs(lhs) }

(18)

Closures

(19)

DECIPHERING TYPES ERRORS IN CLOSURES

Most of the time, the Swift compiler can infer the types of closure arguments and results. When it cannot, or when the code includes a type error, the error message from the compiler can be obscure. You can often clarify type errors by adding types to a closure that are not strictly necessary. In the example below, if the compiler were to complain about a type, you could add the (previously implicit) types:

func makeChannel()

-> ( send: (String) -> Void, receive: () -> String ) {

var message: String = "" return (

send: { (s: String) -> Void in message = s }, receive: { (_: Void ) -> String in return message } )

(20)

Object Orientation

(21)

Protocols Define Interfaces

As with other typed languages, Swift includes a notion that the expected behavior of an entity is separate from any particular embodiment of that entity. The former is called a protocol and the latter a concrete type — think interface versus class if you’re a Java programmer. A Swift protocol lets you define what is expected of a type without specifying any implementation. For example, the operations expected of any HTTP_Request might be that it can supply a URL and a requestString:

protocol HTTP_Request_Protocol { var url: URL {get} var requestString: String {get} }

In other languages, Abstract_HTTP_Request might need an abstract requestString. But in Swift, the protocol serves that purpose:

class Abstract_HTTP_Request {

let url: URL // A constant instance variable

init(url: URL) { self.url = url } }

class Get_HTTP_Request:

Abstract_HTTP_Request, HTTP_Request_Protocol {

var requestString: String { return "GET" } }

class Post_HTTP_Request:

Abstract_HTTP_Request, HTTP_Request_Protocol {

When declaring a variable to hold a request, instead of using the type

(22)

let aRequest: HTTP_Request_Protocol

= Get_HTTP_Request(url: … /* some URL */) aRequest.requestString // returns "GET"

(23)

Generic Protocols

Generic entities allow the same basic code to apply to different types. In addition to concrete types, Swift also allows protocols to be generalized to different types, although the mechanism differs.

For example, suppose you have two responses, a TemperatureResponse and a FavoriteFoodResponse:

struct TemperatureResponse { let city: String

let answer: Int

}

struct FavoriteFoodResponse { let city: String

let answer: String

}

Even though each answer is a different type, they can share the same description by adopting a common protocol:

protocol ResponseProtocol { associatedtype Answer var city: String {get} var answer: Answer {get} }

struct TemperatureResponse: ResponseProtocol { let city: String

let answer: Int

}

struct FavoriteFoodResponse: ResponseProtocol { let city: String

let answer: String

}

(24)

Unfortunately, generic protocols such as this one are more difficult to use than nongeneric ones. Specifically, they cannot be used in place of types, but only as generic constraints. So, you cannot write a declaration to hold a value that conforms to ResponseProtocol:

var someResponse: ResponseProtocol // ILLEGAL

But you can write a function that will work on any type that conforms to ResponseProtocol:

func handleResponse <SomeResponseType: ResponseProtocol> ( response: SomeResponseType ) { … }

Because a generic protocol cannot be used as a type, it is often helpful to split up a generic protocol into generic and nongeneric protocols. A full discussion of these generic protocols is beyond the scope of this book. Generic protocols support generic functions, structures, and objects by providing a way to

(25)

Extending Classes, Structures, and

Enumerations

Like many other languages, Swift allows you to add new behavior to a

preexisting construct. In Swift, this capability is called an extension, and can be used with classes, structures, enumerations, and protocols. The last case is a bit different because protocol extensions supply default behavior, just as method bodies in Java interfaces do.

As you might expect, Swift’s extension facility is especially useful for large programs because the entity you want to extend is likely to have been defined in a separate library. You might not even have source code for it! Less obviously, Swift’s extensions help in two other situations:

1. An extension can add a bit of specialized functionality that is only visible within a single file. Suppose that in some computation you find yourself squaring a number often, such as (a/b) * (a/b) + (c/d) * (c/d). You could add the following to the file containing that code:

private extension Int {

var squared: Int { return self * self } }

Now you can rewrite the above as (a/b).squared +

(c/d).squared. The extension adds a new member to Int without cluttering up its namespace everywhere.

2. You might have a set of classes where each performs the same set of functions. Extensions let you group the code by function as opposed to class. An extension need not be in the same file or even the same module as the original definition. For example, you might have classes for city and state that each perform a country lookup:

(26)

let name: String

init(name: String) { self.name = name } func lookupCountry() -> String { … } }

class State {

let name: String

init(name: String) { self.name = name } func lookupCountry() -> String { … } }

Extensions let you group the lookup functions together:

extension City { func lookupCountry() -> String { … } } extension State { func lookupCountry() -> String { … } } This lets you put functionality where it makes the most sense, whether in a type defined by a library, limited to the scope of a single file, or together with similar functionality for different types.

Unlike Smalltalk, Swift closures cannot return from the home method’s scope (a.k.a., nonlocal return), so they cannot be used to extend the built-in control structures.

(27)

Chapter 2. Optional Types,

Structures, & Enumerations

Programming is hard and debugging is harder, but maintaining and debugging large programs that run asynchronously and concurrently is hardest. It makes sense to place the burden of checking certain runtime

properties of your program on the compiler rather than the developer. Swift’s optional types and structures let you tell the compiler more about your

(28)

Optional Types Exterminate Nil-Value Bugs

Programs represent uninitialized or absent values with nil (a.k.a., null). If your code fails to handle a nil value anywhere one can occur, bad things can happen. So Swift incorporated the might-be-nil versus can-never-be-nil distinction into its static type system. These are called “just” and “maybe” in Haskell. For example, suppose you are trying to extract the “Content-Type” entry from the header fields of an HTTP request. You have the header fields represented as a dictionary with String keys and values:

let headerFields: [String: String] = …

Swift uses subscript notation to look up the value of a given key in a dictionary:

let contentType = headerFields["Content-Type"]

and Swift will infer a type for contentType. But that type is

not “String”! It is “String?” with a question mark, because String represents a value that can never be nil, whereas String? represents a value that be either a String or nil. The latter is called an optional type. The dictionary lookup returns an optional type because the dictionary might not contain a key for “Content-Type.” The type String? is not the same type as String and Swift won’t let you use the value of an optional type without an explicit check:

if contentType.hasPrefix("text") // ILLEGAL

There are many convenient ways to perform this check. For example, the if-let form checks if the value is nonnil. If so, it assigns the value to a new variable that is local to the then-part. If the value is nil, it executes the else-part, if any.

(29)

if let ct = headerFields["Content-Type"] { contentType = ct

} else {

contentType = "no contentType" }

The ?? nil-coalescing operator allows you to substitute a value for nil. It only evaluates the expression on the right if the one on the left is nil. For example, you might use ?? to supply a default value for a missing dictionary entry:

let contentType = headerFields["Content-Type"] ?? "none"

Swift’s treatment of nil values will significantly improve the quality of your programs over many other languages. Swift’s optionals add security without inconvenience.

SURPRISINGLY HELPFUL, A PERSONAL NOTE FROM DAVID

(30)

Structures Isolate Mutation

A Swift structure (struct) is like an instance of a class: it groups values together, the values can be fixed or variable, and it includes methods that operate on those values. However, unlike an instance of a class, a structure is a value type. Assigning a structure to a variable creates a fresh copy,

(31)

STRUCTURE MUTATION GUARANTEES A structure provides two guarantees about its mutability:

1. When passed to another routine or placed in another variable, the original structure is insulated from any changes to the passed structure and vice versa.1 2. When a structure is associated with an identifier via a let statement, the value

of the identifier may not change.

Using structures can prevent bugs. Suppose you need to send a request to a database to find out the current temperature in a given city, and you also need to check and see how long the database took. You could use a class with an instance variable, startTime:

class TemperatureRequestClass { let city: String

After creating a request object:

let request = TemperatureRequestClass(city: "Paris")

you might hand it off to be processed:

request.startTime = Date.now

sendToDB(request: request, callback: receivedResponse)

and later print the time difference:

func receivedResponse(temperature: Int) {

let dbTime = Date.now.timeIntervalSince(request.startTime!) print("It took", dbTime,

(32)

request.city, "is", temperature) }

But if there’s a bug in that sendToDB routine, it could corrupt the startTime!

func sendToDB(

request: TemperatureRequestClass, callback: (Int) -> Void

Now your requestTime calculation would be wrong. (See Figure 2-1.)

Figure 2-1. Because instances of classes are reference types, when request is passed to sendDB that function gets a reference to the same object. Then when it incorrectly mutates startTime, the original

object is corrupted.

Swift provides a better way — using a structure instead of a class:

(33)

Because your calling code alters the startTime, which is contained in a structure, it must put that structure in a var, not a let:

var request = TemperatureRequestStruct(city: "Paris")

Now, the Swift compiler catches the error before the program can even run!

func sendToDB(

request: TemperatureRequestStruct, callback: (Int) -> Void

) {

// Do lots of slow work to prepare to connection

request.startTime = Date.now // ILLEGAL: will not compile!!

// Send the request on the prepared connection

}

Why is this assignment an error? Function parameters are lets by default, and the let-ness of the request parameter “flows down” into the

startTime field.

But what if you see the compile error and try a quick fix to get your code through the compiler?

var mutableRequest = request

mutableRequest.startTime = Date.now

Swift will still protect your code from the bug because even though

(34)
(35)

Mutating Methods

Unlike any other value type, a structure can include methods that mutate the contents of var fields in the structure. As a helpful signal to the programmer, such methods must be annotated with mutating. This requirement

highlights the places where side effects could occur:

struct TemperatureRequestStruct { …

var startTime: Date? = nil

mutating func clearStartTime() { startTime = nil } }

Since a let prohibits mutation, a mutating function may be invoked only upon a var. Unlike mutating an instance variable of a class, a mutation to a structure only changes the copy referenced by one variable:

let request1 = TemperatureRequestStruct(city: "Paris")

var request2 = request1 // makes a copy because is a struct

request1.clearStartTime() // ILLEGAL: cannot mutate a let

(36)

STRUCTURES CONTAINING REFERENCES TO OBJECTS

A structure may contain a reference to an instance of a class, and vice versa. If a

structure contains such a reference, some of the desirable properties of a value type may be lost. In particular, if a structure includes an attribute that computes its result by querying a class instance, the result may be subject to unexpected changes. This problem is transitive: it can occur even if your value type contains a value type from a library and that type contains a reference to a class instance.

(37)

Default Implementations with Protocol Extensions

Swift classes can inherit from each other, but if you want to factor out

behavior that two structures share, instead of creating a super-class, you must use a protocol extension. This technique is sometimes called

protocol-oriented programming. For example, suppose you write two similar structures, a temperature request and an ozone-level request:

struct TemperatureRequest { let city: String

struct OzoneRequest {

let city: String

Both structures have identical cityAndState properties that return a

string combining the city and state. You can factor out the common code with a protocol and a protocol extension. First, the Request protocol ensures that the properties used by the common code, in this case city and state, are implemented by the adopter of the protocol. Then the protocol extension adds the new cityAndState property to all objects that conform to the

(38)

protocol Request {

var city: String {get} var state: String {get} }

extension Request {

var cityAndState: String { return city + ", " + state } }

struct TemperatureRequest: Request { let city: String

struct OzoneRequest: Request { let city: String

A protocol extension cannot add new requirements to a protocol, nor can it add additional data fields to an object; all it can do is add implementations of new behavior to the types that adopt the protocol. (A full description of

protocol extensions is outside the scope of this book; see The Swift Programming Language (Swift 3).)

What if you need to express a multilevel hierarchy? Protocols can inherit from other protocols, but it’s not quite the same as subclassing — for example, there is no super.

(39)
(40)

Enumerations with Associated Values

In Swift, there is usually more than one way to express a given concept. For instance, you can choose between a class and a structure. Moreover, Swift includes many features from the functional programming school. Judiciously exploited, these constructs can clarify, shorten, and generalize your code. Like color TV, air conditioning, or the smartphone, you may never have missed this next construct, yet may soon find you would not think of living without it. One of these is the enumeration with associated values.

As described in “Simple Enumerations”, a Swift enumeration represents a value taken from a fixed set — for example, one of a fixed set of HTTP requests:

enum HTTP_Request_Kind {

case get, post // other request types omitted for brevity

}

But Swift enumerations can do so much more because any case can also include one or more associated values. Such an enumeration is more like a discriminated union than a Java or C enumeration. It can replace a small class hierarchy or a group of structures that implement a common protocol.

For example, suppose you need to implement HTTP requests with an equality (==) operation. You could create a class hierarchy, with an abstract

superclass containing common code and concrete subclasses for each kind of request. But since you don’t want any asynchronous code to mutate requests, you would do better to use structures and a protocol. The protocol would have to be generic (because a requirement of == references the associated type, Self). However, recall that Swift disallows the use of a

generic protocol as a type:

protocol HTTP_Request {

static func == (a: Self, b: Self) -> Bool

}

(41)

let someRequest: HTTP_Request = ... // ILLEGAL

let requests: [HTTP_Request] = ... // also ILLEGAL

So you could neither declare a variable that could contain any kind of request, nor an array that could contain a mixture of requests.

However, since the set of requests is fixed, there is no need to allow others to create new kinds of requests. Thus, an enumeration with associated values is the best fit.

enum HTTP_Request {

(42)

// also delete, put & patch

default:

return false }

} }

Now that an HTTP request is represented by an enumeration, there is a single concrete type, HTTP_Request, that subsumes every kind of request. As a consequence, the same array can hold any kind of HTTP_Request:

let requests: [HTTP_Request] = [

.get (destination: url1, headerFields: [:]),

.post(destination: url2, headerFields: [:], data: someData) ]

and you can test different kinds of requests for equality with each other:

(43)

Enumerations as Inside-Out Class Hierarchies

When you replace a class hierarchy with an enumeration, it’s like turning the hierarchy inside out: the hierarchy first divides the code into various types, such as the request types above; then, within each class the methods divide up the code by function. But the methods of an enumeration first divide up the code by function, and the switches within each function then divide up the code by case.

The enumeration isn’t always better than the hierarchy. There are drawbacks to using an enumeration:

More code

Instead of implicit dynamic dispatching within a class hierarchy, you must write explicit switch statements.

Less extensibility

You can add a subclass to a class that was defined in a different file, but you cannot add a case to an enumeration that was defined in a different file. (This restriction can be a benefit where appropriate.) An

enumeration is favored when the set of cases is less likely to expand later; a class hierarchy is favored when the set of cases is more likely to expand, or be expanded by importers of your library.

Awkward for common data

A superclass or protocol offers better support than an enumeration for data attributes that are common to all the alternatives. It may be better to use a structure holding the common attributes and an enumeration for the specifics.

As you get used to Swift, you will find more and more uses for generic enumerations with associated values.

(44)

Choosing an Aggregate

Tuples, classes, structures, protocols, enumerations: Swift offers many

(45)
(46)
(47)

Instantiate a Class for a Thing with an Identity

Use an instance of a class to represent a thing in the real world with an

identity that persists as its state changes. For example, to model a user whose identity remains the same as his location changes, you use a class:

class User {

let name: String // the User’s name can not change

var location: String // the User’s location can change

init( name: String, location: String ) { self.name = name

self.location = location }

}

(48)

Otherwise, Replace the Class with a Value Type

An instance of a class is passed by reference: any portion of your program with that reference can couple to any other portion with that reference by changing a field in that instance. In contrast, a value type is passed by value: handing off a value type to another portion of your program (or a library) does not create the potential for future interaction.

The potential for interaction impedes the prevention and elimination of bugs. Most bugs surface as undesired behavior resulting from unexpected values in data structures. Fixing the bug requires you to find the statement that wrote the bad value. But if the bad value occurs in an instance of a class, you must examine every place in the program that could possibly refer to that instance. Even if the fault turns out to be in a part of the program that is

obviously related to the bug, the mere chance that it could be in some

unrelated part imposes significant practical and psychological overhead in the search for the offending code.

Value types help with both sorts of reverse reasoning. They help to get from crash to offending code by reducing coupling. With value types, your

(49)

VALUE TYPE MUTATION RESTRICTIONS The values associated with an instance of an enumeration cannot change. The immutability implied by a let constant holding a tuple or structure flows down into its fields.

Each time a tuple or structure is passed to another variable it is copied. The two variables are decoupled unless passed via in-out, and in that case the caller cannot pass a constant and must mark the variable to be passed with an ampersand prefix. Any member function that mutates a structure must be specially marked and cannot be invoked on a constant.

If you are currently using a language with little support for optional types or value types, your code will benefit from moving to Swift and using them wherever you can. The assurances they provide about freedom from nil values, mutation, and coupling will help you write cleaner code. This code will be easier to reason about, maintain, and debug.

Unless it is passed in-out, in which case the caller must mark it with an ampersand.

Swift does include a way to pass in a structure so that the callee can mutate it — in-out parameters. But the call site looks different when passing such a parameter, so that you need only inspect the local part of the code to see that there might be a possibility of mutation.

(50)

Chapter 3. Swift Promises to

Tame Asynchrony

Server-side applications typically make requests to other web services or databases, and these requests are usually asynchronous; the application does not stop to wait for the result to return. These requests may succeed or fail, and often the result contains the input to the next asynchronous request. For example, consider an application that can find the temperature at the user’s home. The application makes a request to a database to discover where the user lives, then makes a subsequent request to a weather service to get the temperature in that location. But if the database is missing an entry for a user, the subsequent request to the weather service must be skipped. In addition to sequencing the chain of asynchronous operations, an application must vary the sequence to cope with errors. The required logic can be a challenge to program.

This chapter shows how Swift’s support for first-class functions can be harnessed to implement promises, one of the most effective constructs for programming such chains. After reading this chapter, you will understand how promises work, and why they are so beneficial. To illustrate this, we have implemented a very basic version of PromiseKit

called MiniPromiseKit that you can clone, refer to, and use in your projects. This chapter explains promises in three incremental steps:

1. Synchronous operations are linked together in a chain, each operation depending on the previous one, and each potentially throwing an exception. By using the Result enumeration, the divergent control flow that results from handling exceptions gets restructured into a simpler linear data flow that is easier to follow.

(51)

3. Steps 1 and 2 converge, combining Result and

BasicPromise into Promise. This synthesis simplifies and clarifies the construction of chains of asynchronous operations that cope with errors at any stage.

(52)

PROMISEKIT CONVENTIONS

Faced with many different names for the concepts in this chapter, we chose the terminology used in PromiseKit because it is one of the most popular libraries in this domain. It is a third-party, open-source library written by Max Howell and available at

(53)

Step 1: Chaining Synchronous Errors

To set the stage for programming chains of asynchronous requests, you encapsulate exceptions thrown by synchronous calls. For example, suppose you need to print out the ambient temperature at a user’s home. You are composing two calls: one that returns the city where a user lives, and another that returns the temperature of a given city. Each operation can fail by

throwing an exception:

enum City { case Austin, Mountain_View, Podunk }

enum Errors: String, Error { case unknownUser, unknownCity } func getCity(of user: String) throws -> City {

switch user {

case "Rob": return .Austin

case "David": return .Mountain_View case "John": return .Podunk

default: throw Errors.unknownUser }

}

func getTemperature(in city: City) throws -> Int {...}

Since each call returns a unique exception, you coalesce the exception handling into a single catch block that reports a specific error:

do {

let city = try getCity(of: user)

let temperature = try getTemperature(in: city) print("temperature:", temperature)

}

catch { print("error:", error) }

In the preceding code, the getCity and getTemperature functions either return an answer or throw an exception. In turn, each of their call sites must implicitly test for the exception and conditionally branch to the catch block. But in order to prepare for asynchronous requests, you need to

(54)

successful case fulfilled and the type of that value FulfilledValue. Call the error case rejected. It will always have type Error.

How do you implement Result? Use a Swift generic enumeration with associated values.1 Just as with a generic class, a generic enumeration can handle a multitude of result types, including in this example both City and Int. Here is the enumeration with its cases:2

enum Result<FulfilledValue> {

case fulfilled(FulfilledValue) case rejected(Error)

}

The last argument to the initializer for Result is a closure, which the initializer invokes. If the closure returns normally, the initializer creates a fulfilled Result that includes the closure’s return value. If the closure throws an exception, the initializer creates a rejected Result that

includes the error:

extension Result {

init( of body: () throws -> FulfilledValue ) { do { self = try .fulfilled( body() ) } catch { self = .rejected ( error ) } }

}

Using trailing closure syntax to omit the parentheses, a Result might be created as follows:

(55)

SUCCESS CAN BE VACUOUS

Since Swift treats the Void type consistently, Result can also be used with a closure that does not return anything, such as Result() {}.

Now that you have a Result<City>, use it to get the temperature for a city. Although you could write a switch statement for this particular chain:

func getTemperatureFromCity( aResult: Result<City> ) -> Result<Int>

You can do better by encapsulating the switch statement inside a reusable member function of Result:

extension Result {

func then <NewFulfilledValue> ( execute body:

(FulfilledValue) throws -> NewFulfilledValue ) -> Result<NewFulfilledValue>

{

Now the chain can be written more simply:

(56)

.then { try getTemperature(in: $0) } .then { print("temperature:", $0) }

If getCity encounters an error, the calls to getTemperature and print will never occur because getCity returns a rejected case. To process rejected results, two methods, recover and catch,3 respectively, allow errors to be replaced with successful results or merely caught:

extension Result {

func recover(execute body: (Error) throws -> FulfilledValue) -> Result

For example, if you were willing to assume that any unknown users lived in Austin, you could situate them there with a recover:

// Type inference allows omission of <City> after Result below:

Result { try getCity(of: user) } .recover { _ in .Austin } .then { try getTemperature(in: $0) } .catch { print("error:", $0) } .then { print("temperature:", $0) }

(57)

as branching into recovery code if the first link failed, and bypassing the temperature query if the recovery code failed. The code would be

complicated and obscure; it would be easy to make mistakes. Result modularizes the control flow: it is linear at this level, from recover to then to catch to then. It is only inside of these methods that the control flow branches. Result exploits Swift’s support for higher-order functions to pass the work required to handle each eventuality (as closures) into other functions (then, catch, and recover). Instead of conditionally branching at the top level, the enumeration carries the information as data.

FUNCTIONS OPERATING ON FUNCTIONS

Result encapsulates control flow by providing functions that operate on functions. Such functions that operate on functions instead of data are called "higher-order functions.” These functions-on-functions are a hallmark of functional programming and can be a bit mind-bending at first, but once understood, they can do a lot to improve the quality of your code. To stave off cognitive overload, you can try out these functions-that-wrap-functions a little at a time, first to defend against nils (for instance, by using map or flatMap), then to untangle error handling. As code gets moved into higher-order functions, the compiler helps out more and more to simplify the code and prevent bugs.

(58)

Step 2: Linearizing Nested Callbacks

(59)

Composing Asynchronous Operations

Asynchronous operations are usually programmed with callbacks, and when an operation depends on the result of a previous operation, those callbacks must be nested. But this nesting obfuscates the sequence of operations. For example, suppose getting the user’s city and that city’s temperature require asynchronous operations with callbacks:

func requestCity( of user: String,

_ callback: @escaping (City?, Error?) -> Void

) {

DispatchQueue.global(qos: .userInitiated).async { switch user {

case "Rob": callback(.Austin, nil) case "David": callback(.Mountain_View, nil) case "John": callback(.Podunk, nil)

(60)

} else {

print("error:", $1) }

}

(61)

Replacing Callbacks

In order to get rid of nested callbacks, an asynchronous operation must return some thing that can be fed to the next operation. That thing must be

something that can be created and returned before the first asynchronous operation completes. That way, it can be passed on to the next operation without being nested inside the first operation. This thing is commonly called a future or a promise. It works like a Unix pipe between two threads: the first thread creates the pipe and immediately returns it. Then, the second thread receives the pipe and waits for some data. Later, the first thread computes its data and sends it down the pipe. The pipe wakes up the second thread and presents it with the data from the first thread.

For now, defer error-handling considerations to “Step 3: Coping with

Asynchronous Errors”, so define a BasicPromise, which does not account for errors. Recall that Result is generic because the value for success can be any type, but that type can be determined statically. BasicPromise is also generic for the same reason. Call the type of the value for

(eventual) success Outcome. Because a

shared BasicPromise communicates the readiness of a result, it must be a class: classBasicPromise<Outcome>.

BasicPromise has three fundamental operations4: Creating

A BasicPromise object:

let aBasicPromise = BasicPromise<Int>()

Fulfilling the promise

When an outcome becomes available, fulfill the promise with the outcome:

(62)

Chaining one promise to another

The code that consumes the outcome of a fulfilled promise goes in the body of a then. Although this body is called back, it does not itself invoke the next step. Instead, it returns a new value, which could be another BasicPromise, to continue the chain. Here is a simple example using then:

aBasicPromise .then {

// asynchronous request returning a BasicPromise

}

(63)

SPECIFYING A DISPATCHQUEUE

Since then runs its body asynchronously, you might want the body to run on a specific DispatchQueue. Methods such as then take an optional argument to allow you to specify a DispatchQueue. In PromiseKit, the DispatchQueue

defaults to .main, which can often result in a deadlock. We suggest

.userInitiated instead.

There are three versions of then:

1. The simplest one takes a body with no return value and calls it when the original BasicPromise is fulfilled:

func then(

on q: DispatchQueue = BasicPromise.defaultQueue, execute consumer:

@escaping (Outcome) -> Void

)

2. Sometimes the body synchronously processes the result of the original BasicPromise. In such a case, the then operation needs to continue the chain by returning a new

BasicPromise for the transformed result:

func then<NewOutcome> (

on q: DispatchQueue = BasicPromise.defaultQ, execute transformer:

@escaping (Outcome) -> NewOutcome

) -> BasicPromise<NewOutcome>

(64)

However, the BasicPromise returned by the then method cannot be the same one that its body returns because the then method must return something immediately, so then returns a new BasicPromise, which will eventually be fulfilled with the right result:

func then<NewOutcome>(

on q: DispatchQueue = BasicPromise.defaultQ, execute asyncTransformer:

@escaping (Outcome)

-> BasicPromise<NewOutcome> ) -> BasicPromise<NewOutcome>

In order to use BasicPromises, you will have to wrap each request so that it returns a BasicPromise result instead of receiving a callback argument. For example, ignoring errors for now, assume there is an existing function that has a callback, requestCityIgnoringErrors:

func requestCityIgnoringErrors( of user: String,

callback: @escaping (City) -> Void

) {

// Simulate the asynchrony of a web request

DispatchQueue.global(qos: .userInitiated).async { switch user {

case "Rob": callback(.Austin)

case "David": callback(.Mountain_View) case "John": callback(.Podunk)

default: abort() }

} }

Although you could write a general wrapper for turning a function that

receives a callback into one that returns a promise, skip that step for now and just write two wrappers for this running example:

func requestCityIgnoringErrors(of user: String) -> BasicPromise<City>

{

let promise = BasicPromise<City>()

(65)

return promise }

// Also wrap the temperature request:

func requestTemperatureIgnoringErrors(in city: City) -> BasicPromise<Int>

{…}

Using BasicPromises, the composition of asynchronous queries becomes just as simple as that of synchronous ones:

requestCityIgnoringErrors(of: user)

.then { requestTemperatureIgnoringErrors(in: $0) } .then { print("Temperature is", $0) }

(66)

Step 3: Coping with Asynchronous Errors

(67)

Promising Either Success or Failure

You could handle asynchronous errors by using a Result enumeration as the Outcome of a BasicPromise:

func requestCity(of user: String) -> BasicPromise<Result<City>> {

let promise = BasicPromise<Result<City>>() // Simulate the asynchrony of a web request

DispatchQueue.global(qos: .userInitiated).async { switch user {

promise.fulfill(.rejected(Errors.unknownUser)) }

}

return promise }

func requestTemperature(in city: City) -> BasicPromise<Result<Int>>

{…}

With these request routines, the asynchronous chain would be:

requestCity(of: user) .then(on: myQ) {

(68)
(69)

Promises with Handy Shortcuts for Success Versus

Failure

The final version, called Promise, is presented here. It stands for the result of an asynchronous computation that may fail. Under the covers, the

implementation of Promise in the MiniPromiseKit is just a structure holding a BasicPromise that takes a Result for its Outcome.

struct Promise<FulfilledValue> {

let basicPromise: BasicPromise<Result<FulfilledValue>> }

The Promise methods named “then” only run their bodies if the

computation has succeeded, and the Promise method named “catch”5 only runs its body if the computation has failed. Unlike a BasicPromise, a Promise only gets fulfilled if the computation succeeds, and it gets rejected if the computation fails. In addition to explicitly calling

reject, a body can also signal failure by throwing an exception.

Here is a requestCity function that returns a Promise. It creates the Promise with pending, a static function that returns a new Promise, together with its fulfill and reject functions. It simulates the

asynchrony that would occur with a real web request with an asynchronous invocation of the switch statement:

func requestCity(of user: String) -> Promise<City> { // obtain a new Promise & fulfill & reject functions

let (promise, fulfill, reject) = Promise<City>.pending() DispatchQueue.global(qos: .userInitiated).async {

switch user {

case "Rob": fulfill(.Austin)

(70)

Using Promises, a chain of asynchronous requests that also handles errors

The first requestCity sends out a request. If the request later fails, each of the then blocks will bypass its body, and only the catch will run its body. If the request instead succeeds, the first invocation of the then method will run its body, requesting the temperature of the city that was returned from the city request. Then, if the temperature request succeeds, the temperature will be printed. Otherwise, the body of the catch will run. Although the code above is asynchronous, it is as clear as the synchronous version:

Result { try getCity(of: user) }

.then { try getTemperature(in: $0) } .then { print("temperature:", $0) } .catch { print("error:", $0) }

Space precludes a thorough tour through the implementation here; see

MiniPromiseKit.

Asynchrony and errors have been abstracted away! Swift’s support of

functional programming and type-based overloading have raised the level of discourse so that the code focuses on what is happening, not when or what-if failure.

If you know other functional languages, you might recognize the enumeration by names such as “maybe” or “either.”

Throughout this chapter, in the interest of brevity, we omit public on what would normally be exported library declarations. We also omit the @discardableResult function attribute, which silences warnings when a function is invoked without consuming its return value.

Following PromiseKit, we reuse “catch” as a method name to introduce error-handling code. (Swift permits the reuse of many of its reserved words by quoting them in backticks.) Don’t confuse this “catch” method invocation with the “catch” Swift keyword.

1

2

3

(71)

See MiniPromiseKit for full implementations.

“Catch” here is just another method name, a sort of pun on the catch keyword in Swift.

4

(72)

Chapter 4. Swift Package

Manager

Reinventing the wheel is a great way to waste your time and effort. When another developer has already created a solid library that performs a function needed by your application, you can save weeks of time if you are able to reuse it. The Swift ecosystem is growing very quickly, and often there are many similar libraries that solve the same problem. If you adopt Swift for your project, you will find hundreds of Swift packages on GitHub or listed in the IBM Swift Package Catalog, which can be easily incorporated with the aid of the Swift Package Manager (SwiftPM). Whether you are looking to store your data in a database, parse JSON, or use reactive programming, chances are that there is a Swift package out there that can do what you need it to.

(73)

Semantic Versioning

When SwiftPM fetches a required package, it must fetch an appropriate version that will work with your project. So, each package uses a three-level version number, consisting of major, minor, and patch numbers:

The major version indicates API compatibility.

The minor version indicates functionality.

The patch version indicates fixes.

The major version is incremented when there are changes made to the API that could break the packages that depend on it. The minor version is

incremented when there is a new feature added that does not break the compatibility with older versions. The patch version is incremented when bugs are fixed that do not break backwards compatibility.

SwiftPM will only clone the dependencies at specific revision points set by the developer’s tags. Therefore, you cannot fetch the latest commit from the head of a specific branch; instead, SwiftPM will clone the source code at specific tagged revisions in the repository. Although this requirement may seem limiting, it prevents broken builds and introduction of bugs by ensuring that every commit made to the repository does not affect the many

applications that could be using the library.

Consider this typical workflow: suppose your project uses the master branch for your production-ready releases, and currently your project is at version 0.1.3. When developing a new API-breaking feature, you can branch from the master into a new feature branch called add_feature. From this feature

(74)

Creating an Application

This section steps you through the process of using the SwiftPM to create a new project from scratch, find a package to include, import that package, and build your project.

First, generate a new executable project by creating a new directory called MyProject. The following command uses the name of the current working directory for automatically creating a new project with that name:

~$ cd MyProject

MyProject$ swift package init --type executable

Creating executable package: MyProject

There were a few files created in that step. The Sources/main.swift is the main entry file, and it starts off with very basic Swift code for a “Hello, World!” application. All executable projects or targets will contain a

main.swift in the Sources directory or a subdirectory in the Sources directory, respectively.

print("Hello, world!")

The Tests directory is empty. See “Creating Your Own Library” for unit-testing information.

(75)

has:

import PackageDescription

let package = Package(name: "MyProgram")

Now, produce an executable file from your project by running swift build:

$ swift build

Compile Swift Module ‘MyProject’ (1 sources) Linking ./.build/debug/MyProject

The swift build command reads the Package.swift file and checks if there are any dependencies to fetch. Next, it runs the Swift compiler,

swiftc, on each of your .swift files to convert them into swift modules. Finally, it links the .swiftmodule files to produce your executable, which is left in the .build/debug/ directory. Run your program now:

(76)

COMPILING IN RELEASE MODE

Projects can be built in either debug or release mode. By default, swift build

compiles the project in debug mode. When moving your project to production, consider building your project in release mode so that you have the best performance. You may compile with swift build –-configuration release, which will generate an optimized executable in the directory .build/release. Consequently, debugging symbols will be included and optimizations excluded so that LLDB can examine data, add breakpoints, and step through your program.

The Package.swift file describes the project and how to build it. Builds are not described by a special language like Gradle or a Makefile — it’s native Swift code! You don’t have to learn another language to control the build process. You can write if statements that will select the correct library to use based on whether you are running on Darwin or Linux:

#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) import Darwin

#elseif os(Linux) import Glibc

#endif

The system’s environment variables can also influence the build process. For example, you can check for the existence of an environment variable before executing a code block:

(77)

Importing a Library in Your Project

Once the necessary boilerplate for a Swift project has been created, you can add new dependencies to it — for example, that nifty pipe operator ( |>) introduced in Chapter 1. First, find the source code, which is often in GitHub. You can find libraries with the IBM Swift Package Catalog. Open the

Catalog in your browser and search for “Pipes” in the search bar as shown in

(78)

Figure 4-1. Searching for a package in the IBM Swift Package Catalog

Select the Pipes package to view the details about the project shown in

(79)

Figure 4-2. Pipes library description page on IBM Swift Package Catalog

Click on “Include project in your code,” which will copy the necessary dependency link to your clipboard so you can paste it

into Package.swift.

Go back to your Package.swift file and insert that dependency in your Package constructor:

import PackageDescription

let package = Package( name: "MyProject", dependencies: [

.Package(url: "https://github.com/IBM-Swift/Pipes.git", majorVersion: 0, minor: 1),

(80)

OTHER GIT SERVERS

In the dependencies package definition, any Git repository can work: it can be on the local filesystem, a private Git server, or on GitHub. You can use the file, https, or ssh

protocols for any of these repositories.

There are different ways to specify the package versions as described in

Table 4-1:

Table 4-1. Other ways to specify package versions

Version Range: Version(0, 1, 0)..<Version(0, 2, 0) Major Version only: majorVersion: 0

Major and Minor Version: majorVersion: 0, minor: 1 Version: Version(0, 1, 0)

Version String: 0.1.0

Although you have the flexibility to use a large version range (such as only specifying the major version), we recommend locking down both the major and minor versions. Although, in theory, minor version changes should not break a build, they often do so at early version numbers.

You can now write code that uses Pipes. Open main.swift and import the Pipes module, and write some basic code:

import Pipes

func sayHello(str: String) -> String { return “Hello, \(str)”

}

"Alice" |> sayHello |> print

Compile with swift build. Before the compilation begins, the SwiftPM will clone the Pipes library into a subdirectory of the Packages

(81)

$ swift build

Cloning https://github.com/IBM-Swift/Pipes HEAD is now at 71c2c80 Improve Pipes

Resolved version: 0.1.2

Compile Swift Module 'Pipes' (1 sources) Compile Swift Module 'MyProject' (1 sources) Linking ./.build/debug/MyProject

The compiled program is located in the .build/debug folder. You can now run your program:

$ ./.build/debug/MyProgram

(82)

OTHER USEFUL SWIFT PACKAGE COMMANDS

swift package fetch fetches package dependencies only

(83)

Developing in Xcode

While you can easily develop entirely on the command line using your favorite text editor, many macOS users will want to use Xcode for their server-side Swift development. You can use Xcode to edit and compile the code, run tests, and profile your Swift code just like you can with iOS and macOS projects. But first, in order to use Xcode, a project must be created from your Package.swift. The following command will create your project:

$ swift package generate-xcodeproj

Running this command line fetches the necessary dependencies before creating the Xcode project. When you build with Xcode, it does not use SwiftPM to build your project, but instead will use Xcode’s native build system.

(84)

BEWARE OF MANUALLY CHANGING XCODE PROJECT FILES

If you add or remove a dependency to your Package.swift file, it will be necessary for you to recreate the Xcode project again. Note that any changes made to your project will be lost when a new project file is generated over the old one.

(85)

Creating Your Own Library

Suppose you wanted to create a new library from scratch. What would you have to do to create one? You could use the SwiftPM! This section walks you through the steps to create a Pipes library from scratch.

To start, create a new subdirectory called Pipes. Go inside this directory and run the SwiftPM project generator to create a boilerplate for a library:

$ swift package init --type library

This time the following files are generated:

./Package.swift ./.gitignore

./Sources/Pipes.swift ./Tests/LinuxMain.swift

./Tests/PipesTests/PipesTests.swift

swift init generates files for a library, just as it does for an executable. But instead of generating a main.swift file, it generates a file named after the module. Instead of nothing in Tests, it creates a very basic test suite. Next, open Sources/Pipes.swift, erase what’s there, and add an implementation of the Pipe operator that will be used for your library:

precedencegroup LeftFunctionalApply { associativity: left

higherThan: AssignmentPrecedence lowerThan: TernaryPrecedence }

infix operator |> : LeftFunctionalApply

@discardableResult public func |> <A, B> ( x: A,

f: (A) throws -> B ) rethrows -> B

{

(86)

}

What hasn’t been tested doesn’t necessarily work, so add a test by opening the Tests/PipesTests.swift file. Erase the content inside of the testExample() function and replace it with:

import XCTest

@testable import Pipes

func double(a: Int) -> Int { return 2 * a } class PipesTests: XCTestCase {

func testDouble() { XCTAssertEqual(6 |> double, 12) } }

Tell the Linux test runner which methods of PipeTests to run:

extension PipesTests {

static var allTests : [

The allTests array is not needed to run your tests in Xcode, but it is

important for the SwiftPM to run the tests with the swift test command. Now, compile and test your library by running swift build, then swift test:

Pipes$ swift build

Compile Swift Module 'Pipes' (1 sources)

Pipes $ swift test

Compile Swift Module 'PipesTestSuite' (1 sources)

Linking .build/debug/PipesTests.xctest/Contents/MacOS/PipesTests Test Suite 'All tests' started ...

Gambar

Table P-1. Where to find code examples
Figure 2-1. Because instances of classes are reference types, when request is passed to sendDB that function gets a reference to the same object
Figure 2-2. Because structures are value types, the sendToDB function receives a whole new copy of the request
Figure 4-1. Searching for a package in the IBM Swift Package Catalog
+3

Referensi

Dokumen terkait

Jambatan Dlna* Pelerfarn Umum lhbupaten llageleo Tahun Anggsran 2012, telah melaksanakan Penjehsrn Dokumen Pehlangan Umum.. dan Pendlilhan Langsung kepda peserhyang

Microsoft Access 2002 adalah software yang penulis gunakan dalam membuat Laporan Bulanan Distribusi Listrik, karena selain mudah digunakan untuk pemula juga memiliki fasilitas

Untuk menja min hak-hak dan kewa jiban setiap penduduk Madinah, Rasulullah me mp raka rsai penyusunan piagam perjanjian yang disebut Piagam Madinah. Dengan piagam ini

Seperti situs-situs lainnya, halaman-halaman flippa juga terindeks dengan baik di Google, walaupun ada yang bilang banyak juga halaman yang tidak terindeks, tapi

[r]

[r]

Di ant ara nikm at Allah Subhanahu w a Ta'ala y ang diberik an k epada Nabi Yusuf ‘alaihissalam adalah dua k eut am aan: Kebaik an y ang bersifat lahiriah dan

Untuk dapat menjadi guru yang profesional dalam mendidik dan mengajar peserta didik melalui proses ruang pembelajaran di kelas, maka selain harus memperhatikan ketiga elemen pokok