書いて覚えるSwift入門

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

モジュールの分割法

前回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 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の実行環境(基礎編)

おすすめ記事

記事・ニュース一覧