書いて覚えるSwift入門

第27回 静かなること型の如し

この記事を読むのに必要な時間:およそ 4.5 分

得意なことは違うから

この何でもござれぶりを見ると,関数という関数を総称型で書きたくなってきますが,これは以前言った「本当に必要なとき以外Any型は使うべきではない」と同様な意味でムチャ振りです。それを実感するため,今度はl == rに相当するeq(l,r)を同様に書いてみましょう。

  1> func eq<T>(_ l:T, _ r:T)->Bool {
  2. return l == r
  3. }
  4.
error: repl.swift:2:14: error: binary operator'==' cannot be applied to two 'T' operands
    return l == r

repl.swift:2:14: note: overloads for '=='
exist with these partially matching parameterlists: (Any.Type?, Any.Type?), (UInt8, UInt8),/* 中略 */ (UnsafePointer<Pointee>,
UnsafePointer<Pointee>)
    return l == r

なんかむちゃくちゃ文句言って来ましたよ。Any型には==はない」と前回も言いましたが,==演算子はどんな型でもOKとはいかない以上,総称しようがないのです。ということは,

func eq(_ l:Int, _ r:Int)->Bool {return l == r}
// ...

の時代に戻らなければいけないということでしょうか?

ここでいよいよプロトコルが登場します。要は==演算子を持つ型」というのを何とか表現できればいいのですよね? これでどうだ?

func eq<T:Equatable>(_ l:T, _ r:T)->Bool {
  return l == r
}

今度はうまくいきました!

  1> func eq<T:Equatable>(_ l:T, _ r:T)->Bool {
  2. return l == r
  3. }
  4> eq(0,0)
$R0: Bool = true
  5> eq(0.0,0.0)
$R1: Bool = true
  6> eq("","")
$R2: Bool = true

このEquatableのことをプロトコル(protocol)といい,T:EquatableTという型はEquatableというプロトコルに準拠(conform)している」ことを表現します。

めでたし,めでたし?

――ちょっと待った! これは?

  4> [0]==[0]
$R0: Bool = true
  5> eq([0],[0])
error: repl.swift:5:8: error: argument type'[Int]' does not conform to expected type
'Equatable'
eq([0],[0])

なぜ[0]==[0]はうまくいくのにeq([0],[0])はうまくいかないのでしょう? むしろ[0]==[0]がうまくいくほうが不思議ではありませんか? Array自体はEquatableではないのに……。

「それ自体はプロトコル準拠ではないけど,中身は準拠している」ということをSwift語で何と言えばいいのでしょうか?

こういうときに便利なのがswiftdoc.orgArrayDictionaryRangeが共通して準拠しているSequenceをよく見てみるとelementsEqualなるメソッドが存在するではありませんか。

func eq<T:Sequence>(_ l:T, _ r:T)->Bool
    where T.Iterator.Element:Equatable
{
    return l.elementsEqual(r)
}

このようにして,実行してみると

  4> eq([0],[0])
$R0: Bool = true
  5> eq(0...1,0...1)
$R1: Bool = true

うまくいったようですが,これでもまだ完璧ではありません。

  7> eq([0:""],[0:""])
error: repl.swift:7:3: error: type '(key: Int,value: String)' does not conform to protocol
'Equatable'
eq([0:""],[0:""])

SequenceとしてのDictionary<K,V>Element(K, V)というタプル型で,これがEquatableではない,と。

さすれば……

func eq<K: Equatable,V: Equatable>
    (_ l:[K:V], _ r:[K:V])->Bool
{
    return l == r
}

これを実行してみると,

6> eq([0:""],[0:""])
$R0: Bool = true

これでDictionaryeq()できるようになりました。

オレオレプロトコルの書き方

それでは同様に,Collectionの中身を総和するsumというメソッドを書いてみましょうか。そのためにはCollectionIterator.Elementが演算+をサポートしていることをSwiftが知っていればよいわけですが,==Equatableと違って+Addableというプロトコルは見当たりません。

ならば定義してしまいましょう。

protocol Addable {
    static func +(_ l:Self, r:Self)->Self
}

これは,次のとおりに読めます。

> プロトコルAddableに準拠している型は,自分自身と同じ型を持つlrを受けて同じ型の値を返す+という二項演算子を持つ

IntDoubleといった数値型のみならずStringAddableに準拠しているのは明白ですが,残念ながらSwiftは良きに計らってくれません。プロトコルに準拠しているのだということをSwiftに教えるには,空のextensionを用います。

extension Int: Addable{}
extension Double: Addable{}
extension String: Addable{}
extension Array: Addable{}
……(中略)……

ここでプロトコルに準拠していない型にextensionをかけるとエラーで止まります。

extension Dictionary: Addable {}
// error: type 'Dictionary<Key, Value>' doesnot conform to protocol 'Addable'

これで準備は完了。あとはCollectionを拡張するだけです。

extension Collection where Iterator.
Element:Addable {
    func sum()->Iterator.Element? {
        guard var v = self.first else {
            return nil
        }
        var i = self.startIndex
        i = self.index(after:i)
        while i != self.endIndex {
            v = v + self[i]
            i = self.index(after:i)
        }
        return v
    }
}

要素をイテレートするのにずいぶんまだるっこしい方法を使っていますが,これはArrayRangeをそのまま拡張するのではなく,Collectionというプロトコルを拡張しているから。たとえばArrayだけであれば,

extension Array where Element:Addable {
    func sum()->Element? {
        guard var v = self.first else {
            return nil
        }
        for i in 1..<self.count {
            v = v + self[i]
        }
        return v
    }
}

とより簡潔に書けますが,(0...100).sum()のようにRangeまでまとめて拡張することはままなりません。

図1 実行画面

図1 実行画面

次回予告

というわけで今回もコード盛りだくさんでお届けしましたが,次回はWWDCの知見をなるべくお伝えしたうえで,次回にまたプロトコルについて続きを話します。

Software Design

本誌最新号をチェック!
Software Design 2021年5月号

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

  • 第1特集
    ハンズオンTCP/IP
    コードで納得 ネットワークのしくみ
  • 第2特集
    PHP 8移行のタイミングとコツ
    コーディング,JITコンパイラ,フレームワークの3点で考える

著者プロフィール

小飼弾(こがいだん)

1969年生まれ,東京都出身。元ライブドア取締役の肩書きよりも,最近はPokemon GOのガチトレーナーのほうが有名になりつつある……かもしれない永遠のエンジニアオヤジ。

活躍の場はIT業界だけでなく,サブカルからアカデミックまで多方面にわたり,ネットからの情報発信は気の向くまま毎日毎秒! https://twitter.com/dankogai,ニコニコチャンネルは,http://ch.nicovideo.jp/dankogai,blogはhttp://blog.livedoor.jp/dankogai/

当社刊行書籍は『小飼弾のアルファギークに逢ってきた』『小飼弾のコードなエッセイ』など。他にも著書多数。