Tải bản đầy đủ (.pdf) (124 trang)

Extending swift value(s) to the server

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (1.74 MB, 124 trang )


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


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
(). For more information, contact our
corporate/institutional sales department: 800-998-9938 or

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
January 2017: First Edition


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.
978-1-491-97196-3
[LSI]


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.
That’s why we wrote this book; we wanted to help you, the developer, who is


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 nativecode 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.
This book will introduce you to the Swift language, illustrate some of its
most interesting features, and show you how to create and deploy a simple
web service. Let’s get started!


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

Kitura To-Do List

Created in Chapter 5


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.
Words fall short in plumbing the depths of our gratitude for the support and

love of our sweethearts, Barbara Hurd and Valerie Magolan.


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.
As you read this chapter, you may want to refer to the documentation, The
Swift Programming Language (Swift 3 Edition).


Types and Type Inference
Swift combines strong and static typing with powerful type inference to keep
code relatively concise. Swift’s type system and compile-time guarantees
help improve the reliability of nontrivial code.



DECIPHERING TYPE ERRORS IN LONG
STATEMENTS
If your program won’t compile, you can often clarify a type error by breaking up an
assignment statement into smaller ones with explicit type declarations for each
intermediate result.


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)

Swift’s syntax combines ease of learning, convenience of use, and prevention
of mistakes.


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 {
case ok = 200
case created = 201
… // more cases go here
case badRequest = 400
case unauthorized = 401

}

The value of this enumeration can be accessed via the rawValue attribute:
func printRealValue(of e: StatusCode) {
print ("real value is", e.rawValue)
}


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)
-> (name: String, serialNumber: Int)
{
// compute n and sn

return (n, sn)
}

and then accessed by name:
let userInfo = lookup(user: "Washington")
print("name:",
userInfo.name,
"serialNumber:", userInfo.serialNumber)

When an identifier is declared with let, every element of the tuple behaves
as if it were declared with a let:


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-ofclasses 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:
return "does not fit"


}
}

Tuples are a convenient way to aggregate a few constant fields that have no
meaningful type apart from the types of the fields.



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,
rethrows -> Out {
return try rhs(lhs)
}


rhs: (In) throws -> Out )

Judiciously used, custom operators can help make code clear and concise.


Closures
Swift includes closures: anonymous functions defined inline, which can read
and write to their enclosing lexical scope.1 A closure is a first-class entity, a
reference type which can be assigned to constants and variables, passed as
arguments, and returned as results. Functions and methods are just special
cases of closures with a slightly different syntax for their definition. Closures
support functional programming, which can be particularly helpful in dealing
with asynchrony (Chapter 3).


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 }
)
}


Object Orientation
Swift includes full support for class-based object-oriented programming,
including classes, instances, constructors (called “initializers”), instance
variables, static variables, computed virtual getters and setters, inheritance,
and final attributes. When one method overrides another, it must be
annotated in the code. This requirement prevents some errors. Experienced
Swift programmers tend to reserve objects for things requiring shared access
to a mutable state. (See Chapter 2.)


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
{
var requestString: String { return "POST" }
var data: String
init( url: URL, data: String ) {
self.data = data
super.init(url: url)
}
}

When declaring a variable to hold a request, instead of using the type
Abstract_HTTP_Request, use the type HTTP_Request_Protocol:


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


In the following, you’ll see an example of a generic protocol, which could not
be used as a type. As in other languages, protocols help to prevent errors as
well as remove dependencies on the representation.


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
}

The associatedtype in the protocol works a bit like a type parameter,
except that instead of being passed in, it is inferred from context. Any
protocol with an associated type is considered to be generic. Self, which
denotes the concrete type conforming to the protocol, is also considered to be
an associated type.


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
express requirements and implementations that apply to entities that
themselves generalize over the types of their arguments, results, or
constituents.


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:
class City {


×