書いて覚えるSwift入門

第19回SwiftとPokémon GO

SiriまでやってるPokémon GO

本稿が読者の皆さんに届くころにはXcode 8とSwift 3も正式リリース間近[1]⁠、⁠i|mac|tv| watch)OSもバージョンが上がり、もしかしてiPhone 7と次期MacBook Proすらリリースされているかもしれません。そんな大事な時期ですが、しかしPokémon GOをスルーするわけには行かないでしょう。何しろSiriまでやってるんですから図1⁠。

図1 Pokémon GO
図1 Pokémon GO

米国版および豪国版のリリースは7月6日。前回執筆時にはすでに同国で社会現象となっていましたが、ポケモンのふるさとでもある日本での正式リリースは脱稿直後の7月22日。で、筆者もやってみると……見事にはまってしまいました。Apple Watch入手後も「わっかが1周」することなどほとんどなかった筆者が1日も欠かさずムーブゴールを達成するどころか図2⁠、1日平均10km以上歩いてます図3⁠。いったい何が起きたのでしょうか?図4

図2 ムーブゴール達成!
図2 ムーブゴール達成!
図3 ダッシュボードを見よ!
図3 ダッシュボードを見よ!
図4 レベル29(8月19日現在)
図4 レベル29(8月19日現在)

こういうのも何ですが、Pokémon GOを成立させている各要素に目新しい点はまるで見当たりません。ポケストップは開発元のNianticが2012年からやっていたIngressの援用。ポケモンにいたっては1996年、20年前リリースの「初代」の151種類そのまま。3DグラフィックスはUnityですし、サーバはGCPやAWSといった「お馴染み」のクラウドプラットフォーム。AR? セカイカメラなら2008年に始まって2013年に終了していますが、何か?

どうしてPokémon GOは先行者たちが超えられなかったキャズムを超えられたのでしょうか?

個々の要素技術が、先行者たちを超えてなかったからだというのが筆者の答えになります。ポケモン20年。スマホ10年。トレーナ、もといユーザは「やって」なくても「知って」はいたわけです。⁠新しい」のに「慣れている⁠⁠。これって何か心当たりありませんか?

そう。Swift。型推論、オプショナル、プロトコルといったSwiftをSwiftをたらしめている要素は、どれ1つとってもほかの言語で実装されていたものばかり。しかしそれが1つにまとまると今までになかった何かになる。その結果、みんなが使うようになる……。

もう1つ似ているのは、リリース時には未完成で、今もなお未完成であること。本原稿執筆現在、ポケモンには欠かせない機能であるはずのモンスター交換はいまだに実現されていませんし、2016年のアプリとは思えないほど強制終了しまくりですし、ポケストップとジムの配置は今もなお試行錯誤が続いています。おかげで中の人はまだLv5みたいではありますが、全世界の人を歩かせるにはそれで十分だったのです。

そしてこれが一番大事だと筆者が感じているのは、細やかな報酬の重要性。人――というのが主語が大き過ぎるのであれば少なくとも筆者――は、いきなり10km歩けと言われても微動だにしないのです。しかし100mごとにポケストップがあると、いつの魔(間)にか10km歩いてしまっている。水族館のイルカやアシカのショーではショー全体が終わったあとではなく一芸ごとに餌を与えていますが、実は人というケダモノもそうなのです。大きな報酬がまとめて支払われるその日まで我慢に我慢を重ねられる人は偉大ですが、小さな人まで動いて初めて世の中は動くことを、Pokémon GOがあらためて示してくれたと感じています。

変わらないために変える

ポケモントレーナーの視点からあらためてSwift 3を見てみると、変わらないために変えているのだという思いを新たにします。とくに前回紹介した++--演算子やC-styleのforの廃止は、⁠Cにおもねる新言語」から「Swiftという一人前の言語」への進化だと言い切ってよいでしょう。ところでポケモンの弊害に「進化」という言葉の「誤用」があります。生物学的には同一個体の変化は「進化」ではなく「変態」なのですが、英語でもmetamorphoseではなくevolveですし、こうなるともはや誤用ではなく「語彙の進化」だと筆者も諦め気味。Xcodeでは2のコードを3に進化させられるのですが、アメ玉が不要な点はPokémon GOより優れてます:-)

API:旧弊は進化の証

その一方、一見するとなんではじめからそうしなかったという変更も多々見られます。たとえばAPIの命名規則ですが、Swift 2ではこうだったのが……、

var a = [0]
a.append(1) // [0, 1]
a.appendContentsOf([2,3]) // [0, 1, 2, 3]

Swift 3ではこうなっています。

var a = [0]
a.append(1) // [0, 1]
a.append(contentsOf:[2,3]) // [0, 1, 2, 3]

Swiftはもとから「名前が同じでもシグネチャが異なれば別の関数」なのですから、はじめからSwift 3のようにすればよかったのにと思わぬでもないですが、その一方進化前のSwiftはObjective-Cだったことを考えれば、生まれたての段階ではObjective-Cを引きずっているのも自然ではあります。余談ですが、演算子を使った記法ではSwift 2とSwift 3の違いはありません。

Swift (2|3)
var a = [0]
a += [1]
a += [2, 3]

わかりやすさも、非英語圏まで考慮すると演算子を使ったほうが直感的なような気がします。

真偽値を返すAPIは必ずisを付けるというのも同様で、Objective-Cではそうなっていなかったのですね。

Swift 2
import Foundation
var u = NSURL(fileURLWithPath: #file)
if u.fileURL {
    print(u.path!)
}
Swift 3
import Foundation
var u = NSURL(fileURLWithPath: #file)
if u.isFileURL {
    print(u.path!)
}

ラベルも脱Objective-C

SwiftがObjective-Cの祖先であることを最も感じさせたのは、ラベルの扱いかもしれません。たとえば、

func volume(x:Double, y:Double, z:Double)->Double {
    return x*y*z
}

という関数は、Swift 2ではこう呼び出します。

Swift 2
volume(2, y:3, z:4) // 24.0

最初のラベルだけ省略されるというわけですが、これはわかりづらい。

Swift 3
volume(x:2, y:3, z:4) // 24.0

のほうがずっとわかりやすいですよね。

ちなみに「呼び出し時にラベルを省略する」のであれば、Swift 2もSwift 3も同様に_を付ければよいので、ラベルの省略は関数定義時に明示するというのは癖にしておいてよいでしょう。

Swift (2|3)
func volume(_ x:Double, _ y:Double, _
z:Double)->Double {
    return x*y*z
}
volume(2, 3, 4) // 24.0

「戻り値を捨てる」も明示

「コンパイラーが推論できる場合は推論」以上に「明示すべき場合は明示」というのがSwiftismですが、関数をサブルーチンとして使う場合、つまり戻り値を使わない場合にも明示するようになりました。

Swift (2|3)
func plusOne(_ i: Int) -> Int {
  print(i)
    return i + 1
}
_ = plusOne(0) // 1

_に代入する」ことで「戻り値不要」を示しているわけです。Swift 2でも実は有効です。Swift 3では、@discardableResult修飾子で関数側で「戻り値捨ててもOK」を指定することもできます。

Swift 3
@discardableResultfunc plusOne(_ a: Int) ->
Int { print(a) // side effect! return
a+1}plusOne(x)````

var引数禁止

Swift 2までは、関数の引数にvarをつけることで次のようなコードを書くことができました。

Swift 2
// 最大公約数
func gcd(var a: Int, var _ b: Int) -> Int {
    a = abs(a); b = abs(b)
    if (b > a) { (a, b) = (b, a) }
    while (b > 0) { (a, b) = (b, a % b) }
    return a
}

これがSwift 3では廃止されるので、上記のコードは次のように書き直す必要があります。

Swift 3
func gcd(_ a: Int, _ b: Int) -> Int {
    var (x, y) = (abs(a), abs(b))
    if (x > y) { (x, y) = (y, x) }
    while (y > 0) { (x, y) = (y, x % y) }
    return x
}

一見不便に思えるのですが、その一方Swiftにはinoutという引数もあって、

  • 指定なし:letと同様。イミュータブル
  • var:ミュータブルだが、呼び出し元は変更されない
  • inout:ミュータブルかつ呼び出し元も変更される

という状態だったのが、1つ減ることで紛らわしさがずいぶんと軽減されます。

UnsafePointer nullability

C APIとの連携ではUnsafePointerが大活躍するのですが、これが明示的にOptionalとなることで、次のようなコードが安全に書けるようになります。

let ptr : UnsafeMutablePointer<Int>? = nil
ptr?.memory = 42

型推論もOptional指向に

たとえば次のコードをご覧ください。

func f(value : Int!) {
  let x = value + 1 // x: Int - force
unwrapped
  let y = value // y: Int? let array = [value, 42]
  let array2 = [value!, 42] // [Int] use(a)
}

arrayの型は[Int]なのか[Int?]なのか。このような場合Swift 3では[Int?]よりに推論して、明示的にUnwrapされている場合のみ[Int]にします。

where節

プロトコル指向プログラミング(POP)では、次のようなコードは可能であるにとどまらず推奨すらされます。

anyCommon([1], 0..<2) // true
anyCommon([2], 0..<2) // false

違う型同士でも、同じプロトコルに準拠していれば共通要素があるかどうかを確認できるわけですが、その実装はSwift 2ではずいぶんと長ったらしいものとなっていました。

Swift 2
func anyCommon<T: SequenceType, U:SequenceType
    where T.Generator.Element:Equatable,
    T.Generator.Element == U.Generator.Element
    >(lhs: T, _ rhs: U) -> Bool {
    for l in lhs {
        for r in rhs {
            if l == r { return true }
        }
    }
    return false
}

要は<>の中身が長過ぎるのですが、Swift 3では次のように関数シグネチャの直後に書くことでずいぶんとすっきりします。POPがますますはかどりそうです。

Swift 3
func anyCommon<T: Sequence, U: Sequence>(_lhs: T, _ rhs: U) -> Bool
  where T.Iterator.Element:Equatable,
  T.Iterator.Element == U.Iterator.Element {
    for l in lhs {
        for r in rhs {
            if l == r { return true }
        }
    }
return false
}

Generic Type Aliasing

Swiftのtypealiasは実に便利な機能ですが、typealias Foo = Barはできて、typealias Foo<T> = Bar<T>ができないのは実に不自然でしたが、やっとDWIM(Do what I mean)になります。

typealias StringDictionary<T> = Dictionary<String, T>
typealias DictionaryOfStrings<T : Hashable>= Dictionary<T, String>
typealias IntFunction<T> = (T) -> Int
typealias Vec3<T> = (T, T, T)
typealias BackwardTriple<T1,T2,T3> = (T3, T2, T1)

プログラムの進化、プログラマの進化

0x20代もあますところ1年となった中年プログラマにとって、Pokémon GOがあらためて示したあまりにまっとうな世界観にあらためて自省しています。一言で言えば、⁠千里の道も楽しく一歩から⁠⁠。⁠千里の道も一歩から」だけだと大業は苦行の積み重ねという感じがしますが、一歩が苦しいなんて誰が言ったのでしょう。一歩一歩が楽しかったからこそ、気がつけば千里を踏破したのではないのか。プログラミングもまたその例外ではないように感じます。1行1行が楽しかったからこそ今まで続いてきたのだと。そしてそれが楽しかったのは、楽しむための工夫をどこかでしてきたからではないのか、と。

来月以降も、また一緒に歩いていきましょう。一歩ずつ、楽しく。

Software Design

本誌最新号をチェック!
Software Design 2022年9月号

2022年8月18日発売
B5判/192ページ
定価1,342円
(本体1,220円+税10%)

  • 第1特集
    MySQL アプリ開発者の必修5科目
    不意なトラブルに困らないためのRDB基礎知識
  • 第2特集
    「知りたい」⁠使いたい」⁠発信したい」をかなえる
    OSSソースコードリーディングのススメ
  • 特別企画
    企業のシステムを支えるOSとエコシステムの全貌
    [特別企画]Red Hat Enterprise Linux 9最新ガイド
  • 短期連載
    今さら聞けないSSH
    [前編]リモートログインとコマンドの実行
  • 短期連載
    MySQLで学ぶ文字コード
    [最終回]文字コードのハマりどころTips集
  • 短期連載
    新生「Ansible」徹底解説
    [4]Playbookの実行環境(基礎編)

おすすめ記事

記事・ニュース一覧