書いて覚えるSwift入門

第46回 モジュールの分割法

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

モジュールの分割法

前回swift-floatingpointmathを紹介したところで終わりました。これはもともとモジュールではなくswift-complexの一部として実装されていましたが,Swift Package Managerの登場により別モジュールとして分割されました。

swift-floatingpointmathは何をするモジュールかといえば,やっていることは3つだけです。

  • 0.FloatingPointMathというプロトコルの定義
  • 1.protocol extensionによる(Cにおける<math.h>やJSにおけるMathオブジェクトが提供する)数学関数の提供
  • 2.DoubleおよびFloatFloatingPointMathへの準拠

実際に中身を見てみましょう。まずはプロトコルの定義から。

public protocol FloatingPointMath {
    init (_:Double)
    var asDouble:Double { get }
}

Doubleから初期化可能であること,Doubleへ変換可能であること。つまりDoubleとの相互変換が可能である型(type)であれば準拠可能なプロトコルということになります。実はこの相互変換を記述する点でSwiftには物足りなさがあります。対称性を考えればasDoubleではなく,

public Double {
    init(_:FloatingPointMath)
}

に相当する定義もprotocol FloatingPointMath{}の内部ではありますが,そうなっていないのでasDoubleというプロパティを用意しています。

protocol extension

次にprotocol extensionを見てみましょう。次のような定義が,数学関数の数だけ記述されています。

extension FloatingPointMath {
    public static func acos (_ x:Self)->Self {
        return Self(
            Foundation.acos (x.asDouble)
        )
    }
    // ...
}

要するにDoubleに変換してからFoundationの数学関数で計算した結果をもとの型に戻しているだけですが,これによりFloatingPointMathに準拠している型はこれらの数学関数を自前で実装しなくても,一応はDoubleで計算したのと同様の結果を得ることができるわけです。

そして,DoubleおよびFloatFloatingPointMathに準拠させるために,

extension Double : FloatingPointMath {
    public var asDouble:Double { return self }
    public static func acos (_ x:Double)->Double {
        return Foundation.acos(x)
    }
    /// ...
}
extension Float : FloatingPointMath {
    public var asDouble:Double { return
Double(self) }
}

という最低限の拡張を施しています。Doubleで数学関数を別途定義しているのは,不要な型変換をなくすため。

一見このモジュールは単なる無駄に見えます。import Foundationすれば同モジュールが提供している機能はすべて使えるのですから。

しかし,Foundationには2つの問題があります。

  • 0.あまりに多くの関数がトップレベルにimportされる
  • 1.組込みの数値型しか使えない

たとえばlogといってもMath.logConsole.logではまるで別の意味になりますが,Foundationlogは問答無用でMath.logの意味になります。その一方でNSLogは後者の意味なのですからややこしい。数学関数だけ,明示的に,importする手段がSwift本体には用意されていないのです。

しかしそれだけであればFloatingPointMathをわざわざ定義せずとも,Foundation.logと明記すればいいだけです。実際にこの記法はFloatingPointMathのprotocol extensionでも用いられており,return Foundation.acos(x)return acos(x)と書いたら無限再帰してしまいます。

しかし組込みの数値型しか使えないのは問題です。せっかくSE-0067で浮動小数点が固定的な型ではなくプロトコルとして整理されたのに,DoubleFloatしか使えないのでは宝の持ち腐れというものではないですか。

実際筆者はswift-bignumというパッケージで任意精度有理数BigRatと任意精度浮動小数点数BigFloatを提供しており,双方とも標準のFloatingPointプロトコルに準拠しています。しかしFloatingPointMathの数学関数はFloatingPointプロトコルでは未定義なので,FloatingPointMathプロトコルをパッケージとして独立させたうえで利用するようにしま した。

エレガントに複素数を扱う

こうして「共通項をくくり出した」おかげで,swift-complexの実装もエレガントになりました。同パッケージにおける複素数は,まず,

public protocol ComplexNumeric : Hashable {
    associatedtype Element: SignedNumeric
    var real:Element { get set }
    var imag:Element { get set }
    init(real:Element, imag:Element)
}

すなわち符号付数値SignedNumericを要素Elementとして持つプロトコルとして定義され,要素が浮動小数点数であるものはさらに,

public typealias ComplexFloatElement =
FloatingPoint & FloatingPointMath

public protocol ComplexFloat : ComplexNumeric &
CustomStringConvertible
    where Element: ComplexFloatElement {
}

と,ElementFloatingPointかつFloatingPointMathに準拠しているプロトコルとして定義されたうえで,最後にそのプロトコルに準拠するstructとしてComplex型が定義されています。

public struct Complex<R:ComplexFloatElement> :
ComplexFloat {
    public typealias NumericType = R
    public var (real, imag):(R, R)
    public init(real r:R, imag i:R) {
        (real, imag) = (r, i)
    }
}

つまりComplex型の要素はDoubleFloatだけではなく前述のBigRatBigFloatもそのまま使えるということです。

PONSの爆誕

数学的な数値の性質をプロトコルとしてまとめ,数値の型から独立させる。筆者はこれにProtocol Oriented Number System = PONSと名付けSwift 2で実装しました。当時はSE-0067もSwift Package Managerもなく,必要なものはすべて自前で実装したうえで1つのリポジトリに置いていましたが,Swift本体の数値もよりProtocol Orientedとなり,SPMも導入されたことをふまえてモジュール分割を進めた結果,コードがぐっとコンパクトになったうえに,任意精度整数はより活発にメンテされている他者のものを使えるようになりました。

図1図2はPONSの型とプロトコルの相関関係クリックで拡大できます⁠⁠。図1がSwift 2時代で,図2が現在なのですが,濃いグレーで示されている自前実装が減り,Swift標準の薄いグレーにとって代わられていることが見てとれます。

図1 Swift 2時代のPONSの型とプロトコルの相関関係

図1 Swift 2時代のPONSの型とプロトコルの相関関係

図2 Swift最新版での実装状況

図2 Swift最新版での実装状況

次回予告

今年はSwiftがリリースされてから5年目。不要だった機能は削ぎ落とされ,不足していた機能は補われ,不満はかなり減りました。しかしなくなったわけではありません。次回はSwiftへの不満と将来への期待を取り上げることにします。

Software Design

本誌最新号をチェック!
Software Design 2019年12月号

2019年11月18日発売
B5判/200ページ
定価(本体1,220円+税)

  • 第1特集
    サーバーレスでめざせ! インフラ管理ゼロなシステム
    基本のAWS Lambdaからフルサーバーレスまで
  • 第2特集
    12月6日発売『みんなのPHP』コラボ!
    PHPプログラミング・アラカルト
    なぜ愛され,なぜ進化するのか?
  • 短期連載
    Webサービスを裏で支える!! バッチ処理設計の勘所
    【1】バッチ処理の特徴と設計のポイント
  • 短期集中連載
    Webエンジニアのための時短スマホアプリ開発
    【2】React.jsをおさらいし,React NativeによるUI構築の基本を知ろう

著者プロフィール

小飼弾(こがいだん)

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

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

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