すでに始まったPOP
(Protocol Oriented Programming)の時代
今回はいよいよ懸案のProtocol Oriented Programmingについて解説します。
Swift Standard Library
Classが少ないのです。たったの5つ。しかも1つは継承でつながっているので実質3つ

それに対してStructとEnumとProtocolはどっさりあります。これは何を意味するのか?
SwiftにおいてClassというのはあくまでもObjective-Cの遺産を活用するためのものであって、
ClassとStructやEnumの違い
それを理解するためには、
Classは継承できるが、
class ClassV1 {
var x = 0.0
init (x:Double) {
self.x = x
}
}
class ClassV2 : ClassV1 {
var y = 0.0
init (x:Double, y:Double) {
super.init(x:x)
self.y = y
}
}
class ClassV3 : ClassV2 {
var z = 0.0
init (x:Double, y:Double, z:Double) {
super.init(x:x, y:y)
self.z = z
}
}
var cv3 = ClassV3(x:1, y:2, z:3)
なぜ継承できないのか? 参照型であるClassと異なり、x
にアクセスするには親クラスの親クラスまで参照をたぐらなければなりません。sizeofValue(cv3)
はポインターのサイズである8
。Classのインスタンスであれば、
これに対し、
struct StructV3 {
var x = 0.0, y = 0.0, z = 0.0
init (x:Double, y:Double, z:Double) {
self.x = x
self.y = y
self.z = z
}
}
let sv3 = StructV3(x:1, y:2, z:3)
ここでsizeofValue(sv3)
は、Double
のきっかり3倍である24。確かに実体を持っています。StructやEnumは、
「えー、
でも、
それを可能にするのが、
実践例[swift-complex]
論より証拠。実例を見てみましょう。[swiftcomplex]というgithub projectがあります。Swiftの演習用に筆者がずっと書いてきたものですが、

要は複素数を使うためのライブラリです。使い心地はRubyであれば、
require 'cmath'
include cmath
Pythonであれば
from cmath import *
したときにとてもよく似ています。余談ではありますが、
そのまま遊べるように、

で、
- Swiftの特徴を活かしていること
- 演算子関数による直感的な操作
→Playground - クロスプラットフォーム
→OS XやiOSだけでなく、Linuxでも動く
- 演算子関数による直感的な操作
- 「実数」
の実装が入れ替え可能であること - 現時点でDoubleだけでなく、
FloatやIntもサポート - 任意制度の数値ライブラリを別途用意すれば、
それを使うことも可能
では実際に見てみましょう。500行ちょっとしかないので全部掲載したいところですが、
public protocol ArithmeticType:
AbsoluteValuable, Equatable, カ
Comparable, Hashable {
// Initializers (predefined)
init(_: Int)
//// [中略]
init(_: Double)
init(_: Float)
init(_: Self)
// CGFloat if !os(Linux)
#if !os(Linux)
init(_: CGFloat)
#endif
// Operators (predefined)
prefix func + (_: Self)->Self
prefix func - (_: Self)->Self
func + (_: Self, _: Self)->Self
func - (_: Self, _: Self)->Self
func * (_: Self, _: Self)->Self
func / (_: Self, _: Self)->Self
func += (inout _: Self, _: Self)
func -= (inout _: Self, _: Self)
func *= (inout _: Self, _: Self)
func /= (inout _: Self, _: Self)
}
まず、
- Swiftの組込み型からの初期化をサポートしていること
- 基本的な四則演算をサポートしていること
というのをSwift語で書き下しただけです。
で、
extension Int : ArithmeticType {}
とすでにArithmeticType
に準拠
ここまではSwift 1の時代からあったのですが、
public extension ArithmeticType {
/// self * 1.0i
public var i:Complex<Self> カ
{ return Complex(Self(0), self) }
/// abs(z)
public static func abs(x:Self)->Self { カ
return Swift.abs(x) }
/// failable initializer to conver the type
/// - parameter x: `U:ArithmeticType` カ
where U might not be T
/// - returns: Self(x)
public init?<U:ArithmeticType>(_ x:U) {
switch x {
case let s as Self: self.init(s)
case let d as Double: self.init(d)
case let f as Float: self.init(f)
case let i as Int: self.init(i)
default:
return nil
}
}
}
ご覧のとおり、42.
とかと書くと(0+42.
になるのは、
Protocolといういうのは、
実際Swift 2では、Array
だけではなくSequence
に準拠する型であればすべて.map
や.reduce
が使えるようになったのですが、
ではいよいよComplex
を見てみましょう
public struct Complex<T:ArithmeticType> : Equatable, CustomStringConvertible, Hashable {
public typealias Element = T
public var (re, im): (T, T)
//// [中略]
}
リスト4は見てのとおり、ArithmeticType
に準拠したTによる総称型です。1+1.
はComplex<Int>
、1.
はComplex<Double>
になるわけです。
ところで賢明な読者は、.abs
や偏角.arg
がないことに気づかれるかもしれません。これらは複素数自体がComplex<Int>
、
うまいこと、
まず、ArithmeticType
の要件をすべて満たす上位互換Protocolを1つ追加します
public protocol RealType : ArithmeticType, FloatingPointType {
static var EPSILON:Self { get } // for =̃
}
そしてこれをProtocol Extensionで拡張します
extension RealType {
/// Default type to store RealType
public typealias Real = Double
//typealias PKG = Foundation
// math functions - needs extension for each struct
#if os(Linux)
public static func cos(x:Self)-> Self { return Self(Glibc.cos(Real(x)!))! }
//// [中略]
#else
public static func cos(x:Self)-> Self { return Self(Foundation.cos(Real(x)!))! }
//// [中略]
#endif
}
要するに、Glibc
、Foundation
からごっそり持ってくるわけです。ちなみにProtocol Extensionと型のExtensionで同名の識別子がある場合、Float
に関してはいったんDouble
に変換してFloat
に戻すのではなくFloat
のままで計算するためにcosf
など末尾にf
がついた関数を使いたかったので、extension Float
で上書きしています。
そうしたうえで、
extension Complex where T:RealType {
public init(abs:T, arg:T) {
self.re = abs * T.cos(arg)
self.im = abs * T.sin(arg)
}
/// absolute value of self in T:RealType
public var abs:T {
get { return T.hypot(re, im) }
set(r){ let f = r / abs; re *= f; im *= f }
}
/// argument of self in T:RealType
public var arg:T {
get { return T.atan2(im, re) }
set(t){ let m = abs; re = m * T.cos(t); im = m * T.sin(t) }
}
/// projection of self in Complex
public var proj:Complex {
if re.isFinite && im.isFinite {
return self
} else {
return Complex(
T(1)/T(0), im.isSignMinus ? -T(0) : T(0)
)
}
}
}
つまり複素数の要素がRealType
に準拠してある場合にのみ、.abs
や.arg
を追加するということがSwift 2で可能になったのです。
あとは、/
はこんな感じ。
public func / <T>(lhs:Complex<T>,
rhs:Complex<T>) -> Complex<T> {
return (lhs * rhs.conj) / rhs.norm
}
共役.conj
とノルム.norm
は、Complex<T>
であれば必ず持っているので、
次に"cmath”
public func exp<T:RealType>(z:Complex<T>)
-> Complex<T> {
let r = T.exp(z.re)
let a = z.im
return Complex(r * T.cos(a), r *
T.sin(a))
}
「博士の愛した数式」e ** (x+y.
そのままですね。ただしcos
でなくてT.
と書いています。RealType
のProtocolでpublic static func cos(x:Self)->Self
となっているものを指定しています。なぜメソッドではなく型関数(static method)かというと、.cos
まで補完されてしまってちょっと驚きなのです。Rubyistsなどからするとちょっと残念かもしれませんが。
Todo
というわけで弾言します。総称関数とプロトコルを制するものが、
とはいえ、
というエラーメッセージでXcodeが真っ赤になったりするのですが、
あと、importFoundation
できるのに、#if
を書かずに済ませたいのに……。
それにしても、
本誌最新号をチェック!
Software Design 2022年9月号
2022年8月18日発売
B5判/
定価1,342円
- 第1特集
MySQL アプリ開発者の必修5科目
不意なトラブルに困らないためのRDB基礎知識 - 第2特集
「知りたい」 「使いたい」 「発信したい」 をかなえる
OSSソースコードリーディングのススメ - 特別企画
企業のシステムを支えるOSとエコシステムの全貌
[特別企画] Red Hat Enterprise Linux 9最新ガイド - 短期連載
今さら聞けないSSH
[前編] リモートログインとコマンドの実行 - 短期連載
MySQLで学ぶ文字コード
[最終回] 文字コードのハマりどころTips集 - 短期連載
新生「Ansible」 徹底解説
[4] Playbookの実行環境 (基礎編)