実体ではないのですから。
それを可能にするのが、Protocolなのです。
実践例[swift-complex]
論より証拠。実例を見てみましょう。[swiftcomplex]というgithub projectがあります。Swiftの演習用に筆者がずっと書いてきたものですが、今回の記事のためにごっそり書き直しました(図2 ) 。
図2 swift-complex
要は複素数を使うためのライブラリです。使い心地はRubyであれば、
require 'cmath'
include cmath
Pythonであれば
from cmath import *
したときにとてもよく似ています。余談ではありますが、去年のクリスマスにリリースされたPerl 6では、複素数サポートは組み込みです。
そのまま遊べるように、Playgroundも用意してあります。みんな大好きマルデンブロー集合もこのとおり(図3 ) 。
図3 みんな大好きマンデルブロー集合
で、自分で言うのもなんですが、割によく書けていると思います。よく書けているというのは、
Swiftの特徴を活かしていること
演算子関数による直感的な操作
→Playground
クロスプラットフォーム
→OS XやiOSだけでなく、Linuxでも動く
「実数」の実装が入れ替え可能であること
現時点でDoubleだけでなく、FloatやIntもサポート
任意制度の数値ライブラリを別途用意すれば、それを使うことも可能
では実際に見てみましょう。500行ちょっとしかないので全部掲載したいところですが、紙幅が足りないので要点だけ(リスト2 ) 。
リスト2 複素数実装のサンプル
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)
}
まず、Protocolで複素数の要素として最低限満たしておくべき要件を列挙しておきます。
Swiftの組込み型からの初期化をサポートしていること
基本的な四則演算をサポートしていること
というのをSwift語で書き下しただけです。
で、Intはすでにこれらを満たしているので、
extension Int : ArithmeticType {}
とすでにArithmeticType
に準拠(conform)していますよ、と一言で済みます。
ここまではSwift 1の時代からあったのですが、Swift 2の時代ですごいのは、ここです(リスト3 ) 。
リスト3 ArithmeticTypeの実装サンプル
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.i
とかと書くと(0+42.i)
になるのは、ここでやっています。わざわざ他の型で実装する必要はないんです。
Protocolといういうのは、あくまで規約であって、その規約をどう満たすかはそのProtocolを準拠する型(types)に任されてきたのですが、Swift 2になって、Protocol Extensionで実装まで一緒にできるようになったのです。つまり、準拠する型が10あれば10、100あれば100、一挙にメソッドやプロパティを追加することが可能になったのです。これはすごい。
実際Swift 2では、Array
だけではなくSequence
に準拠する型であればすべて.map
や.reduce
が使えるようになったのですが、まさにProtocol Extensionの賜物と言えるでしょう。
ではいよいよComplex
を見てみましょう(リスト4 ) 。
リスト4 Complexの実装
public struct Complex<T:ArithmeticType> : Equatable, CustomStringConvertible, Hashable {
public typealias Element = T
public var (re, im): (T, T)
//// [中略]
}
リスト4は見てのとおり、ArithmeticType
に準拠したTによる総称型です。1+1.i
はComplex<Int>
、1.0+1.0.i
はComplex<Double>
になるわけです。
ところで賢明な読者は、この時点で絶対値.abs
や偏角.arg
がないことに気づかれるかもしれません。これらは複素数自体がComplex<Int>
、つまりガウス整数であっても整数におさまるとは限らないからです。
うまいこと、整数の場合は整数を返さないメソッドを持たせず、しかし「実数」の場合にはこれを追加するということができるのでしょうか? できます。そう。Swiftならね。
まず、ArithmeticType
の要件をすべて満たす上位互換Protocolを1つ追加します(リスト5 ) 。
リスト5 上位互換Protocolの追加
public protocol RealType : ArithmeticType, FloatingPointType {
static var EPSILON:Self { get } // for =̃
}
そしてこれをProtocol Extensionで拡張します(リスト6 ) 。
リスト6 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
}
要するに、三角関数や指数関数などをLinuxであればGlibc
、そうでなければFoundation
からごっそり持ってくるわけです。ちなみにProtocol Extensionと型のExtensionで同名の識別子がある場合、型のほうが優先して使われます。実際[swift-complex]でも、Float
に関してはいったんDouble
に変換してFloat
に戻すのではなくFloat
のままで計算するためにcosf
など末尾にf
がついた関数を使いたかったので、extension Float
で上書きしています。
そうしたうえで、リスト7 です。
リスト7 Swift 2ならではの実装例
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>
であれば必ず持っているので、複素数の掛け算と複素数と実数の掛け算でこのように定義できるわけです。実際のソースをGitHubでご覧いただくと、ほとんどすべての演算子がこのような1行定義になっています。
次に"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.i) = e**r * (cos(y) + i*sin(y))
そのままですね。ただしcos
でなくてT.cos
と書いています。RealType
のProtocolでpublic static func cos(x:Self)->Self
となっているものを指定しています。なぜメソッドではなく型関数(static method)かというと、既存の型をなるべく上書きしたくなかったから。かつてはメソッドとして追加していたのですが、その方法だとXcodeなどで.cos
まで補完されてしまってちょっと驚きなのです。Rubyistsなどからするとちょっと残念かもしれませんが。
Todo
というわけで弾言します。総称関数とプロトコルを制するものが、Swiftを制するのだ、と。Swift 2のprotocol extensionで、その可能性はさらに高まりました。
とはいえ、Swift 2でもまだ至らないことも多々あります。たとえばプロトコルに準拠するためのメソッドや関数を書いている最中には、“ type Foo does not conform to protocol Bar”
というエラーメッセージでXcodeが真っ赤になったりするのですが、具体的にどんなメソッドやプロパティが足りないかを一挙に調べてくれるとうれしいのですが。
あと、LinuxでもimportFoundation
できるのに、これがOSX/iOSのそれと全然違うってのも悩ましい。なるべく#if
を書かずに済ませたいのに……。
それにしても、これほど書いていて楽しい言語というのはそうありません。[ IBM Swift Sandbox ]のおかげでブラウザからも試せるようになったSwift、皆さんもぜひ遊んでみてください。
第1特集
MySQL アプリ開発者の必修5科目
不意なトラブルに困らないためのRDB基礎知識
第2特集
「知りたい」「 使いたい」「 発信したい」をかなえる
OSSソースコードリーディングのススメ
特別企画
企業のシステムを支えるOSとエコシステムの全貌
[特別企画]Red Hat Enterprise Linux 9最新ガイド
短期連載
今さら聞けないSSH
[前編]リモートログインとコマンドの実行
短期連載
MySQLで学ぶ文字コード
[最終回]文字コードのハマりどころTips集
短期連載
新生「Ansible」徹底解説
[4]Playbookの実行環境(基礎編)