Posted at

Swift 5 の AdditiveArithmetic protocol とは何か


AdditiveArithmetic とは

AdditiveArithmetic は Swift 5 で新しく導入された protocol です(SE-0233)。

Swift には数に関する protocol がいくつもありますが、そのうちのひとつに Numeric protocol があります(参考:Numeric 関連の階層)。Numeric は、数の性質のうち、たし算、ひき算、かけ算の3つの演算を定義する protocol です。

AdditiveArithmeticNumeric よりも条件が弱い protocol で、たし算とひき算の2つの演算を定義する protocol です。

Swift 5 での AdditiveArithmetic 導入にともない、NumericAdditiveArithmetic を継承してかけ算を追加する形になりました。つまり、Numeric 関連の階層において、EquatableNumeric の間に AdditiveArithmetic が入ったことになります。

余談:数学用語になじみがある方向けに補足します(なじみがない方は飛ばしてください)。上記の AdditiveArithmetic(加法群) に、Numeric に対応する、と言われれば分かりやすいかと思います。


AdditiveArithmetic の定義

AdditiveArithmetic protocol の定義は以下のようなものです。単純なたし算とひき算を定義しています。

protocol AdditiveArithmetic {

static var zero: Self
static func + (Self, Self) -> Self
static func += (Self, Self)
static func - (Self, Self) -> Self
static func -= (Self, Self)
}

標準の数値型である IntFloatAdditiveArithmetic に準拠しています。また、独自の型で AdditiveArithmetic に準拠するものを作ることもできます。

この protocol の応用例として、ドキュメントには以下の例が記載されています。AdditiveArithmetic 型の要素を持つ Sequence にメソッドを宣言しています。

extension Sequence where Element: AdditiveArithmetic {

func sum() -> Element {
return reduce(.zero, +)
}
}

IntFloat などの具体的な型を使う代わりに AdditiveArithmetic を使うことで、sum()AdditiveArithmetic に準拠したあらゆる型に対応できる汎用メソッドとなっています。

なお、これにかけ算を追加したものが Numeric となります。

protocol Numeric : AdditiveArithmetic {

static func * (Self, Self) -> Self
static func *= (Self, Self)
}


AdditiveArithmetic が有益な場合

従来の Swift で、すでに Numeric が存在していますし、それで足りるようにも思えます。なぜわざわざ Swift 5 で AdditiveArithmetic を導入したのでしょうか。

それは、AdditiveArithmetic に準拠するのは簡単だが Numeric に準拠するのは難しい、というたぐいの型があるからです。つまり、たし算やひき算は問題なくできるが、かけ算に相当する演算がない、という型です。

具体的な例としては、ベクトルがそうです。ベクトル同士のたし算やひき算はあるので AdditiveArithmetic に準拠するのは簡単です。しかし、ベクトル同士のかけ算に相当する演算はないため Numeric に準拠するのは難しいです。そのため、AdditiveArithmetic が導入されたほうが都合が良いのです。


具体例 : 3次元ベクトル型

AdditiveArithmetic の実例を見るために、3次元ベクトルの型 Vector3D を作ってみます。

struct Vector3D {

typealias Scalar = Float
var x: Scalar
var y: Scalar
var z: Scalar
}

これを、AdditiveArithmetic に準拠させます。

extension Vector3D : AdditiveArithmetic {

static var zero = Vector3D(x: 0, y: 0, z: 0)

static func + (lhs: Vector3D, rhs: Vector3D) -> Vector3D {
return Vector3D(x: lhs.x + rhs.x, y: lhs.y + rhs.y, z: lhs.z + rhs.z)
}
static func - (lhs: Vector3D, rhs: Vector3D) -> Vector3D {
return Vector3D(x: lhs.x - rhs.x, y: lhs.y - rhs.y, z: lhs.z - rhs.z)
}
static func += (lhs: inout Vector3D, rhs: Vector3D) {
lhs = lhs + rhs
}
static func -= (lhs: inout Vector3D, rhs: Vector3D) {
lhs = lhs - rhs
}
}

これで、前述した sum() メソッドが Vector3D に対して使えるようになります。

extension Sequence where Element: AdditiveArithmetic {

func sum() -> Element {
return reduce(.zero, +)
}
}

let sum = [Vector3D(x: 1, y: 2, z: 3),
Vector3D(x: 4, y: 3, z: 2),
Vector3D(x: -3, y: -3, z: -3)].sum()
if sum == Vector3D(x: 2, y: 2, z: 2) {
print("😄")
}

なお、ベクトルの演算といえばたし算ひき算以外にもスカラー倍演算がありますが、AdditiveArithmetic の説明には関係がないのでここでは省略します。それを含めたコードは GitHub に置いています(usami-k/AdditiveArithmeticSample)。


AdditiveArithmetic のメソッドを別のメソッドで実装するパターン

AdditiveArithmetic に準拠させる際の実装ですが、いくつかのメソッドは別のメソッドから実装できます。

上記の例でも、+ を使って += を実装しましたし、- を使って -= を実装しました。

逆に、+= を先に実装して + を実装することもできます。

static func += (lhs: inout Vector3D, rhs: Vector3D) {

lhs.x += rhs.x ; lhs.y += rhs.y ; lhs.z += rhs.z
}
static func + (lhs: Vector3D, rhs: Vector3D) -> Vector3D {
var lhs = lhs ; lhs += rhs ; return lhs
}

また、次のような単項演算子 - を定義しておくと、+ を使って二項演算子 - を実装することもできます。

// 単項演算子

prefix static func - (v: Vector3D) -> Vector3D {
return Vector3D(x: -v.x, y: -v.y, z: -v.z)
}
// 二項演算子
static func - (lhs: Vector3D, rhs: Vector3D) -> Vector3D {
return lhs + (-rhs)
}

逆に、二項演算子 - を先に実装して単項演算子 - を実装することもできます。

// 二項演算子

static func - (lhs: Vector3D, rhs: Vector3D) -> Vector3D {
return Vector3D(x: lhs.x - rhs.x, y: lhs.y - rhs.y, z: lhs.z - rhs.z)
}
// 単項演算子
prefix static func - (v: Vector3D) -> Vector3D {
return .zero - v
}


最後に

AdditiveArithmetic は、通常はあまり触れる機会はないかもしれません。しかし、上述の例で見たようにベクトル型を扱いやすくなるなどの利点があり、それが活用できる分野では役に立つこともありそうです。今後の展開が気になるところです。